Fully verified11,644

Networking

Pi-hole

Pi-hole filters DNS requests for every device on a home network, including TVs and phones that cannot use a browser extension. Its web dashboard shows query statistics, client activity, and allow and deny lists. This recipe runs the official container with persistent SQLite data, a local control panel, and DNS bound to localhost until you choose a trusted LAN or VPN address.

Overview

Pi-hole is a DNS resolver with blocklists. Put it on a trusted network and set it as the DNS server in your router: requests for advertising and tracking domains are blocked for phones, TVs, and other devices without browser add-ons. It suits a home network or a small private network whose administrator can control DHCP or the DNS settings of each client. It is a self-hosted alternative to services such as NextDNS and AdGuard DNS.

The web interface shows recent queries, blocked requests, clients, and list management. Its interface is currently available in English. Pi-hole can also serve local DNS records. DNS filtering cannot remove ads served from the same domain as desired content, and clients that override the network DNS with their own encrypted resolver can bypass it.

This recipe runs one official container. Its pi-hole-data volume holds pihole.toml, Gravity and query databases, and list data. The DNS port starts on 127.0.0.1; choose a specific trusted LAN or VPN address when ready to serve other devices. The web panel remains on the host’s loopback address.

Security assessment

The container uses Docker’s bridge network without privileged mode, host networking, or access to the Docker socket. Only port 53 is needed for DNS; the built-in DHCP and NTP services are not exposed. The official image’s own healthcheck checks DNS service readiness.

Do not bind DNS to a public interface: an open resolver can be used in amplification attacks. A Pi-hole administrator can inspect the domains queried by every client, so keep the web panel behind its password and HTTPS when accessing it remotely. The .env password and backup archives are sensitive. See the official Docker guide and configuration reference.

Update tracking

These are stored results of separate checks, not real-time data. An unknown status means the check has not been performed yet.

New releases

Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.

Current version 2026.09.0Oct 3, 2026, 9:49 AM UTC

Critical vulnerabilities

Result of a dedicated Docker image scan, not a Compose configuration check.

No critical vulnerabilities foundOct 9, 2026, 1:58 PM UTC

Breaking updates

A new major version is found by comparing version numbers. A compatibility verdict is entered by hand after reading the release notes and the migration steps.

Not checkedNot checked

compose.yaml

Upstream Compose changes relative to the verified recipe.

Upstream Compose is not trackedNot checked

Outdated Docker images

Pinned image tags checked for newer versions.

All images are currentOct 9, 2026, 1:58 PM UTC

From observation to action

Update plan

The recipe already pins the latest known release. The steps below show the evidence retained for that version.

2026.09.02026.09.0
  1. Open the release evidence

    Open this release on GitHub ↗ The link is evidence to review, not proof of compatibility.

  2. Review the version boundary

    The version numbers do not cross a major compatibility boundary. No compatibility verdict has been recorded.

  3. Prepare image-tag changes

    No image-tag change is currently planned.

  4. Follow the documented migration shape

    Change the tag and restart. Release-specific migration steps still come from the release notes and installation guide.

  5. Protect the rollback point

    No version change is currently planned. The recipe includes a backup procedure. Restore has been tested.

  6. Verify the updated recipe

    An exact Fully verified check for this target was recorded on 2026-10-09.

Read the update procedure in the installation guide ↓

Specifications

Security and exposure

Installation levels

The status shows how fully the published recipe meets each level's requirements.

Ready

For home

A minimal setup for a local network or access through a VPN.

  • Single server
  • Local access
  • Minimum components

The recipe covers every requirement at this level.

Partial

For a VPS

A public deployment with protected access and backups.

  • Domain
  • HTTPS via reverse proxy
  • Off-server backup

You still need to add: domain, https via reverse proxy, off-server backup.

Not supported

Reliable

A setup designed for predictable operations and observability.

  • Separate database
  • Healthcheck
  • External monitoring
  • CPU and memory limits

You still need to add: separate database, external monitoring, cpu and memory limits.

Screenshots

Quick start

Read the full guide first: it explains external access, backups, and data locations.

Prepare settingscp .env.example .env
Launchdocker compose up -d
Check statusdocker compose ps

Step-by-step guide

1. Prepare the server

