The Ansible method deploys Pi-hole and Unbound as Docker containers using theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/damianiglesias/pihole-ubuntu-deploy/llms.txt
Use this file to discover all available pages before exploring further.
install_pihole.yml playbook. It is the recommended approach for virtual machines and any environment where you want repeatable, idempotent infrastructure. Pi-hole and Unbound run side-by-side on a custom Docker bridge network (172.20.0.0/16) — Pi-hole at 172.20.0.2 and Unbound at 172.20.0.5 — with Pi-hole forwarding all DNS queries to Unbound on port 5335.
Prerequisites
- Ubuntu Server 20.04, 22.04, or 24.04
- Ansible installed on the machine you run the playbook from (the playbook installs Docker itself on the target host)
Installation Steps
(Optional) Edit the inventory
Open To deploy to a remote host, replace the entry with the target’s address and credentials. See Inventory below for details.
ansible/inventory.ini. By default it targets localhost over a local connection — no SSH required:(Optional) Set your admin password
Open
install_pihole.yml and change the pihole_password variable from the insecure default:What the Playbook Does
The playbook contains ten ordered tasks. Each is idempotent and safe to re-run.Task 1 — Stop and disable native services
Stops and disables any previously installed native instances of the following services to free up ports
53, 80, and 5335. Errors are ignored so the playbook succeeds on fresh machines where these services were never installed.pihole-FTLunboundlighttpdsystemd-resolved
Task 2 — Fix DNS resolution for Docker
Sets
DNSStubListener=no in /etc/systemd/resolved.conf. This prevents systemd-resolved from occupying port 53 even when it is restarted later, allowing the Pi-hole container to bind to that port correctly. systemd-resolved is then restarted via an Ansible handler (Restart systemd-resolved) to apply the change.Task 3 — Write a generic resolv.conf
Overwrites
/etc/resolv.conf with a minimal, known-good nameserver so the host can resolve DNS during the rest of the playbook:Task 4 — Remove conflicting Docker packages
Purges any existing Docker CE packages that may conflict with the
docker.io package from Ubuntu’s official repos:docker-cedocker-ce-clicontainerd.iodocker-compose-plugin
Task 5 — Install dependencies
Installs required packages via
apt with update_cache: yes:curlnet-toolsdocker.iodocker-compose-v2
Task 6 — Create project directory
Creates the directory that will hold the Docker Compose file and Pi-hole data volumes. Defaults to
/root/pihole-docker (controlled by the project_dir variable).Task 7 — Write the Docker Compose file
Renders and writes
docker-compose.yml to {{ project_dir }}. See Docker Compose Config below for the full generated file.Task 8 — Launch containers
Starts both containers in detached mode:This command runs from inside
{{ project_dir }}.Task 9 — Configure UFW firewall rules
Opens the three ports required by Pi-hole (TCP protocol for all):
| Port | Purpose |
|---|---|
22 | SSH access |
80 | Pi-hole web interface |
53 | DNS queries |
Docker Compose Config
The followingdocker-compose.yml is generated by the playbook and written to {{ project_dir }}/docker-compose.yml:
The timezone is hardcoded to
Europe/Madrid in the TZ environment variable. To change it, edit the TZ value under the pihole service’s environment block in install_pihole.yml before running the playbook.| Container | Image | IP Address | Exposed Port |
|---|---|---|---|
pihole | pihole/pihole:latest | 172.20.0.2 | 53/tcp, 53/udp, 80/tcp |
unbound | mvance/unbound:latest | 172.20.0.5 | 5335/udp |
172.20.0.5#5335 (PIHOLE_DNS_1). The second upstream DNS server is set to no (PIHOLE_DNS_2) so all queries are routed exclusively through Unbound. The custom bridge network pihole_net uses the 172.20.0.0/16 subnet to give both containers stable, predictable addresses.
Inventory
The defaultansible/inventory.ini targets localhost using a local connection (no SSH):
localhost line with your target host’s details:
| Field | Description |
|---|---|
ansible_host | IP or hostname of the target server (can be set inline as the first token) |
ansible_user | SSH user to connect as (e.g. ubuntu, root) |
ansible_ssh_private_key_file | Path to the SSH private key for authentication |
Variables
Absolute path on the target host where the Docker Compose file and Pi-hole data volumes (
etc-pihole/, etc-dnsmasq.d/) are stored. The directory is created by Task 6 if it does not exist.Password for the Pi-hole web admin interface. This value is injected into the
WEBPASSWORD environment variable of the pihole container. Always change this from the default before running the playbook.