Skip to main content
  1. Open Source/


Pi-hole & Unbound DNS Docker Setup #

This is a docker compose setup which starts a Pi-hole and nlnetlab’s Unbound as upstream recursive DNS using official (or ready-to-use) images. The main idea here is to add security, privacy and have ad and malware protection, everything hosted locally.

If you want to learn more about why you want to have exactly this setup, read a detailed explanation here.

Prepare #

Understand the Setup #

This setup works on a machine that does not itself already has DNS running (i.e. port 53 is already used). If you have a setup like that (e.g. running on a Synology NAS with a Directory Server), you would need a setup that creates a Mac VLAN so the container appears with a different IP. In this case check out this example here.

It is designed to have 2 containers running next to each other and do not aim to combine both programs in one. The idea is to minimize the work needed to adapt provided containerized versions of Pi-hole and Unbound, i.e. use the official images, therefore making it easier to upgrade each.

Prerequisites #

First you need a recent version of Docker installed which at least supports Docker compose v2. Further you may want to have a server or IoT device where this stack can run on, since this should be reachable by every other client 24/7. Finally, don’t forget to change your default DNS server to the server IPs address of your server.

Configuration #

The main configuration can be set in the .env file which overwrites the ENV variables in the docker-compose.yml - change it to your liking:

WEBPASSWORD= # set the password to use in the Web Admin UI
HOST_IP_V4_ADDRESS= # the IP of the host the Pi-hole runs on - defaults to localhost
TIMEZONE= # set your timezone (used to schedule cron jobs e.g.)

Deploy Containers #


Start the stack with going to the root of the repo and do:

 docker compose up --build -d --remove-orphans

To stop everything:

 docker compose down

Pro-Tip, if you want to directly deploy to a remote you can do

 docker compose -H "ssh://your-remote-host" up --build -d --remove-orphans

Use Web UI #

If you didn’t change anything and start this on your local machine you can access the Pi-hole web ui with


The default password is changeMeNow.

Test Setup #

To test if Pi-Hole with unbound is working correctly you can use the test domain I set up in Unbound. In your terminal (you might need to install nslookup) do:

nslookup localhost

This command will use localhost as DNS, if you are running it on a different machine, use the appropriate IP.

This should return the IP

Server:         localhost
Address:        ::1#53


if setup correctly it should also work without forcing DNS


Advanced #

Persistence after Restart #

By default, Pi-hole will forget everything after a restart of the docker container. To change that you need to set a docker volume to show Pi-hole where to save the configuration. You need to map /etc/Pi-hole/ and /etc/dnsmasq.d/ to a directory on the server. Read here if you want to learn more about volumes.

There is an example in the docker-compose.yml:

    container_name: ...
# RECOMMENDED: Uncomment and adapt if you want to persist Pi-hole configurations after restart
#    volumes:
#      - "/var/lib/docker/volumes/pihole/pihole:/etc/pihole/"
#      - "/var/lib/docker/volumes/pihole/dnsmasq.d:/etc/dnsmasq.d/"

Pi-hole Configurations #

In the docker-compose.yml you can add or change the Pi-hole Docker standard configuration variables in

    container_name: ...
      # here

Check out possible configurations here.

Additionally, you can change various settings in your Pi-hole instance (e.g. the used ad-list) through the web ui. I won’t get into detail here apart from recommending as a good default starting list.

Upgrade Base Images #

In the docker-compose.yml change the used Pi-hole version by changing

    container_name: ...
    image: Pi-hole/Pi-hole:2023.03.1 # <- update image version here, see:
    hostname: ...

and Unbound by changing the FROM in ./unbound/Dockerfile

# Update the version here, I use the docker build from
FROM mvance/unbound:1.17.1

Define Local A-Records #

If you want to resolve certain domains locally you can set A-Records in ./unbound/conf/a-records.conf. There are already examples, but to add a new record do:

# Example: Resolve all * addresses to the same ip of the main reverse proxy / router

local-zone: "" redirect
local-data: " 86400 IN A"

Check here the full documentation or tutorial to learn more.

Unbound, Forwarders and Manual Configuration #

Unbound is set as a recursive DNS, because all forwarders in ./unbound/conf/a-records.conf are commented out. If you prefer to use cloudflare or any other public DNS as upstream instead of having the slight performance impact of directly asking the nameservers, then you can enable the respective server by removing the comment (but then using Unbound at all has little value.

If you want to fine-tune the Unbound configuration, you can add the file ./unbound/conf/unbound.conf (see an example here) and Unbound will use it.

Limitations #

Supported Platforms #

Currently, this setup will only support platform type amd64, that means it will not run on machines that e.g. have an ARM architecture like the Raspberry Pi. While the official Pi-hole image supports multi-arch, MatthewVance’s unbound image does not. There is, however, a solution: there is a specific build for arm/v7 which can be found on Docker hub. Just update the Dockerfile in ./unbound/Dockerfile:

FROM mvance/unbound-rpi:1.17.1

Watchtower #

If you use tools like Watchtower to be notified about image updates - this will not work with Unbound here since we re-build it to create a self-contained, stateless image. It is possible to use the image mvance/unbound directly in the docker-compose and mount the configuration files to unbound instead of pre-building it. See MatthewVance readme on how to do that.

Links #

Credits #

Similar Projects #

Further Information #

License #

Copyright 2023 Patrick Favre-Bulle

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Patrick Favre
Patrick Favre
Software Engineer currently working as architect, backend dev, cloud engineer, IT ops rookie. Cryptography and security are my passions.