Help with PiHole + unbound in Docker

Hi everyone,

I’m trying to run Pi-hole + Unbound in Docker on my server, with the help of Chatgpt, but I keep running into issues with Unbound. Pi-hole works fine, but Unbound keeps crashing with this error:

/unbound.sh: line 36: /opt/unbound/etc/unbound/unbound.conf: Is a directory
input in flex scanner failed
unbound exited with code 2

My setup:

  • OS: Debian 12 bookworm server

  • Docker version: latest

  • Pi-hole + Unbound running via Docker Compose

Directory structure:

~/docker/unbound/config/unbound.conf   (file, 243 bytes)
~/docker/unbound/data/root.hints      (file, 3311 bytes)

docker-compose.yml content:

version: "3"
services:
  unbound:
    container_name: unbound
    image: mvance/unbound:latest
    restart: unless-stopped
    ports:
      - "5335:5335/tcp"
      - "5335:5335/udp"
    volumes:
      - ./unbound/config:/opt/unbound/etc/unbound:ro
      - ./unbound/data/root.hints:/var/lib/unbound/root.hints:ro

  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    depends_on:
      - unbound
    restart: unless-stopped
    environment:
      TZ: "Europe/Amsterdam"
      WEBPASSWORD: "XXX"
      DNS1: "127.0.0.1#5335"
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d

What I’ve tried so far:

  • Verified unbound.conf is a file and not a directory

  • Placed unbound.conf inside a config directory

  • Ran docker-compose down -v and docker-compose up -d

  • Checked the file inside the container using:

    docker run -it --rm -v ~/docker/unbound/config:/opt/unbound/etc/unbound mvance/unbound:latest sh
    

    The unbound.conf file is visible inside /opt/unbound/etc/unbound

Problem:

Unbound still fails with the message that unbound.conf is a directory, even though the file exists in the config folder. I suspect something is wrong with the Docker volume mounting.

Has anyone successfully run Unbound in Docker with Pi-hole as upstream DNS? How should I configure the volumes and paths so that Unbound starts correctly?

Thanks in advance!

Unrelated to the Unbound issue, I notice a Pi-hole error.

The answers are probably using old data as source, returning invalid Pi-hole configuration.

WEBPASSWORD and DNS1 are invalid variables (they were used by Pi-hole v5).

Please check Pi-hole Docker documentation:

1 Like

Try to add a slash to the path, like suggested on this example:

    volumes:
      - ./unbound/config:/opt/unbound/etc/unbound/:ro
1 Like

Thanks, that’s helping me a lot.

I gave ChatGPT this information and was able to update my docker-compose.yml:

version: "3.8"

services:
  unbound:
    container_name: unbound
    image: klutchell/unbound:latest
    restart: unless-stopped
    command: \["unbound", "-d", "-c", "/etc/unbound/unbound.conf"\]
    ports:
      - "5335:5335/tcp"
      - "5335:5335/udp"
    volumes:
      - ../unbound/config/unbound.conf:/etc/unbound/unbound.conf/
      - ../unbound/data/root.hints:/etc/unbound/root.hints/

  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    depends_on:
      - unbound
    restart: unless-stopped

    environment:
      TZ: "Europe/Amsterdam"

      *# 🔐 Pi-hole v6 wachtwoord*
      FTLCONF_webserver_api_password: "XXX"

      *# 🌐 Gebruik Unbound via Docker netwerk*
      FTLCONF_dns_upstreams: "unbound#5335"

      *# 🌍 Nodig voor Docker networking*
      FTLCONF_dns_listeningMode: "all"

    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80/tcp"

    volumes:
      - ./etc-pihole:/etc/pihole

    cap_add:
      - NET_ADMIN

I let ChatGPT compose a reply, as he knows the problem better than I do:

I switched from mvance/unbound to klutchell/unbound as suggested and updated my docker-compose.yml to use the newer Pi-hole v6 environment variables (FTLCONF_*).