Use Ubuntu 22.04+ or Debian 12+ with Docker Engine and Docker Compose v2.24+. Allow at least one CPU core, 512 MB RAM, and 2 GB of disk; 1 GB RAM and 4 GB of disk leave room for retained query history and several blocklists. No separate database is needed. Check the tools and whether anything already uses DNS port 53:

docker --version
docker compose version
sudo ss -luntp | grep ':53 ' || true

systemd-resolved often listens on 127.0.0.53, which does not necessarily conflict with this recipe’s 127.0.0.1 bind. Change the host resolver only if the actual address conflicts. Do not point the host’s /etc/resolv.conf at Pi-hole before Pi-hole is running.

2. Prepare the recipe

Place compose.yaml, .env.example, backup.sh, restore.sh, and proxy/ in a dedicated directory, then create your private environment file:

mkdir -p ~/services/pi-hole
cd ~/services/pi-hole
cp .env.example .env
chmod 600 .env
openssl rand -hex 24

Replace the PIHOLE_WEB_PASSWORD example in .env with the generated value. Never start a real instance with the sample password. The variables are:

  • PIHOLE_VERSION: pinned official image release;
  • PIHOLE_WEB_PASSWORD: administrator password;
  • PIHOLE_DNS_BIND: host address for TCP and UDP DNS, initially 127.0.0.1;
  • PIHOLE_DNS_PORT: host DNS port, normally 53;
  • PIHOLE_WEB_PORT: host-only admin port, initially 8080;
  • PIHOLE_VOLUME: Docker volume holding all persistent Pi-hole data;
  • TZ: IANA time zone for charts and schedules.

The Compose file sets FTLCONF_dns_listeningMode=ALL because DNS arrives through Docker’s bridge. It does not publish Pi-hole’s DHCP (67/udp), NTP (123/udp), or self-signed HTTPS (443/tcp) listeners.

3. Start on a VPS

docker compose config --quiet
docker compose pull
docker compose up -d --wait
docker compose ps

With the defaults, DNS and the panel are reachable only from the host. On a VPS, keep DNS on a private VPN address, such as WireGuard or Tailscale, by setting PIHOLE_DNS_BIND to that interface address. Never use 0.0.0.0 or a public address: public DNS resolvers are abused for amplification. The panel can be accessed with an SSH tunnel:

ssh -L 8080:127.0.0.1:8080 user@server.example

Open http://localhost:8080/admin/ and sign in with the password from .env. The web interface is currently English-only. Test the DNS server on the host:

dig @127.0.0.1 pi.hole

Trusted local network

Set PIHOLE_DNS_BIND in .env to this server’s specific LAN address, for example 192.168.1.10, and recreate the container:

docker compose up -d --wait
docker compose port pi-hole 53/udp

Permit UDP and TCP 53 only from the trusted subnet in your firewall. For UFW:

sudo ufw allow from 192.168.1.0/24 to 192.168.1.10 port 53 proto udp
sudo ufw allow from 192.168.1.0/24 to 192.168.1.10 port 53 proto tcp

Then set 192.168.1.10 as the DNS server in your router’s DHCP settings, or configure clients individually. Test from another device with nslookup pi.hole 192.168.1.10. Do not enable Pi-hole’s DHCP service with this recipe: bridge networking does not carry the broadcasts it needs.

Domain and HTTPS

The proxy/Caddyfile, proxy/nginx.conf, and proxy/traefik.yaml examples publish only the web panel; substitute your own dns.example.com. Caddy obtains certificates automatically; Nginx expects a Certbot certificate; Traefik uses the configured letsencrypt resolver. If Traefik runs in a container, replace 127.0.0.1 with a host gateway address that container can reach. Keep the Pi-hole login enabled and restrict panel access to trusted users. An HTTP proxy does not carry ordinary DNS traffic on port 53.

Backup

All persistent settings, blocklists, Gravity database, and query history live in the pi-hole-data Docker volume (or the name in PIHOLE_VOLUME). The script stops Pi-hole briefly so its SQLite files are consistent:

chmod +x backup.sh restore.sh
./backup.sh

Copy the resulting ./backups/pi-hole-*.tar.gz off the server and protect it; it contains DNS history and configuration. The .env password is separate and must be stored securely too. The web interface’s Teleporter export is useful for selected settings, but this full-volume archive is needed for a complete restore.

Restore

