Documentation 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.
ansible/install_pihole.yml deploys Pi-hole and Unbound as Docker containers on an Ubuntu target host. The playbook is idempotent and uses become: yes for privilege escalation throughout all tasks.
Playbook Metadata
| Field | Value |
|---|---|
| Name | Deploy Pi-hole and Unbound on Ubuntu (Docker) |
| Hosts | pihole_server |
| Become | yes |
| Variables | project_dir, pihole_password |
Variables
The directory on the target host where the Docker Compose project is created. The playbook creates this directory with permissions
0755 if it does not already exist.The Pi-hole web admin password set via the
WEBPASSWORD environment variable in the Docker Compose configuration.Tasks
All 10 tasks execute in sequence. The playbook is designed to be safe to run multiple times — idempotent modules (apt, file, copy, lineinfile, ufw) will not duplicate work on subsequent runs.
1. Stop and Disable Native Services
Stops and disables four services that would conflict with the Docker containers by occupying ports 53, 80, and 5335.
- Module:
service - State:
stopped,enabled: no - Services:
pihole-FTL,unbound,lighttpd,systemd-resolved ignore_errors: yes— prevents the playbook from failing if any service is not installed
2. Fix DNS Resolution for Docker
Disables the
systemd-resolved DNS stub listener so that Docker containers can use the host’s DNS without conflict. Notifies the Restart systemd-resolved handler to apply the change.- Module:
lineinfile - File:
/etc/systemd/resolved.conf - Regexp:
^#?DNSStubListener= - Line:
DNSStubListener=no - Notifies:
Restart systemd-resolvedhandler
3. Create Generic resolv.conf
Overwrites
/etc/resolv.conf with a minimal Google DNS entry so the host has working DNS resolution during the rest of the play.- Module:
copy - Destination:
/etc/resolv.conf - Content:
nameserver 8.8.8.8 - Force:
yes
4. Remove Conflicting Docker Packages
Purges any conflicting Docker packages that may be present from a prior installation. This prevents version conflicts with the
docker.io package installed in the next task.- Module:
apt - State:
absent - Purge:
yes - Packages:
docker-ce,docker-ce-cli,containerd.io,docker-compose-plugin
5. Install Dependencies
Installs all required packages.
update_cache: yes ensures the APT cache is refreshed before the install.- Module:
apt - State:
present - Packages:
curl,net-tools,docker.io,docker-compose-v2
6. Create Project Directory
Creates the project directory defined by
project_dir. The directory is created with mode 0755. This task is idempotent — it will not fail if the directory already exists.- Module:
file - Path:
{{ project_dir }} - State:
directory - Mode:
0755
7. Create Docker Compose File
Writes the complete
docker-compose.yml to {{ project_dir }}. The pihole_password variable is interpolated into the WEBPASSWORD environment variable. See Docker Compose File below for the full output.- Module:
copy - Destination:
{{ project_dir }}/docker-compose.yml
8. Launch Containers
Starts the Pi-hole and Unbound containers in detached mode using the Compose file created in task 7.
- Module:
command - Command:
docker compose up -d - Working directory:
{{ project_dir }}
9. Configure UFW
Opens the three required inbound TCP ports using the
ufw module. Loops over port numbers 22, 80, and 53.- Module:
ufw - Rule:
allow - Protocol:
tcp - Ports:
22,80,53
Handler
The playbook defines one handler, triggered by task 2:| Handler name | Module | Action |
|---|---|---|
Restart systemd-resolved | service | Restarts the systemd-resolved service to apply the DNSStubListener=no change |
Docker Compose File
This is the completedocker-compose.yml that the playbook writes to {{ project_dir }}/docker-compose.yml. The WEBPASSWORD field is populated from the pihole_password variable at runtime.
Pi-hole forwards all DNS queries to Unbound at
172.20.0.5#5335 via the custom pihole_net Docker bridge network. The PIHOLE_DNS_2: 'no' setting disables any secondary upstream, making Unbound the sole resolver.Inventory
The defaultinventory.ini targets localhost via a local Ansible connection:
localhost with the target IP address or hostname and set the appropriate SSH connection parameters:
pihole_server group name must match the hosts: field in install_pihole.yml.
Running the Playbook
Edit inventory and variables
Update
inventory.ini with your target host and override pihole_password with a secure value — either in the playbook vars block or via --extra-vars on the command line.