Run Pi-hole on a Raspberry Pi with Docker Compose

Pi-hole is a DNS sinkhole that blocks ads and trackers network-wide, without relying on browser extensions. Running it as a Docker Compose service on a Raspberry Pi keeps the setup self-contained and easy to update — pull a new image, recreate the container, done. This tutorial assumes Docker and Docker Compose are already installed and covers only the Pi-hole side of the setup.
Prerequisites
A Raspberry Pi with Docker and Docker Compose already installed — see Install Docker and Docker Compose on a Raspberry Pi if you haven't set that up yet
A static IP (or DHCP reservation) assigned to the Raspberry Pi — every client on the network will point to this IP as its DNS server
An active internet connection
Callout — Static IP first If the Raspberry Pi's IP changes after a reboot, every device on the network loses DNS resolution. Set up a static IP (via
dhcpcd.confor a DHCP reservation on the router) before proceeding.
How this setup works
Pi-hole normally binds to port 53 (DNS) and port 80 (admin web UI) on the host. When you containerize it, the cleanest way to preserve that behavior is to run the container with network_mode: host — the container shares the Raspberry Pi's network stack directly, so Pi-hole answers DNS queries on the Pi's own IP exactly as if it weren't containerized. The alternative, bridge networking with explicit port mapping, works too, but UDP port mapping for DNS is fiddlier to get right and adds a layer of NAT you don't need on a dedicated DNS box — host networking is what most Pi-hole-in-Docker setups use in practice.
On Raspberry Pi OS there's usually nothing else already listening on port 53 (unlike Ubuntu, where systemd-resolved often claims it), but it's still worth a quick check before starting the container.
Steps
1. Confirm port 53 is free
sudo ss -tulpn | grep ':53'
If this returns nothing, you're clear to proceed. (You may see unrelated entries on port 5353 from avahi-daemon — that's mDNS/.local hostname resolution, a completely different port that never conflicts with Pi-hole's DNS on port 53.)
2. Create a project directory and compose file
mkdir -p ~/pihole/etc-pihole ~/pihole/etc-dnsmasq.d
cd ~/pihole
Create docker-compose.yml:
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
network_mode: host
environment:
TZ: 'Europe/Rome'
FTLCONF_webserver_api_password: 'change-me'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
cap_add:
- NET_ADMIN
restart: unless-stopped
Adjust TZ to your timezone and replace change-me with a real password for the admin panel.
Callout — Environment variable names Recent Pi-hole images use the
FTLCONF_*naming scheme for configuration environment variables, replacing the olderWEBPASSWORDvariable from earlier image versions. Check the tag you're pulling against the image's documentation if you're using anything other thanlatest.
3. Start the container
docker compose up -d
Docker pulls the pihole/pihole image and starts the container in the background, bound to the host network. Since it's created with restart: unless-stopped, it will come back up on its own after any reboot or power cycle, as long as Docker itself is enabled at boot.
4. Point devices at Pi-hole
For the blocking to apply network-wide, set the Raspberry Pi's IP as the primary DNS server on your router (preferred — applies to every device automatically), or configure it individually in each device's network settings.
5. Verify it's working
Open the admin panel from a browser:
http://<raspberry-pi-ip>/admin
Log in with the password you set in the compose file. The dashboard should start showing DNS queries from configured devices, along with a percentage of blocked requests.
Summary
We created a Docker Compose service for Pi-hole on a Raspberry Pi, using host networking so it binds directly to ports 53 and 80 exactly as a native install would, with persistent volumes for its configuration. Combined with Docker's own auto-start on boot, this gives you a network-wide ad blocker that comes back up on its own after every power cycle.


