Pi-hole + Unbound DNS Stack
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:latest4 ports: 5 - "53:53/tcp"6 - "53:53/udp"7 - "80:80"8 environment: 9 - TZ=UTC10 - WEBPASSWORD=${PIHOLE_PASSWORD}11 - PIHOLE_DNS_=unbound#533512 volumes: 13 - pihole_config:/etc/pihole14 - pihole_dnsmasq:/etc/dnsmasq.d15 depends_on: 16 - unbound17 cap_add: 18 - NET_ADMIN19 networks: 20 pihole_net: 21 ipv4_address: 172.20.0.22223 unbound: 24 image: mvance/unbound:latest25 volumes: 26 - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro27 networks: 28 pihole_net: 29 ipv4_address: 172.20.0.33031 pihole-exporter: 32 image: ekofr/pihole-exporter:latest33 environment: 34 - PIHOLE_HOSTNAME=pihole35 - PIHOLE_PASSWORD=${PIHOLE_PASSWORD}36 ports: 37 - "9617:9617"38 depends_on: 39 - pihole40 networks: 41 - pihole_net4243 prometheus: 44 image: prom/prometheus:latest45 ports: 46 - "9090:9090"47 volumes: 48 - ./prometheus.yml:/etc/prometheus/prometheus.yml49 - prometheus_data:/prometheus50 networks: 51 - pihole_net5253 grafana: 54 image: grafana/grafana:latest55 ports: 56 - "3000:3000"57 environment: 58 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}59 volumes: 60 - grafana_data:/var/lib/grafana61 networks: 62 - pihole_net6364volumes: 65 pihole_config: 66 pihole_dnsmasq: 67 prometheus_data: 68 grafana_data: 6970networks: 71 pihole_net: 72 ipam: 73 config: 74 - subnet: 172.20.0.0/24.env Template
.env
1# Pi-hole + Unbound2PIHOLE_PASSWORD=secure_pihole_password3GRAFANA_PASSWORD=secure_grafana_password45# Pi-hole Admin at http://localhost/admin6# DNS at localhost:53Usage Notes
- 1Pi-hole Admin at http://localhost/admin
- 2Set device DNS to Pi-hole IP
- 3Unbound provides recursive DNS
- 4No upstream DNS provider needed
- 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 file2cat > docker-compose.yml << 'EOF'3services:4 pihole:5 image: pihole/pihole:latest6 ports:7 - "53:53/tcp"8 - "53:53/udp"9 - "80:80"10 environment:11 - TZ=UTC12 - WEBPASSWORD=${PIHOLE_PASSWORD}13 - PIHOLE_DNS_=unbound#533514 volumes:15 - pihole_config:/etc/pihole16 - pihole_dnsmasq:/etc/dnsmasq.d17 depends_on:18 - unbound19 cap_add:20 - NET_ADMIN21 networks:22 pihole_net:23 ipv4_address: 172.20.0.22425 unbound:26 image: mvance/unbound:latest27 volumes:28 - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro29 networks:30 pihole_net:31 ipv4_address: 172.20.0.33233 pihole-exporter:34 image: ekofr/pihole-exporter:latest35 environment:36 - PIHOLE_HOSTNAME=pihole37 - PIHOLE_PASSWORD=${PIHOLE_PASSWORD}38 ports:39 - "9617:9617"40 depends_on:41 - pihole42 networks:43 - pihole_net4445 prometheus:46 image: prom/prometheus:latest47 ports:48 - "9090:9090"49 volumes:50 - ./prometheus.yml:/etc/prometheus/prometheus.yml51 - prometheus_data:/prometheus52 networks:53 - pihole_net5455 grafana:56 image: grafana/grafana:latest57 ports:58 - "3000:3000"59 environment:60 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}61 volumes:62 - grafana_data:/var/lib/grafana63 networks:64 - pihole_net6566volumes:67 pihole_config:68 pihole_dnsmasq:69 prometheus_data:70 grafana_data:7172networks:73 pihole_net:74 ipam:75 config:76 - subnet: 172.20.0.0/2477EOF7879# 2. Create the .env file80cat > .env << 'EOF'81# Pi-hole + Unbound82PIHOLE_PASSWORD=secure_pihole_password83GRAFANA_PASSWORD=secure_grafana_password8485# Pi-hole Admin at http://localhost/admin86# DNS at localhost:5387EOF8889# 3. Start the services90docker compose up -d9192# 4. View logs93docker compose logs -fOne-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 | bashTroubleshooting
- 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
Components
piholeunboundprometheusgrafana
Tags
#pihole#unbound#dns#adblock#privacy
Category
Home Lab & Self-HostingAd Space
Shortcuts: C CopyF FavoriteD Download