docker.recipes

Pi-hole + Unbound DNS Stack

beginner

Network-wide ad blocking with Pi-hole and recursive DNS with Unbound.

Overview

Pi-hole is a network-level DNS sinkhole that blocks advertisements and trackers at the network infrastructure level, protecting all connected devices without requiring client-side software. Originally developed as a Raspberry Pi project, Pi-hole has evolved into a comprehensive DNS filtering solution that intercepts DNS queries and blocks requests to known advertising and malicious domains before they reach your devices. This approach provides superior performance compared to browser-based ad blockers since blocked content never gets downloaded. This stack combines Pi-hole with Unbound recursive DNS resolver to create a completely self-contained, privacy-focused DNS infrastructure. Unbound eliminates the need for upstream DNS providers like Google or Cloudflare by performing recursive DNS lookups directly from authoritative nameservers. The integration includes Pi-hole Exporter for metrics collection, Prometheus for time-series data storage, and Grafana for visualization, providing comprehensive monitoring of DNS query patterns, blocking statistics, and resolver performance. Home network administrators and privacy-conscious users will find this stack particularly valuable for reducing bandwidth usage, improving browsing speed, and maintaining complete control over DNS resolution. The combination delivers enterprise-grade DNS filtering and monitoring capabilities while ensuring that DNS queries never leave your network infrastructure, maximizing privacy and reducing dependency on external DNS services.

Key Features

  • Network-wide ad and tracker blocking without client-side configuration
  • Recursive DNS resolution through Unbound eliminating upstream DNS dependencies
  • Real-time DNS query logging and statistics through Pi-hole web interface
  • Custom blocklist management with regex filtering capabilities
  • Prometheus metrics collection for DNS query patterns and blocking statistics
  • Grafana dashboards for visualizing DNS performance and threat intelligence
  • Group-based device management for different filtering policies
  • DHCP server integration for automatic network device configuration

Common Use Cases

  • 1Home network ad blocking for all connected devices including smart TVs and IoT devices
  • 2Privacy-focused DNS infrastructure avoiding reliance on commercial DNS providers
  • 3Parental controls with custom blocklists and time-based filtering
  • 4Network security monitoring to identify malware communication attempts
  • 5Bandwidth optimization by blocking resource-heavy advertisements and tracking scripts
  • 6Small office DNS filtering with user group policies and access logging
  • 7Home lab DNS analytics and performance monitoring with historical data

Prerequisites

  • Minimum 1GB RAM recommended for full stack with monitoring components
  • Static IP address for the Docker host to serve as network DNS server
  • Port 53 (TCP/UDP) available for DNS queries and port 80 for Pi-hole admin interface
  • Network router configuration access to change DHCP DNS settings
  • Basic understanding of DNS concepts and network configuration
  • Unbound configuration file (unbound.conf) for recursive resolver settings

For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms

docker-compose.yml

docker-compose.yml
1services:
2 pihole:
3 image: pihole/pihole:latest
4 ports:
5 - "53:53/tcp"
6 - "53:53/udp"
7 - "80:80"
8 environment:
9 - TZ=UTC
10 - WEBPASSWORD=${PIHOLE_PASSWORD}
11 - PIHOLE_DNS_=unbound#5335
12 volumes:
13 - pihole_config:/etc/pihole
14 - pihole_dnsmasq:/etc/dnsmasq.d
15 depends_on:
16 - unbound
17 cap_add:
18 - NET_ADMIN
19 networks:
20 pihole_net:
21 ipv4_address: 172.20.0.2
22
23 unbound:
24 image: mvance/unbound:latest
25 volumes:
26 - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
27 networks:
28 pihole_net:
29 ipv4_address: 172.20.0.3
30
31 pihole-exporter:
32 image: ekofr/pihole-exporter:latest
33 environment:
34 - PIHOLE_HOSTNAME=pihole
35 - PIHOLE_PASSWORD=${PIHOLE_PASSWORD}
36 ports:
37 - "9617:9617"
38 depends_on:
39 - pihole
40 networks:
41 - pihole_net
42
43 prometheus:
44 image: prom/prometheus:latest
45 ports:
46 - "9090:9090"
47 volumes:
48 - ./prometheus.yml:/etc/prometheus/prometheus.yml
49 - prometheus_data:/prometheus
50 networks:
51 - pihole_net
52
53 grafana:
54 image: grafana/grafana:latest
55 ports:
56 - "3000:3000"
57 environment:
58 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
59 volumes:
60 - grafana_data:/var/lib/grafana
61 networks:
62 - pihole_net
63
64volumes:
65 pihole_config:
66 pihole_dnsmasq:
67 prometheus_data:
68 grafana_data:
69
70networks:
71 pihole_net:
72 ipam:
73 config:
74 - subnet: 172.20.0.0/24

.env Template

.env
1# Pi-hole + Unbound
2PIHOLE_PASSWORD=secure_pihole_password
3GRAFANA_PASSWORD=secure_grafana_password
4
5# Pi-hole Admin at http://localhost/admin
6# DNS at localhost:53

Usage Notes

  1. 1Pi-hole Admin at http://localhost/admin
  2. 2Set device DNS to Pi-hole IP
  3. 3Unbound provides recursive DNS
  4. 4No upstream DNS provider needed
  5. 5Privacy-focused DNS resolution