Restoring replaces the entire PIHOLE_VOLUME contents. The script creates a safety backup of the current state first:

./restore.sh ./backups/pi-hole-YYYYMMDDTHHMMSS.NNNNNNNNNZ.tar.gz
docker compose ps

Restore the matching .env separately if the password or volume name changed.

Update

Read the Docker release notes and create a backup. Change PIHOLE_VERSION in .env to a tested, dated tag; never use latest. Then:

./backup.sh
docker compose pull
docker compose up -d --wait
docker compose logs --tail=100 pi-hole

Rollback

Restore the previous pinned tag in .env, pull it, and recreate the container. If the new version changed pihole.toml or the SQLite databases, restore the pre-update archive too; an older image may not read newer data:

docker compose pull
docker compose up -d --wait
./restore.sh ./backups/pi-hole-YYYYMMDDTHHMMSS.NNNNNNNNNZ.tar.gz

The restore script first saves the current data, then replaces the volume. Check the panel and a DNS query after the rollback.

Stop or remove

docker compose down stops and removes the container while retaining its data. After moving the router and clients to another DNS server, permanently remove the stored data with docker compose down --volumes (or remove the volume named by PIHOLE_VOLUME). Keep any off-server backups you still need. Removing the resolver while clients still point to it interrupts name resolution.

Sources: official Docker setup, Docker configuration and capabilities, the hardware prerequisites, and Pi-hole v6 configuration.

Report a recipe problem

Tell us how it went on your server

Opens a prefilled GitHub issue. A GitHub account is required; submitting the issue makes it public.

Troubleshooting

Port 53 is already in use

Check the exact listening address, not just the port:

sudo ss -luntp | grep ':53 '
docker compose logs --tail=100 pi-hole

127.0.0.53 used by systemd-resolved is distinct from the default 127.0.0.1 bind. If a service holds 127.0.0.1:53 or all interfaces, choose a specific free LAN/VPN address or reconfigure the conflicting service. Avoid changing the host resolver until a working alternative is configured.

The container is unhealthy

The official image’s healthcheck makes a DNS request to its own FTL process:

docker compose ps
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q pi-hole)"
docker compose logs --tail=150 pi-hole

If the logs show a corrupted database, restore a known good volume archive. If they show a permissions or disk error, inspect the volume and free space.

A LAN device cannot resolve names

docker compose port pi-hole 53/udp
nslookup pi.hole 192.168.1.10

If Compose reports 127.0.0.1, set PIHOLE_DNS_BIND to the actual LAN address and recreate the container. Permit both TCP and UDP 53 in the LAN firewall. Make sure the router hands out the same server address over DHCP.

Ads still appear, or a site breaks

Check the query log: if the client’s requests are absent, it may use its own DNS-over-HTTPS or a different DNS server. Network filtering cannot remove content served from the same domain as the page itself. For a broken site, find its blocked domain in Query Log and add only that domain to the allowlist. Avoid disabling all blocking to fix one false positive.

The web password does not work

Verify that .env contains the intended PIHOLE_WEB_PASSWORD and recreate the container with docker compose up -d. The FTLCONF_webserver_api_password environment setting controls the password on every start. Avoid pasting a password into shell history or posting docker inspect output publicly.

A restored instance looks new

Check that PIHOLE_VOLUME names the same volume as the backup and that the archive contains pihole.toml and gravity.db:

docker compose config
docker volume inspect pi-hole-data
tar -tzf ./backups/pi-hole-YYYYMMDDTHHMMSS.NNNNNNNNNZ.tar.gz | grep -E 'pihole.toml|gravity.db'

Do not create new lists before finding the correct volume or restoring the pre-update archive.

Official sources

Latest release: 2026-09-19 · GitHub Stars: 11,644 · metadata checked Oct 3, 2026, 9:49 AM UTC

Disclaimer

Fossary is an independent informational catalog and is not affiliated with the developers of the listed applications. We collect information from public sources and verify published recipes, but we do not develop, distribute, or control these applications.

A listing or validation status is not a requirement, endorsement, or personal recommendation to use an application. You decide whether it is suitable and use it at your own risk. We do not warrant an application's security, availability, or fitness for purpose and, to the extent permitted by law, are not liable for resulting loss or damage. Review its license, security settings, data processing, and backup procedures before installation.