2025 Unbound and Pi-Hole in Docker
2025 Unbound and Pi-Hole in Docker

Unbound and Pi-Hole in Docker in 2025

Unbound: https://hub.docker.com/r/mvance/unbound

Pi-Hole: https://hub.docker.com/r/pihole/pihole

Start of the project

At the start of every Linux project a good update is recommended this project is no different

sudo apt update && sudo apt upgrade -y

The next step is going to be building your compose file this is like the brain to your drocker project. This is where you setup your storage, networking, and applications for the project.

sudo nano docker-compose.yaml

version: '3.8'

networks:
  dns_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.23.0.0/16

services:
  pihole:
    container_name: pihole
    hostname: pihole
    image: pihole/pihole:latest
    networks:
      dns_net:
        ipv4_address: 172.23.0.7
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
    environment:
      TZ: ${TZ}
      WEBPASSWORD: ${WEBPASSWORD}
      PIHOLE_DNS_: '172.23.0.8#5053'
      DNSMASQ_LISTENING: all
    volumes:
      - pihole_data:/etc/pihole/
      - pihole_dnsmasq:/etc/dnsmasq.d/
    restart: unless-stopped

  unbound:
    container_name: unbound
    image: mvance/unbound:latest
    networks:
      dns_net:
        ipv4_address: 172.23.0.8
    volumes:
      - ./unbound:/opt/unbound/etc/unbound
      - /dev:/opt/unbound/etc/unbound/dev
    ports:
      - "5053:53/tcp"
      - "5053:53/udp"
    restart: unless-stopped

volumes:
  pihole_data:
  pihole_dnsmasq:


version: '3.8'

networks:
  dns_net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.23.0.0/16

services:
  pihole:
    container_name: pihole
    hostname: pihole
    image: pihole/pihole:latest
    networks:
      dns_net:
        ipv4_address: 172.23.0.7
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
    environment:
      TZ: ${TZ}
      WEBPASSWORD: ${WEBPASSWORD}
      PIHOLE_DNS_: '172.23.0.8#5053'
      DNSMASQ_LISTENING: all
    volumes:
      - pihole_data:/etc/pihole/
      - pihole_dnsmasq:/etc/dnsmasq.d/
    restart: unless-stopped

  unbound:
    container_name: unbound
    image: mvance/unbound:latest
    networks:
      dns_net:
        ipv4_address: 172.23.0.8
    volumes:
      - ./unbound:/opt/unbound/etc/unbound
      - /dev:/opt/unbound/etc/unbound/dev
    ports:
      - "5053:53/tcp"
      - "5053:53/udp"
    restart: unless-stopped

volumes:
  pihole_data:
  pihole_dnsmasq:


List of TZs that can be set, can be found here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

Link to unedited .conf file https://github.com/JamesTurland/JimsGarage/blob/main/Unbound/unbound.conf

The next step is to make a folder where the unbound config file will be stored

mkdir unbound

now it is time to make the config file for unbound this file will be used to tell unbound how we wont it to act. I have removed all the comments from this file to make it easer to copy and past reference the links above for the comments.

sudo nano unbound/unbound.conf