At this point:

  • Pi-hole starts correctly (still in “health: starting”, but no crashes)

  • Unbound no longer shows the “unbound.conf is a directory” error

  • My unbound.conf validates and runs fine when I start it manually inside a container (unbound -d -c ...)

However, I’m still running into an issue where the Unbound container keeps restarting:

unbound   Restarting

And DNS queries fail:

dig @127.0.0.1 -p 5335 google.com
;; no servers could be reached

I’ve verified the following:

  • unbound.conf is a file (not a directory)

  • File permissions are correct (readable)

  • Paths in docker-compose.yml are correct (../unbound/...)

  • Unbound runs fine manually in a test container with the same config

So it seems like:
:backhand_index_pointing_right: The config itself is valid
:backhand_index_pointing_right: But something in the container startup is still causing it to exit

At this point I suspect either:

  • an issue with how the container is started (command / entrypoint), or

  • a mismatch between expected paths inside the image

Does anyone see something obvious I’m missing in this setup?

Thanks in advance!

I’m resuming my ChatGPT session, but it seems to not be able to resolve the problem and I’m in a loop. Could someone take a look at my docker-compose.yml file and reply with some suggestions or preferably rewrite my docker-compose.yml file?

I thought I could set this up with the help of ChatGPT, whith almost 0 knowledge of Linux/Debian… but seems not. I did learn some commands and things, but else I have no clue…

I think AI is not giving you the most correct information.

Unrelated to your issue: the version: element is obsolete since v2, so you can remove this line from every compose file.

This is usually not necessary if you are using Klutchell's image.

Note about volumes:
ChatGPT recommendation for the volumes is different than the official compose file.
Your compose file is mounting a file (unbound.conf) as a directory /etc/unbound/unbound.conf/ (ending with a /). This is probably causing issues.

Also, as explained here, you don't need to modify the default config in /etc/unbound as it is configured to load custom configs from /etc/unbound/custom.conf.d.

You should try to use the official example found on klutchell/unbound-docker repository and adapt it adding your own env variables and volumes: unbound-docker/examples/pi-hole/docker-compose.yml at a9bdf92f13aa66561c5e254a0a80b8dba2c6366c · klutchell/unbound-docker · GitHub


You can use AI help, but don't believe the answer was correct without checking using other sources. My suggestion is to also read a lot to understand what the AI answer is doing.

