# How to Set Up Pi-hole v6 with Unbound on Raspberry Pi Using Docker

Pi-hole is a powerful network-wide ad blocker that runs on your local network. Pairing it with Unbound—a recursive DNS resolver—gives you complete control over DNS queries without relying on third-party DNS providers like Google or Cloudflare.

This guide shows you how to deploy Pi-hole v6 (which is very much different from v5) with Unbound on a Raspberry Pi using Docker Compose.

## Prerequisites

[Raspberry Pi](https://amzn.to/3N0hddS) (3, 4, or 5) running Ubuntu Server LTS with [Docker/Docker Compose](https://docs.docker.com/engine/install/ubuntu/) installed.

<AnnouncementBox variant="info">
  If you don't have a Raspberry Pi, check out my complete guide on [buying your first Raspberry
  Pi](/blog/buying-your-first-raspberry-pi-5/) to get started.
</AnnouncementBox>

## Step 1: Disable systemd-resolved

Pi-hole needs port 53, which is used by systemd-resolved by default:

```bash
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
```

## Step 2: Create Directory Structure

It's good practice to deploy applications in the `/srv` folder (on your Pi).

```bash
mkdir -p /srv/docker/pihole/unbound
cd /srv/docker/pihole
```

## Step 3: Create Unbound Configuration

Create `/srv/docker/pihole/unbound/unbound.conf`:

```text
server:
    # Port to listen on (non-standard to avoid conflict with Pi-hole)
    port: 5335

    # Listen on all interfaces (accessible within shared network namespace)
    interface: 0.0.0.0

    # Use IPv4 only
    do-ip4: yes
    do-ip6: no
    do-udp: yes
    do-tcp: yes

    # Trust glue only if it is within the servers authority
    harden-glue: yes

    # Require DNSSEC data for trust-anchored zones
    harden-dnssec-stripped: yes

    # Don't use Capitalization randomization
    use-caps-for-id: no

    # Reduce EDNS reassembly buffer size
    edns-buffer-size: 1232

    # Perform prefetching of close to expired message cache entries
    prefetch: yes

    # Number of threads to create
    num-threads: 1

    # Power of 2 close to num-threads
    so-rcvbuf: 1m

    # Ensure privacy of local IP ranges
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10
```

These values ☝️, if you're wondering where they came from, are straight from [the docs](https://docs.pi-hole.net/guides/dns/unbound/).

## Step 4: Create Docker Compose File

Create `/srv/docker/pihole/docker-compose.yml`:

```yaml
services:
  pihole:
    image: pihole/pihole:2025.11.1
    container_name: pihole
    restart: unless-stopped

    # Use host networking for DNS (port 53)
    network_mode: host

    environment:
      - TZ=America/New_York
      - FTLCONF_webserver_api_password=changeme123
      # - FTLCONF_webserver_domain=pihole.yourdomain.com -- If you plan to have a custom domain for the web interface
      - FTLCONF_LOCAL_IPV4=192.168.1.92 # change me
      - FTLCONF_dns_upstreams=127.0.0.1#5335;127.0.0.1#5335

    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d

    cap_add:
      - NET_ADMIN

  unbound:
    image: alpinelinux/unbound:latest
    container_name: unbound
    restart: unless-stopped

    # Share Pi-hole's network stack
    network_mode: service:pihole

    volumes:
      - ./unbound/unbound.conf:/etc/unbound/unbound.conf:ro
```

**Important**: Update these values:

- `TZ`: Your timezone ([list here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones))
- `FTLCONF_webserver_api_password`: Your desired admin password
- `FTLCONF_LOCAL_IPV4`: Your Raspberry Pi's static IP address

## Step 5: Deploy the Stack

```bash
cd /srv/docker/pihole
sudo docker compose up -d
```

## Step 6: Verify Unbound Integration

Check that Pi-hole is forwarding queries to Unbound:

```bash
# Query a domain
dig google.com @127.0.0.1

# Watch live query log
sudo docker exec pihole pihole -t
```

You should see queries being forwarded to `127.0.0.1#5335` (Unbound) in the logs.

## Step 7: Configure Your Network

Point your router's DNS settings to your Pi's IP address (e.g., `192.168.1.92`), or configure individual devices to use it as their DNS server.

## Key Configuration Notes

### Pi-hole v6 Environment Variables

Pi-hole v6 uses `FTLCONF_*` environment variables instead of the old v5 variables:

- ❌ `PIHOLE_DNS_` (deprecated)
- ✅ `FTLCONF_dns_upstreams` (use this)

### Network Mode

We use `network_mode: host` for Pi-hole so it can bind to port 53, and `network_mode: service:pihole` for Unbound so it shares Pi-hole's network namespace. This allows them to communicate via `127.0.0.1`.

### Persistent Data

Your Pi-hole configuration is stored in `./etc-pihole`, which persists across container restarts and updates.

## Troubleshooting

### DNS Not Working

Verify systemd-resolved is disabled:

```bash
sudo systemctl status systemd-resolved
```

### Unbound Not Connecting

Check if Unbound is running:

```bash
sudo docker logs unbound
```

### Web Interface Not Accessible

Access Pi-hole's web interface at `http://YOUR_PI_IP/admin` (e.g., `http://192.168.1.92/admin`)

## Updating

To update Pi-hole and Unbound:

```bash
cd /srv/docker/pihole
sudo docker compose pull
sudo docker compose up -d
```

Your configuration and data will persist across updates.

## Conclusion

You now have a privacy-focused, ad-blocking DNS server running on your Raspberry Pi. All DNS queries are resolved recursively via Unbound without relying on third-party DNS providers, giving you complete control and privacy over your network's DNS traffic.