server:
    cache-min-ttl: 300
    directory: "/opt/unbound/etc/unbound"
    do-ip4: yes
    do-ip6: yes
    do-tcp: yes
    do-udp: yes
    edns-buffer-size: 1232
    interface: 0.0.0.0@5053
    port: 53
    prefer-ip6: no
    rrset-roundrobin: yes
    username: "_unbound"
    log-local-actions: no
    log-queries: no
    log-replies: no
    log-servfail: no
    logfile: /dev/null
    verbosity: 0
    infra-cache-slabs: 4
    incoming-num-tcp: 10
    key-cache-slabs: 4
    msg-cache-size: 142768128
    msg-cache-slabs: 4
    num-queries-per-thread: 4096
    num-threads: 3
    outgoing-range: 8192
    rrset-cache-size: 285536256
    rrset-cache-slabs: 4
    minimal-responses: yes
    prefetch: yes
    prefetch-key: yes
    serve-expired: yes
    so-reuseport: yes
    aggressive-nsec: yes
    delay-close: 10000
    do-daemonize: no
    do-not-query-localhost: no
    neg-cache-size: 4M
    qname-minimisation: yes

    access-control: 127.0.0.1/32 allow
    access-control: 192.168.0.0/16 allow
    access-control: 172.16.0.0/12 allow
    access-control: 10.0.0.0/8 allow
    access-control: fc00::/7 allow
    access-control: ::1/128 allow

    auto-trust-anchor-file: "var/root.key"
    chroot: "/opt/unbound/etc/unbound"
    deny-any: yes
    harden-algo-downgrade: yes
    harden-below-nxdomain: yes
    harden-dnssec-stripped: yes
    harden-glue: yes
    harden-large-queries: yes
    harden-referral-path: no
    harden-short-bufsize: yes
    hide-http-user-agent: no
    hide-identity: yes
    hide-version: yes
    http-user-agent: "DNS"
    identity: "DNS"

    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: fd00::/8
    private-address: fe80::/10
    private-address: ::ffff:0:0/96

    ratelimit: 1000
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
    unwanted-reply-threshold: 10000
    use-caps-for-id: yes
    val-clean-additional: yes

remote-control:
    control-enable: no
server:
    cache-min-ttl: 300
    directory: "/opt/unbound/etc/unbound"
    do-ip4: yes
    do-ip6: yes
    do-tcp: yes
    do-udp: yes
    edns-buffer-size: 1232
    interface: 0.0.0.0@5053
    port: 53
    prefer-ip6: no
    rrset-roundrobin: yes
    username: "_unbound"
    log-local-actions: no
    log-queries: no
    log-replies: no
    log-servfail: no
    logfile: /dev/null
    verbosity: 0
    infra-cache-slabs: 4
    incoming-num-tcp: 10
    key-cache-slabs: 4
    msg-cache-size: 142768128
    msg-cache-slabs: 4
    num-queries-per-thread: 4096
    num-threads: 3
    outgoing-range: 8192
    rrset-cache-size: 285536256
    rrset-cache-slabs: 4
    minimal-responses: yes
    prefetch: yes
    prefetch-key: yes
    serve-expired: yes
    so-reuseport: yes
    aggressive-nsec: yes
    delay-close: 10000
    do-daemonize: no
    do-not-query-localhost: no
    neg-cache-size: 4M
    qname-minimisation: yes

    access-control: 127.0.0.1/32 allow
    access-control: 192.168.0.0/16 allow
    access-control: 172.16.0.0/12 allow
    access-control: 10.0.0.0/8 allow
    access-control: fc00::/7 allow
    access-control: ::1/128 allow

    auto-trust-anchor-file: "var/root.key"
    chroot: "/opt/unbound/etc/unbound"
    deny-any: yes
    harden-algo-downgrade: yes
    harden-below-nxdomain: yes
    harden-dnssec-stripped: yes
    harden-glue: yes
    harden-large-queries: yes
    harden-referral-path: no
    harden-short-bufsize: yes
    hide-http-user-agent: no
    hide-identity: yes
    hide-version: yes
    http-user-agent: "DNS"
    identity: "DNS"

    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: fd00::/8
    private-address: fe80::/10
    private-address: ::ffff:0:0/96

    ratelimit: 1000
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
    unwanted-reply-threshold: 10000
    use-caps-for-id: yes
    val-clean-additional: yes

remote-control:
    control-enable: no

The last file the we need to make is the .env file this is where we set environment variables for the docker project

sudo nano .env

TZ=America/New_York
WEBPASSWORD=password
TZ=America/New_York
WEBPASSWORD=password

By default ubuntu uses its own DNS resolver but it gets in the way of setting up the server  so we need to turn it off and disable it

sudo systemctl stop systemd-resolved

sudo systemctl disable systemd-resolved

Now it is time lets start the project

docker compose up -d

You do not need to do this step but it is a test to make sure every thing in working

dig @127.0.0.1 example.com