Pihole + unbound docker setup on Raspberry Pi

Pihole + unbound docker setup on Raspberry Pi

Pihole is DNS based Ad blocking solution. It can also be used to enhance your home network security by filtering out malicious domain and provide privacy protection by preventing unnecessary telemetry data leaking out.

I have mentioned couple times in my previous posts. As pihole project has very good documentation of installation, even with the configuration of unbound recursive DNS server, I don’t feel the need to repeat the normal straightforward installation process. However, there’s not much guide talking about the details of pihole + unbound setup in docker container. And during the migration from straight installation to docker containers, I met lot of unexpected issues and mistakes. I think it worth to share my experience and my configuration to save your time doing the troubleshooting.

Why move pihole and unbound to docker container?

Docker adds another abstraction layer on top of OS.

It increases the complexity of initial configuration, but gives you flexibility of future maintenance and migration.

Neat and clean is the main reason people like docker. No need to install hundreds of library and packages into Operation System which may mess up.

Each service is a container. Configuration can be managed by file and docker-compose.yaml. Very quick to replicate the configuration to different devices.

And by using Raspberry pi, considering the SD card may wear after constant write, to backup the docker container configuration is the easiest way to recovery the configuration.


So how to setup pihole and unbound into docker container on Raspberry pi?


You need a Raspberry Pi and with OS (Raspbian) installed.

Raspbian has already configured with SSH access and static IP address / DNS resolver.

Raspbian installed docker packages including docker-compose. (search google or docker website if you don’t know how to)

File and Structure

Assume you have logged in Raspbian with user ‘pi‘, your home directory will be /home/pi/ .

Create below folders and files with the right path with mkdir command.

mkdir /home/pi/pihole

mkdir /home/pi/pihole/etc-dnsmasq.d

mkdir /home/pi/pihole/etc-pihole

mkdir /home/pi/unbound

Unbound Configuration

Let’s get recursive DNS server unbound configuration right first.

Create some config files under /home/pi/unbound/

/home/pi/unbound/a-records.conf this file is mandatory required as we mapped volume of unbound docker image, otherwise the container will fail to start. You can leave it empty or put your local custom a records if you need. Below is example.

# A Record
#local-data: "somecomputer.local. A"

# PTR Record
#local-data-ptr: " somecomputer.local."

/home/pi/unbound/unbound.conf main configuration file unbound service. It will automatically generate a default one if you not create it. For this guide, let’s define the unbound.conf first. Example below, modify by your situation.

# If no logfile is specified, syslog is used
# logfile: "/var/log/unbound/unbound.log"
verbosity: 0

access-control: allow
access-control: allow
access-control: allow
access-control: allow
port: 5053
do-ip4: yes
do-udp: yes
do-tcp: yes

# May be set to yes if you have IPv6 connectivity
do-ip6: no

# You want to leave this to no unless you have *native* IPv6. With 6to4 and
# Terredo tunnels your web browser should favor IPv4 for the same reasons
prefer-ip6: no

# Use this only when you downloaded the list of primary root servers!
# If you use the default dns-root-data package, unbound will find it automatically
# I have to quote out this root-hints, as it causing container endless restarting for a new installation. You can add root-hints back after first run. 
#root-hints: “/opt/unbound/etc/unbound/root.hints”

# Trust glue only if it is within the server's authority
harden-glue: yes

# Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
harden-dnssec-stripped: yes

# Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
# see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
use-caps-for-id: no

# Reduce EDNS reassembly buffer size.
# Suggested by the unbound man page to reduce fragmentation reassembly problems
edns-buffer-size: 1472

# Perform prefetching of close to expired message cache entries
# This only applies to domains that have been frequently queried
prefetch: yes

# One thread should be sufficient, can be increased on beefy machines. In reality for most users running on small networks or on a single machine, it should be unnecessary to seek performance enhancement by increasing num-threads above 1.
num-threads: 1

# Ensure kernel buffer is large enough to not lose messages in traffic spikes
so-rcvbuf: 1m

# Ensure privacy of local IP ranges
private-address: fd00::/8
private-address: fe80::/10

private-domain: plex.direct

And download root.hints to /home/pi/unbound/