In this case, I think the suggested image was a good choice (I actually use Klutchell's image here), but comparing the suggested compose file with the official example shows a lot of differences (and possibly mistakes).

1 Like

Oke I’m using the official docker-compose.yml configuration with minor tweaks:

removed:

  • ‘'67:67/udp' # Required if you are using Pihole as your DHCP server’
  • cap_add:
    • NET_ADMIN # Required if you are using Pi-hole as your DHCP server

edited the TZ to Europe/Amsterdam

My Pihole is UP and my unbound is UP, but when I’m trying to communicate (is that the correct term?) to google.com it refuses connection.

I’m trying to copy and paste the failed message from my terminal, but this forum won’t let me.

Please post the updated compose file.


Try to paste the error message (or file contents) inside a code block, using one line containing ``` before and another one after the error message, like this:

```
My error message 
```

The result will be:

My error message 

Also, you can simply copy and paste images directly on the post, when necessary.

volumes:
  pihole:

services:

  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      '53:53/tcp'
      '53:53/udp'
      '80:80/tcp'
      '443:443/tcp' *# FTL will generate a self-signed certificate. Not required to use*
    networks:
      default

    environment:
      # Sets upstream DNS to unbound sibling container*
      FTLCONF_dns_upstreams: unbound
      # If using Docker's default \`bridge\` network setting the dns listening mode should be set to 'all'*
      FTLCONF_dns_listeningMode: all
      # Set the appropriate timezone for your location*
      # See* https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
      # e.g. Europe/Paris, US/Pacific, Asia/Tokyo*
      TZ: 'Europe/Amsterdam'
      # See* https://github.com/pi-hole/docker-pi-hole *for all options*
    volumes:
      pihole:/etc/pihole'
    restart: unless-stopped

  unbound:
    image: klutchell/unbound
    networks:
      default
    healthcheck:
      # Use the drill wrapper binary to reduce the exit codes to 0 or 1 for healthchecks*
      test: \['CMD', 'drill-hc', '@127.0.0.1', 'dnssec.works'\]
      interval: 30s
      timeout: 30s
      retries: 3
      start_period: 30s
    volumes:
      /path/to/config:/etc/unbound/custom.conf.d
    restart: unless-stopped
networks:
  default:
    driver: bridge

At this point, the main issue seems to be that the Unbound container is stuck in a restart loop. It never stays up long enough to accept connections, which explains why queries to 127.0.0.1:5335 fail with “connection refused”.

From the logs, I only see the generic Unbound usage output, which suggests that it is not successfully loading the configuration file.

Earlier, I also ran into an issue where unbound.conf was accidentally created as a directory instead of a file. I have since removed that and recreated it as a proper file, so that specific issue should be resolved now.

Are you sure this is solved?

The compose file above is using /path/to/config as the path to the Unbound config file.

/path/to/config is just part of the example.

If you want to use your config file, you need to replace /path/to/config with the real path where you saved your file.

1 Like

Those **-** don't look like they really would be part of your compose file.
Could you please update your post and correct that?

You likely see that message as there is nothing listening on port 5335, which seems correct with your current configuration:
The above compose doesn't expose any ports for its unbound container - it is meant to be accessible only to containers on the same network, i.e. from the pihole container on the same default brigde network.

You'd still need to adjust unbound's volume as rdwebdesign mentioned, e.g. to read:

    volumes:
      - './unbound-conf:/etc/unbound/custom.conf.d'
1 Like

I adjusted my docker-compose.yml

Sorry for the screenshots above, but this forum won’t let met post this as text. Some error message because I’m new user and can’t name another username or something.

There's no point in sharing your AI conversation.
It just clutters the topic with obsolete contents.

The relevant information isn't with AI, but with you - what you observe on your system, how your configuration looks like, and any errors you encounter.

Try following rdwebdesign's recommendations for pasting text:

My suggestion was

But you entered:

    volumes:
      - './unbound/config:/etc/unbound/custom.conf.d'

Judging by how your ls -l output shows that directory as populated, it would seem that ./unbound/config was the folder you've already been using for mvance/unbound. The folder would be empty for klutchell.
In that case, you'd be starting klutchell/unbound with mvance's configuration, which quite probably isn't matching klutchell.

Please edit your docker compose file to replace your volumes lines as suggested, and start up your containers afresh with that file.

This was indeed the problem! For the first time this command gave me a response:

Try to paste the error message (or file contents) inside a code block, using one line containing ``` before and another one after the error message

This didn’t do the trick.

You’re right, I removed the post.

Now in my Pihole webinterface, how can I check unbound is my DNS?

This is what I have in DNS settings, but can’t be edited:

In the querry log I should see unbound as client right? But I don’t:

At the moment Pihole only blocks about 21%, that is not a lot (I have 5 lists) but maybe normal?

It's correctly showing unbound as Pi-hole's sole upstream, as custom DNS server.

Note that any option that you control via your Pi-hole container's environment variables will become read-only in Pi-hole's UI.
Accordingly, you'd need to edit your docker compose file if you'd want to change such an option's value.

No, unbound is your Pi-hole's upstream.
Your DNS resolution chain looks like this:
client :right_arrow: Pi-hole :right_arrow: unbound :right_arrow: public authoritative DNS servers.

That is a statistical figure, not an indicator of blocking quality.
It can be expected to fluctuate over time, even wildly, according to the domains your clients try to connect to.
If you had only a single client spending time on Pi-hole's forum, your blocking rate would be close to 0%.
And at 100% blocking rate, you wouldn't be able to connect to anything. :wink:

Should I? Or is eveything set correctly now?

the website I visit daily, which has normally a lot of ad’s, shows no adds now. So I’m happy!