Skip to content
appsgit

Deploy guide

How to self-host Pi-hole with Docker Compose

Block ads network-wide with Pi-hole v6 in Docker Compose: DNS on port 53, the FTLCONF web password, freeing port 53 on Ubuntu, router setup and updates.

  • Updated
  • Beginner
  • About 15 minutes

You will need

  • 1 vCPU / 512 MB RAM (a Raspberry Pi is enough)
  • Docker + Docker Compose v2
  • A static LAN IP address for the server
  • Access to your router's DNS or DHCP settings

What is Pi-hole?

Pi-hole is a DNS sinkhole that blocks ads, trackers and malware domains for every device on your network, including smart TVs and phones where browser extensions cannot run. It is open source under the EUPL-1.2 license and includes a web dashboard with query logs, per-client statistics, blocklist management and an optional DHCP server.

Requirements

  • Any always-on Linux machine on your home network. A Raspberry Pi, mini PC or VM with 512 MB of RAM is enough.
  • Docker Engine and Docker Compose v2.
  • A static IP on your LAN, set on the device or reserved in your router.
  • Access to your router to change the DNS server handed out to clients.

Step 1: Prepare the server

This guide assumes Ubuntu 24.04 with Docker installed; if you need Docker, use the official install guide. Ubuntu's systemd-resolved already listens on port 53, so free it first:

sudo sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
sudo sh -c 'rm /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf'
sudo systemctl restart systemd-resolved
mkdir -p ~/pihole && cd ~/pihole

Step 2: Create the Docker Compose file

Pi-hole v6 is configured through FTLCONF_ environment variables, which map directly to settings in pihole.toml. Save this as docker-compose.yml:

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:2026.09.0
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
      # Uncomment if Pi-hole will be your DHCP server:
      # - "67:67/udp"
    environment:
      TZ: "Europe/London"
      FTLCONF_webserver_api_password: "CHANGE_ME"
      FTLCONF_dns_listeningMode: "ALL"
      FTLCONF_dns_upstreams: "1.1.1.1;9.9.9.9"
    volumes:
      - ./etc-pihole:/etc/pihole
    cap_add:
      - NET_ADMIN # only required if Pi-hole is your DHCP server
      - SYS_NICE # optional: gives FTL more CPU priority

Replace CHANGE_ME with a strong password, for example from openssl rand -hex 32. FTLCONF_dns_listeningMode: "ALL" is required on Docker's default bridge network, because Pi-hole sees queries arriving from the Docker gateway rather than your LAN. This guide maps the web interface to port 8080 so port 80 stays free for a reverse proxy. Pin the image to a release from the Docker Pi-hole releases, since latest can jump to a new version without warning.

Never publish port 53 to the internet on a VPS. Open resolvers are abused for DDoS amplification. Use a firewall at your provider, or bind to your LAN address, for example "192.168.1.10:53:53/udp".

Step 3: Start and open the app

docker compose up -d
dig @127.0.0.1 example.com +short

If dig returns an IP address, DNS works. Open http://SERVER_IP:8080/admin and sign in with your password. Then point your network at Pi-hole: in your router's DHCP settings, set the DNS server to the Pi-hole's LAN IP and remove any secondary public DNS, otherwise devices bypass the blocking. Reconnect a device and watch its queries appear in the dashboard.

Step 4: Put it behind HTTPS

On a LAN, HTTPS for the admin page is optional. If you run a reverse proxy, Caddy can serve it with a certificate for an internal name:

pihole.home.example.com {
    redir / /admin/ 302
    reverse_proxy 127.0.0.1:8080
}

For a public certificate on an internal-only name, use Caddy's DNS challenge, or Nginx Proxy Manager with a DNS provider. Do not open the admin page or DNS port to the internet.

Backups and upgrades

All configuration lives in ./etc-pihole: pihole.toml, the gravity database with your lists, and local DNS records. Pi-hole also exports a Teleporter archive under Settings, Teleporter, which restores everything into a fresh install. Download one after each significant change.

Upgrades happen by replacing the container. pihole -up does not work inside Docker. Update the image tag, then:

docker compose pull && docker compose up -d

Troubleshooting

  • "Address already in use" on port 53: systemd-resolved or another DNS server is still running. Repeat Step 1 and check with sudo ss -lunpt | grep :53.
  • Dashboard shows queries only from one IP: all traffic is coming through the Docker gateway. That is expected on bridge networking; use network_mode: host if you need per-client statistics.
  • Some ads still appear: devices may use hard-coded DNS or DNS-over-HTTPS. Block outbound port 53 to other servers on your router where possible.
  • Login fails after an upgrade: check the password in the Compose file. If it was unset, find the generated one in docker compose logs pihole.

Next steps

Add blocklists under Lists, define local DNS records for your services, enable conditional forwarding for hostname resolution, and run a second Pi-hole for redundancy.

Spotted something out of date? Tell us and we will update the guide.

FAQ

Pi-hole questions

Still curious? Email info@appsgit.com.

What ports does Pi-hole use?

Pi-hole needs port 53 over both TCP and UDP for DNS, and serves its web interface on port 80 (and 443 with a self-signed certificate). Port 67/UDP is only needed if Pi-hole is your DHCP server.

Is Pi-hole free?

Yes. Pi-hole is free and open source under the EUPL-1.2 license. The project is funded by donations.

How do I set the Pi-hole admin password in Docker?

Set the FTLCONF_webserver_api_password environment variable in your Compose file. If you leave it out, Pi-hole generates a random password and prints it in the container logs.

Why does Pi-hole say port 53 is already in use on Ubuntu?

Ubuntu's systemd-resolved runs a DNS stub listener on port 53. Disable it with DNSStubListener=no in /etc/systemd/resolved.conf and restart systemd-resolved.

Should I expose Pi-hole to the internet?

No. An open DNS resolver on the internet gets abused for DNS amplification attacks. Keep port 53 on your LAN, and use a VPN to reach Pi-hole when you are away.

Pi-hole vs AdGuard Home?

Both block ads and trackers at the DNS level. AdGuard Home has built-in DNS-over-HTTPS and a single binary, while Pi-hole has a larger community, rich query logs and long-standing blocklist tooling.