wget -O root.hints https://www.internic.net/domain/named.root

Unbound initial configuration is done.

Pihole and Docker Compose setup

This is the key part of this guide.

Create docker-compose.yml in your favourite directory with the content below.

version: '3'

    driver: bridge
        - subnet:

    container_name: pihole
    hostname: pihole
    image: pihole/pihole:latest
    - "53:53/tcp"
    - "53:53/udp"
    - "80:80/tcp"
    - "443:443/tcp"
    - 'TZ=Australia/Sydney'
    - 'WEBPASSWORD=yourpasswd'
    - 'DNS1='
    - 'DNS2=no'
    - '/home/pi/pihole/etc-pihole/:/etc/pihole/'
    - '/home/pi/pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/'
    restart: unless-stopped
    container_name: unbound
    image: mvance/unbound-rpi:latest
    - /home/pi/unbound:/opt/unbound/etc/unbound
    - "5053:5053/tcp"
    - "5053:5053/udp"
      disable: true
    restart: unless-stopped

Start the services

Let’s start unbound container first

sudo docker-compose up -d unbound

If everything goes right, you will see something says pi_dns_net has been created a new container is up.

You can now test if unbound is working as expected

dig www.google.com @ -p 5053

dig www.google.com @ -p 5053

If all return normal results then unbound is up running.

Then start pihole container.

sudo docker-compose up -d pihole

You can test if pihole is working

dig www.google.com @ -p 53

dig www.google.com @ -p 53

You can also try to access http://your-Raspberry-pi-ip/admin/index.php to configure pihole blocklist and etc.

Override container configuration

If there’s any configuration you need to change, you can first add environment into docker-compose file if the image support.

Otherwise, you can touch the configuration file directly.

For example, if you want to reduce the pihole cache size from 10000 to 0, let unbound do the caching. You can modify /home/pi/pihole/etc-dnsmasq.d/01-pihole.conf to set cache-size=0 . Save the file then restart dnsmasq service from pihole admin portal.

If you want to add a custom configuration file for pihole or unbound, just add *.conf file under the mapped volume.

Then restart the container.

sudo docker stop <container name>

sudo docker-compose up -d <container name>

It will usually recreate the container if there’s any configuration change.

Commands to troubleshoot

sudo docker ps

sudo docker inspect <container name, such as pihole or unbound>

sudo docker logs pihole

Maintenance and Update

sudo docker pull pihole/pihole:latest

sudo docker pull mvance/unbound-rpi:latest

sudo docker stop pihole

sudo docker stop unbound

sudo docker rm pihole

sudo docker rm unbound

sudo docker-compose up -d unbound

sudo docker-compose up -d pihole


Hope this post help you and save your time. Please leave comment if you have any question.


Written by Felix. Licensed under CC BY-NC-SA 3.0 Unported.