Individual Services(5 services)

Copy individual services to mix and match with your existing compose files.

pihole
pihole:
  image: pihole/pihole:latest
  ports:
    - 53:53/tcp
    - 53:53/udp
    - "80:80"
  environment:
    - TZ=UTC
    - WEBPASSWORD=${PIHOLE_PASSWORD}
    - PIHOLE_DNS_=unbound#5335
  volumes:
    - pihole_config:/etc/pihole
    - pihole_dnsmasq:/etc/dnsmasq.d
  depends_on:
    - unbound
  cap_add:
    - NET_ADMIN
  networks:
    pihole_net:
      ipv4_address: 172.20.0.2
unbound
unbound:
  image: mvance/unbound:latest
  volumes:
    - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
  networks:
    pihole_net:
      ipv4_address: 172.20.0.3
pihole-exporter
pihole-exporter:
  image: ekofr/pihole-exporter:latest
  environment:
    - PIHOLE_HOSTNAME=pihole
    - PIHOLE_PASSWORD=${PIHOLE_PASSWORD}
  ports:
    - "9617:9617"
  depends_on:
    - pihole
  networks:
    - pihole_net
prometheus
prometheus:
  image: prom/prometheus:latest
  ports:
    - "9090:9090"
  volumes:
    - ./prometheus.yml:/etc/prometheus/prometheus.yml
    - prometheus_data:/prometheus
  networks:
    - pihole_net
grafana
grafana:
  image: grafana/grafana:latest
  ports:
    - "3000:3000"
  environment:
    - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
  volumes:
    - grafana_data:/var/lib/grafana
  networks:
    - pihole_net

Quick Start

terminal
1# 1. Create the compose file
2cat > docker-compose.yml << 'EOF'
3services:
4 pihole:
5 image: pihole/pihole:latest
6 ports:
7 - "53:53/tcp"
8 - "53:53/udp"
9 - "80:80"
10 environment:
11 - TZ=UTC
12 - WEBPASSWORD=${PIHOLE_PASSWORD}
13 - PIHOLE_DNS_=unbound#5335
14 volumes:
15 - pihole_config:/etc/pihole
16 - pihole_dnsmasq:/etc/dnsmasq.d
17 depends_on:
18 - unbound
19 cap_add:
20 - NET_ADMIN
21 networks:
22 pihole_net:
23 ipv4_address: 172.20.0.2
24
25 unbound:
26 image: mvance/unbound:latest
27 volumes:
28 - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
29 networks:
30 pihole_net:
31 ipv4_address: 172.20.0.3
32
33 pihole-exporter:
34 image: ekofr/pihole-exporter:latest
35 environment:
36 - PIHOLE_HOSTNAME=pihole
37 - PIHOLE_PASSWORD=${PIHOLE_PASSWORD}
38 ports:
39 - "9617:9617"
40 depends_on:
41 - pihole
42 networks:
43 - pihole_net
44
45 prometheus:
46 image: prom/prometheus:latest
47 ports:
48 - "9090:9090"
49 volumes:
50 - ./prometheus.yml:/etc/prometheus/prometheus.yml
51 - prometheus_data:/prometheus
52 networks:
53 - pihole_net
54
55 grafana:
56 image: grafana/grafana:latest
57 ports:
58 - "3000:3000"
59 environment:
60 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
61 volumes:
62 - grafana_data:/var/lib/grafana
63 networks:
64 - pihole_net
65
66volumes:
67 pihole_config:
68 pihole_dnsmasq:
69 prometheus_data:
70 grafana_data:
71
72networks:
73 pihole_net:
74 ipam:
75 config:
76 - subnet: 172.20.0.0/24
77EOF
78
79# 2. Create the .env file
80cat > .env << 'EOF'
81# Pi-hole + Unbound
82PIHOLE_PASSWORD=secure_pihole_password
83GRAFANA_PASSWORD=secure_grafana_password
84
85# Pi-hole Admin at http://localhost/admin
86# DNS at localhost:53
87EOF
88
89# 3. Start the services
90docker compose up -d
91
92# 4. View logs
93docker compose logs -f

One-Liner

Run this command to download and set up the recipe in one step:

terminal
1curl -fsSL https://docker.recipes/api/recipes/pi-hole-unbound/run | bash

Troubleshooting

  • Pi-hole admin interface shows 'DNS resolution is currently failing': Check that Unbound container is running and accessible on port 5335
  • Devices not using Pi-hole for DNS queries: Verify router DHCP settings point to Pi-hole IP address or configure device DNS manually
  • Unbound failing to start with permission errors: Ensure unbound.conf file has correct read permissions and valid configuration syntax
  • High memory usage from Prometheus: Adjust retention policies in prometheus.yml or increase Docker host memory allocation
  • Grafana dashboards showing no data: Verify pihole-exporter is collecting metrics and Prometheus is scraping the exporter endpoint
  • DNS queries timing out intermittently: Check Docker network configuration and ensure containers have static IP addresses in pihole_net network

Community Notes

Loading...
Loading notes...

Download Recipe Kit

Get all files in a ready-to-deploy package

Includes docker-compose.yml, .env template, README, and license

Ad Space