Self-hosted • Privacy-first • No tracking
Home / Homelab / Pi-hole in Docker: The Complete Step-by-Step Installation and Hardening Guide
Homelab #docker#homelab#pihole#dns#ad-blocking ⏱ 13 min • 👁 7 • Aug 29, 2026

Pi-hole in Docker: The Complete Step-by-Step Installation and Hardening Guide

Install Pi-hole with Docker Compose, configure advanced DNS settings, reverse proxy, backups, and solve common issues. Full guide with copy-paste commands.

AdSense — Top (970x90) • Responsive
Pi-hole in Docker: The Complete Step-by-Step Installation and Hardening Guide

Introduction

Network-wide ad blocking is the cornerstone of a privacy-respecting home lab. Pi-hole, when deployed via Docker, offers a clean, reproducible, and easily updatable solution that doesn't pollute your host OS. Unlike manual installations that scatter files across your system, a Dockerized Pi-hole encapsulates everything into a single container, making backups trivial and rollbacks instant. This approach also allows you to run Pi-hole alongside other services like Unbound or WireGuard without conflicts.

In this guide, you will learn not just how to run docker run and forget, but how to build a production-grade Pi-hole setup. We will cover the exact Docker Compose file you need, with all volume mappings and environment variables explained. You will also learn how to configure your router to use Pi-hole as the DNS server, set up a reverse proxy for the web interface with SSL, implement automated backups, and harden the container against common security pitfalls.

By the end, you will have a fully functional, secure, and maintainable Pi-hole installation that serves your entire home network. Whether you are a beginner taking your first steps into self-hosting or an experienced homelabber looking for a reference, this guide provides everything in a copy-paste-ready format. No vague instructions, no “etc.”—just precise commands and configurations.

Let’s dive into the world of DNS-level ad blocking with Docker.

Prerequisites / Requirements

Before you begin, ensure your host system meets the following minimum and recommended specifications. The table below breaks down each requirement.

Component Minimum Recommended Notes
CPU 1 core 2 cores (ARM or x86) Pi-hole is lightweight; even a Raspberry Pi Zero 2 W works. x86_64, armv7, or arm64 are supported.
RAM 512 MB 1 GB The container typically uses 100-200 MB, but the OS and Docker overhead need headroom.
Storage 2 GB free 10 GB free Logs and blocklists can grow. Use an SSD for better performance.
Software Docker 20.10+ Docker 24+ with Compose v2 Install via official Docker repo. docker-compose standalone is deprecated; use docker compose plugin.
Network Static IP for host DHCP reservation You need a fixed IP for the DNS server to be reliable.
Router DNS settings configurable Any router that allows custom DNS You must be able to point clients to your Pi-hole IP.
Ports 53/udp, 53/tcp, 80/tcp, 443/tcp Same Port 53 must be free on the host. If systemd-resolved uses it, you must disable it.

Important: On Ubuntu 22.04+, systemd-resolved listens on port 53. You must stop and disable it before starting Pi-hole, or change Pi-hole's port (not recommended for production).

Step-by-Step Installation Guide

Follow these steps in order. Each step includes the exact command or configuration file you need.

Step 1: Prepare the Host System

First, update your package list and install required tools. Then disable systemd-resolved to free port 53.

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git

Now, stop and disable systemd-resolved:

sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved

Edit /etc/resolv.conf to use a public DNS temporarily (so you don't lose connectivity):

sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 1.1.1.1" > /etc/resolv.conf'

Note: This change is temporary and will be overwritten by Pi-hole's container once it runs. Do not skip this step, or Docker will fail to resolve external domains.

Step 2: Install Docker and Docker Compose Plugin

If you don't have Docker installed, use the official convenience script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Verify the installation and enable the Docker Compose plugin:

sudo docker --version
sudo docker compose version

If docker compose is not recognized, install the plugin manually:

sudo apt install -y docker-compose-plugin

Step 3: Create a Directory Structure for Pi-hole

Create a dedicated directory to keep all Pi-hole files organized. This will make backups and updates easier.

mkdir -p ~/pihole/{etc-pihole,etc-dnsmasq.d}
cd ~/pihole
  • etc-pihole : Stores Pi-hole's configuration, databases, and blocklists.
  • etc-dnsmasq.d : Stores custom dnsmasq configuration files.

Step 4: Write the Docker Compose File

Create a file named docker-compose.yml in the ~/pihole directory:

nano docker-compose.yml

Paste the following complete configuration. Do not omit any lines; this is the production-ready version.

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    restart: unless-stopped
    hostname: pihole
    domainname: lab.local
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
      - "443:443/tcp"
    environment:
      TZ: 'America/New_York'   # Change to your timezone
      WEBPASSWORD: 'your_strong_password'   # Set a strong password
      FTLCONF_LOCAL_IPV4: '192.168.1.100'   # Your host's static IP
      REV_SERVER: 'false'
      PIHOLE_DNS_: '1.1.1.1;1.0.0.1'   # Upstream DNS servers
      DNSSEC: 'true'
      DNSMASQ_LISTENING: 'all'   # Listen on all interfaces
      CONDITIONAL_FORWARDING: 'false'
    volumes:
      - './etc-pihole:/etc/pihole'
      - './etc-dnsmasq.d:/etc/dnsmasq.d'
    cap_add:
      - NET_ADMIN   # Required for DNS and DHCP
    dns:
      - 127.0.0.1   # Prevent Docker's internal DNS from interfering
      - 1.1.1.1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/admin/" ]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    networks:
      - pihole_net

networks:
  pihole_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

Explanation of key variables:

  • WEBPASSWORD: Set a strong password for the web interface. If empty, a random password is generated and shown in the logs.
  • FTLCONF_LOCAL_IPV4: Must match your host's IP address. This prevents Pi-hole from guessing incorrectly.
  • PIHOLE_DNS_: Upstream DNS resolvers. Use a semicolon to separate multiple servers.
  • DNSSEC: Enables DNS Security Extensions for better security.
  • cap_add: NET_ADMIN: Gives the container the necessary capability to modify network settings (required for DHCP mode).
  • dns: 127.0.0.1: Prevents Docker's internal DNS from hijacking queries.

Step 5: Start the Pi-hole Container

Run the following command to pull the image and start the container in detached mode:

sudo docker compose up -d

This will download the latest Pi-hole image and start it. Wait for the container to become healthy. Check the status with:

sudo docker compose ps

You should see the pihole service with status running and health healthy. If it's not healthy, wait 30 seconds and check again.

Step 6: Verify Pi-hole is Responding to DNS Queries

Test that Pi-hole is correctly resolving DNS queries. Use dig (install with sudo apt install dnsutils if needed):

dig @127.0.0.1 google.com

You should see a response with an IP address. Also, check the web interface by opening your browser to http://<your-host-ip>/admin/. Log in with the password you set.

Step 7: Configure Your Router to Use Pi-hole

To block ads network-wide, you must change your router's DHCP settings to assign Pi-hole's IP as the DNS server. The exact steps vary by router, but generally:

  1. Log into your router's admin panel.
  2. Find the DHCP or LAN settings.
  3. Set the primary DNS server to your Pi-hole host's IP (e.g., 192.168.1.100).
  4. Set the secondary DNS to a public resolver like 1.1.1.1 as a fallback.
  5. Save and reboot the router or renew DHCP leases on your devices.

After this, all devices on your network will use Pi-hole for DNS queries. You can verify by checking the “Query Log” in the Pi-hole web interface.

Step 8: Set a Static IP for Your Host (If Not Already Done)

Ensure your Pi-hole host has a static IP address, otherwise its IP may change and break DNS resolution. On Ubuntu, edit the netplan configuration or use your router's DHCP reservation feature.

For a quick netplan example (Ubuntu 22.04):

sudo nano /etc/netplan/01-network-manager-all.yaml

Add your static IP configuration:

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: no
      addresses:
        - 192.168.1.100/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [1.1.1.1, 1.0.0.1]

Apply the changes:

sudo netplan apply

Warning: Make sure you use the correct interface name (e.g., eth0, ens3). Use ip a to check.

Step 9: Update Pi-hole Regularly

To update Pi-hole to the latest version, simply pull the new image and recreate the container:

cd ~/pihole
sudo docker compose pull
sudo docker compose up -d

This will fetch the latest image and restart the container with the same configuration. Your data persists due to the volume mounts.

Step 10: Configure Web Interface Access (Optional but Recommended)

By default, the web interface is available on port 80. To avoid conflicts with other services, you can change the port in the compose file (e.g., 8080:80) but then you must update the FTLCONF_LOCAL_IPV4 to include the port? Actually, the web interface port is independent. If you change the host port, just access http://<host-ip>:8080/admin/. No other changes needed.

For better security, we'll later set up a reverse proxy with SSL.

Advanced Configuration / Optimization

Now that Pi-hole is running, let's harden and optimize it.

Reverse Proxy with SSL (Using Nginx Proxy Manager)

To access Pi-hole securely via HTTPS, set up Nginx Proxy Manager (NPM) as a reverse proxy. This also allows you to use a custom domain name.

First, create a new docker-compose.yml for NPM (or add to the same file, but separate is cleaner). Create a directory ~/nginx-proxy-manager and a compose file:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: npm
    restart: unless-stopped
    ports:
      - "80:80"   # HTTP
      - "443:443" # HTTPS
      - "81:81"   # Admin UI
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - pihole_net

networks:
  pihole_net:
    external: true

But first, we need to ensure both containers are on the same Docker network. Our Pi-hole compose creates a network named pihole_pihole_net. We'll use that network as external for NPM.

Start NPM:

mkdir -p ~/nginx-proxy-manager/{data,letsencrypt}
cd ~/nginx-proxy-manager
sudo docker compose up -d

Now, in NPM's admin UI (http://:81), add a new proxy host:

  • Domain: pi-hole.lab.local (or your actual domain)
  • Forward Hostname: pihole (the container name of Pi-hole)
  • Forward Port: 80
  • Enable SSL: Request a new Let's Encrypt certificate, or use your own.

Once configured, access Pi-hole via https://pi-hole.lab.local.

Backup Strategy

The volumes ./etc-pihole and ./etc-dnsmasq.d contain all critical data. You can back them up with a simple cron job. Create a script ~/pihole/backup.sh:

#!/bin/bash
BACKUP_DIR="/path/to/backup/location"
DATE=$(date +%Y%m%d-%H%M%S)
tar -czf "$BACKUP_DIR/pihole-backup-$DATE.tar.gz" -C ~/pihole etc-pihole etc-dnsmasq.d
# Keep only last 7 backups
find "$BACKUP_DIR" -name "pihole-backup-*" -mtime +7 -delete

Make it executable and add a cron job:

chmod +x ~/pihole/backup.sh
crontab -e

Add the following line to run the backup daily at 3 AM:

0 3 * * * /home/youruser/pihole/backup.sh

Security Hardening

  1. Do not expose port 53 to the internet: Only allow DNS queries from your local network. Use firewall rules to block external access.
  2. Use a non-standard web interface port: If you don't use a reverse proxy, change the host port for the web UI to something like 8080 to avoid automated scans.
  3. Enable DNSSEC: Already set in the compose file. Verify in the Pi-hole settings under DNS.
  4. Disable the web interface's default admin path: You can use the VIRTUAL_HOST environment variable to restrict access, but this is complex. Instead, rely on the reverse proxy and strong password.
  5. Run the container as a non-root user: Pi-hole's official image runs as root by default. To harden, you can override the user, but this may break some features. For now, rely on container isolation.
  6. Update the blocklists regularly: Pi-hole automatically updates lists, but you can also add custom lists from sources like Firebog for extra protection.

Troubleshooting Common Issues

Error/Symptom Likely Cause Solution
Port 53 already in use systemd-resolved is running sudo systemctl stop systemd-resolved and sudo systemctl disable systemd-resolved. Then restart Pi-hole.
Container restarts in a loop Incorrect FTLCONF_LOCAL_IPV4 or volume permissions Check the logs with sudo docker compose logs pihole. Ensure the etc-pihole directory is writable by the container (run sudo chown -R 1000:1000 etc-pihole).
DNS queries timeout Firewall blocking port 53 Allow UDP/TCP 53 on your firewall: sudo ufw allow 53/udp and sudo ufw allow 53/tcp.
Web interface not loading Port 80 conflict with another service Change the host port in the compose file (e.g., 8080:80) and re-run docker compose up -d.
No ads blocked Clients are not using Pi-hole as DNS Check your router's DHCP settings. Ensure the DNS is set to your Pi-hole IP. Also, clear the DNS cache on clients.
Container exits with code 137 Out of memory Increase RAM or add a memory limit to the container. Check dmesg for OOM killer.
Cannot pull image Network issue or proxy Check internet connectivity and DNS resolution. Try sudo docker pull pihole/pihole:latest manually.
Time zone wrong TZ environment variable not set correctly Update the TZ variable in the compose file and recreate the container.

Conclusion and FAQ

You now have a fully functional Pi-hole running in Docker, with a robust configuration, reverse proxy, and backup strategy. This setup will protect your entire network from ads and trackers, while giving you full control over your DNS traffic. The modular nature of Docker makes it easy to update, back up, and even migrate to a new host. Remember to regularly check the Pi-hole dashboard for blocked queries and to update your blocklists for maximum protection.

FAQ

1. Can I run Pi-hole on a Raspberry Pi with Docker?

Yes, Pi-hole is designed to run on ARM devices. The official Docker image supports armv7 and arm64. Just ensure you have Docker installed on your Raspberry Pi OS (64-bit recommended) and follow the same steps. The performance is excellent even on a Pi 3 or 4.

2. How do I change the web interface password?

You can change it by editing the WEBPASSWORD environment variable in your docker-compose.yml and then running sudo docker compose up -d to recreate the container. Alternatively, you can change it from the web interface under Settings > Web Interface, but the environment variable will override it on the next restart.

3. Does Pi-hole block all ads?

Pi-hole blocks ads that are served from known ad domains. It cannot block ads that are embedded in the same domain as the content (e.g., YouTube ads). For those, you need a browser extension or a more advanced solution like a proxy. However, Pi-hole covers the vast majority of banner ads and trackers.

4. Can I use Pi-hole with a VPN for remote ad blocking?

Yes, you can combine Pi-hole with WireGuard or OpenVPN. The VPN server can push Pi-hole's IP as the DNS server to clients, so even when you are away from home, your queries are filtered. Set up the VPN container on the same Docker network as Pi-hole for easy communication.

5. How do I migrate Pi-hole to a new host?

Back up the etc-pihole and etc-dnsmasq.d directories, then copy them to the new host. Install Docker and create the same directory structure. Place the backup files in the correct locations, and start the container with the same docker-compose.yml. Your configuration and query history will be preserved.

Now go ahead and enjoy a cleaner, faster, and more private internet experience with your self-hosted Pi-hole.

⚠️ Content disclosure: This article was AI-assisted. Versions and commands can change quickly in the self-hosted world — always verify against the official documentation before running anything in production.
AdSense — In-article (responsive)

Related Guides