Leave a Reply


  • Liju

    Super useful. Thanks!

    2 years ago Reply
  • kevin

    There is an error in your docker-compose file.
    driver: bridge
    – subnet:

    driver: and ipam: should both be at the same level

    2 years ago Reply
  • Jack

    I am getting a warning that rcvbuf was not granted 306448 , what is this and what do I need to do to grant it.

    2 years ago Reply
    • FelixOwner

      @Jack: Which step you get this message?

      2 years ago Reply
      • Jack

        @Felix: I completed your tutorial. But when I check unbounds logs. It gives me that warning. I dont know of this is a bad thing?

        2 years ago Reply
        • Jack

          @Jack: Also dnssec is sometimes not validating signatures. How come? Harden dnssec stripped is on yes.

          Also how do I setup unbound dns over https? I read thats a feature now.

          2 years ago Reply
          • Jack

            @Jack: Sorry I mean doh

            2 years ago
          • Jack

            @Jack: Lol again sorry not doh but dot, u can delete that other comment

            2 years ago
  • ch33baguy

    Thank you for your work!
    Really appreciate what you have done here.

    2 years ago Reply
  • Tno


    Just for your information, you should also add this line to the unbound config to prevent warnings from the containers integrated health check:

    test: [“CMD”, “drill”, “@”, “-p 5053”, “cloudflare.com”]

    There is a healthcheck entry in the docker file which goes to the default port :53 and causes a waring message in the log:

    HEALTHCHECK –interval=5s –timeout=3s –start-period=5s CMD drill @ cloudflare.com || exit 1

    By the way, there are also some log entries during the start of the container (tested with unbound-rpi version 1.13.0):

    unbound[1:0] warning: so-rcvbuf 1048576 was not granted. Got 360448. To fix: start with root permissions(linux) or sysctl bigger net.core.rmem_max(linux) or kern.ipc.maxsockbuf(bsd) values.

    and there are also some udp connect failures to the IPv6 addresses of some root servers.

    error: udp connect failed: Cannot assign requested address for 2001:503:c27::2:30 port 53
    error: udp connect failed: Cannot assign requested address for 2001:500:12::d0d port 53
    error: udp connect failed: Cannot assign requested address for 2001:503:c27::2:30 port 53

    But these errors do not cause any issues (and I have also disabled IPv6 support within docker, that’s why this could not work). I would expect that unbound would not try to use IPv6 if settings in unbound.conf are set to not using IPv6 – anyway, no issue at all.


    2 years ago Reply
  • pancho-villa

    Hello, and thank you for this! It’s working great here! I was wondering about the root.hints file tho. Shouldn’t you change the unbound.conf to point at it in your docker volume location, it would look like this in unbound.conf:

    root-hints: “/opt/unbound/etc/unbound/root.hints”

    Otherwise when you download it into /home/pi/unbound/ it will default to the /var/lib/unbound/ location. Right? Or am I off base? Also, you have to update that file about every 6 months to update the root servers.

    Thanks again!

    2 years ago Reply
    • FelixOwner

      @pancho-villa: Hi pancho-villa, the root-hints path has been quoted out of this config file. And since you download it manually to the mapped unbound docker folder, unbound will automatically find and load it. No need to specify the actual path.

      2 years ago Reply
      • Atrocia6

        @Felix: Are you sure this is right? According to the unbound.conf(5) man page, “Default is nothing, using builtin hints for the IN class.” And according to Matthew Vance, the developer of the unbound docker image, you do need to include a root-hints: line pointing unbound to the downloaded root-hints, even when you’re putting it in the same directory as the config file: https://github.com/MatthewVance/unbound-docker/issues/21

        2 years ago Reply
  • Mike

    If you enable DHCP piHole fails withe the following error

    DNSMASQ_CONFIG FTL failed to start due to process is missing required capability NET_ADMIN

    2 years ago Reply
    • Mark

      @Mike: Clearly DHCP will never work with build as port 67 is not in the port list. I would suggest it has never been tested

      2 years ago Reply
  • The Dogfather

    Excellent. Thank you!

    2 years ago Reply
  • AppieHappie

    Thanks! I struggled a bit with the “root.hints” line in the supplied unbound.conf. Indeed, the container restarts endlessly. That can be fixed by surrounding the line in normal double quotes… Then the supplied path is not even necessary. My line: root-hints: “root.hints”

    2 years ago Reply
  • abielo

    Great guide. Thank you! The note on the a-record helped me deal with other missing record files which the default unbound.conf was using (I used the default configuration in my setup).

    2 years ago Reply
  • Alex.

    Thank you for this tutorial.
    In my opinion this line “dig http://www.google.com @ -p 5053″ should be “dig http://www.google.com @ -p 53″
    The Port number is wrong.

    1 year ago Reply
    • FelixOwner

      @Alex.: No, the intent is to test whether unbound service is running properly. Unbound is listening on port 5053.

      1 year ago Reply
  • gaayab

    I followed the instructions and updated .yml and .config file to suit my network. the unbound instance starts without any error but I cannot dig any websites. Can;t find any logs either.

    7 months ago Reply
  • waveformer

    fantastic guide, but with that configuration I’m not able to update Gravity ->

    5 months ago Reply


Pihole + unbound docker setup on Raspberry Pi
Pihole is DNS based Ad blocking solution. It can also be used to enhance your home network security by filtering out malicious domain and provide privacy protection by preven…
Scan QR code to continue reading