Network-wide ad blocking accessible via Tailscale VPN using Docker Compose.
- Ad Blocking: Network-wide ad and tracker blocking via Pi-hole
- Tailscale Integration: Secure access from any device on your Tailscale network
- Optimized Performance: 25,000 entry DNS cache with optimistic caching
- Privacy Focused: Quad9 upstream DNS with query logging
- Auto-restart: Containers restart automatically on failure
- Health Checks: Built-in monitoring for both services
- Docker and Docker Compose installed (or Coolify for managed deployments)
- Tailscale account (sign up free)
- Server/VPS with at least 512MB RAM
- Git (for cloning repository)
- Python 3.8+ (optional, for pre-commit hooks)
This guide covers two deployment methods:
- Coolify Deployment - Recommended for managed, easy deployments
- Manual Docker Compose - Direct Docker Compose deployment
Coolify is an open-source, self-hosted platform that simplifies application deployment and management. It's the recommended method for deploying this Pi-hole + Tailscale setup.
- Built-in environment variable management
- Automatic container health monitoring
- Easy updates and rollbacks
- Web-based management interface
- Persistent volume management
- No manual Docker commands needed
- Coolify installed and running (installation guide)
- Git repository with this configuration
- Tailscale auth key ready
- Log into your Coolify dashboard
- Click + Add Resource
- Select Docker Compose
- Choose Public Repository or connect your Git provider
- Repository URL: Your Git repository URL (or use public repo)
- Branch:
main(or your default branch) - Docker Compose Location: Leave as
docker-compose.yml - Base Directory: Leave empty (unless compose file is in subdirectory)
Coolify will automatically detect environment variables from your compose file. You need to set these in the Coolify UI:
Required Variables:
TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PIHOLE_PASSWORD=your_strong_password_here
TZ=America/New_York
Optional Variables (use defaults or customize):
DNS_UPSTREAMS=9.9.9.9;149.112.112.112
CACHE_SIZE=25000
MAX_CACHE_TIME=3600
CACHE_OPTIMISTIC=true
RATE_LIMIT=1000/60
MAX_CONCURRENT=150
QUERY_LOGGING=true
DB_RETENTION_DAYS=365
DB_INTERVAL=1.0
ENABLE_IPV6=true
VIRTUAL_HOST=pihole.local
To add these:
- In your Coolify resource, go to Environment Variables tab
- Click + Add
- Add each variable name and value
- For sensitive values (like
PIHOLE_PASSWORD), toggle the "secret" option
Port Configuration:
- DO NOT add port mappings in Coolify's port configuration
- Ports are defined in
docker-compose.ymland Coolify will handle them automatically - Adding duplicate ports will cause deployment failures
Volume Persistence:
- Coolify automatically creates and manages volumes defined in compose file
- Volumes persist across container updates and restarts
- Location:
/var/lib/docker/volumes/
Network Mode Compatibility:
- The
network_mode: service:tailscaleconfiguration is supported - Coolify will respect the network namespace sharing
- Both containers will start in correct order due to
depends_on
Capabilities:
cap_add: NET_ADMINandSYS_MODULEare supported in Coolify- These are necessary for Tailscale VPN functionality
- No additional Coolify configuration needed
Before deploying, generate your Tailscale auth key:
- Go to https://login.tailscale.com/admin/settings/keys
- Click Generate auth key
- Configure:
- Reusable: [x] Yes (allows container recreations)
- Ephemeral: [ ] No (keeps node in admin console)
- Tags:
tag:dns(optional, for ACL management)
- Copy the key and add to Coolify environment variables as
TS_AUTHKEY
- Click Deploy button in Coolify
- Monitor the deployment logs in real-time
- Wait for both containers to start (typically 30-60 seconds)
- Check deployment status shows "Running"
After successful deployment, retrieve the Tailscale IP:
Option A: Using Coolify Terminal
- Go to your resource in Coolify
- Click on tailscale-pihole container
- Open Terminal tab
- Run:
tailscale ip -4 - Note the IP (e.g.,
100.64.1.5)
Option B: Using Tailscale Admin Console
- Go to https://login.tailscale.com/admin/machines
- Find machine named "pihole"
- Note the Tailscale IP address
- Go to https://login.tailscale.com/admin/dns
- Enable MagicDNS
- Under Nameservers:
- Click Add nameserver
- Select Custom
- Enter your Pi-hole's Tailscale IP (e.g.,
100.64.1.5)
- Enable Override local DNS
- Click Save
- Open Pi-hole web interface:
http://<tailscale-ip>/admin - Login with your
PIHOLE_PASSWORD - Go to Settings -> DNS
- Under Interface settings:
- Select Permit all origins
- Click Save
- Go to https://login.tailscale.com/admin/machines
- Find your "pihole" machine
- Click ... (three dots) -> Disable key expiry
Check Container Health:
- In Coolify, go to your resource
- Both containers should show "Running" status
- Click on pihole container -> Logs to verify no errors
Test DNS Resolution:
# From any device on your Tailscale network
dig @<pihole-tailscale-ip> google.com
# Test ad blocking
dig @<pihole-tailscale-ip> doubleclick.net
# Should return 0.0.0.0Update Containers:
- Go to your resource in Coolify
- Click Redeploy button
- Coolify will pull latest images and restart containers
- Data persists in volumes automatically
View Logs:
- Click on container name (pihole or tailscale)
- Select Logs tab
- Use search and filter options
Restart Services:
- Click on container name
- Click Restart button
- Or restart entire stack with Redeploy
Deployment Fails with Port Conflict:
- Ensure you haven't added port mappings in Coolify UI
- Ports should only be in docker-compose.yml
- Check if port 53 or 80 is already in use on host
Containers Won't Start:
- Check deployment logs in Coolify
- Verify all required environment variables are set
- Ensure Tailscale auth key is valid and not expired
- Check container logs for specific error messages
Can't Access Pi-hole Interface:
- Verify both containers are running in Coolify
- Get Tailscale IP from container terminal
- Ensure you're connected to Tailscale on client device
- Try accessing via
http://<ip>/admin(not https)
DNS Not Working:
- Check Pi-hole interface setting is "Permit all origins"
- Verify Tailscale DNS is configured in admin console
- Ensure "Override local DNS" is enabled
- Restart Tailscale on client device
For direct Docker Compose deployment without Coolify, follow these steps:
cd /path/to/your/projects
git clone <your-repo> blackhole
cd blackhole# Copy example environment file
cp .env.example .env
# Edit with your values
nano .envRequired values in .env:
TS_AUTHKEY: Get from https://login.tailscale.com/admin/settings/keysPIHOLE_PASSWORD: Your Pi-hole admin passwordTZ: Your timezone (e.g.,America/New_York)
- Go to https://login.tailscale.com/admin/settings/keys
- Click Generate auth key
- Configure:
- Reusable: [x] Yes
- Ephemeral: [ ] No
- Tags:
tag:dns
- Copy the key and paste into
.envasTS_AUTHKEY
# Create required directories
mkdir -p etc-pihole etc-dnsmasq.d tailscale-state
# Fix permissions
sudo chown -R 999:999 etc-pihole etc-dnsmasq.d
# Start containers
docker-compose up -d
# Check status
docker-compose ps
docker-compose logs -f# Get the Tailscale IP address
docker exec tailscale-pihole tailscale ip -4
# Example output: 100.64.1.5Option A: Tailscale Admin Console (Recommended)
- Go to https://login.tailscale.com/admin/dns
- Enable MagicDNS
- Under Nameservers:
- Click Add nameserver
- Select Custom
- Enter your Pi-hole's Tailscale IP (e.g.,
100.64.1.5)
- Enable Override local DNS
- Click Save
Option B: Using Tailscale CLI
# On any device in your tailnet
sudo tailscale up --accept-dns=true- Get Pi-hole IP:
docker exec tailscale-pihole tailscale ip -4 - Open Pi-hole web interface:
http://<tailscale-ip>/admin - Login with your
PIHOLE_PASSWORD - Go to Settings → DNS
- Under Interface settings:
- Select Permit all origins
- Click Save
- Go to https://login.tailscale.com/admin/machines
- Find your Pi-hole machine
- Click ... (three dots) -> Disable key expiry
# From any device on your Tailscale network
dig @<pihole-tailscale-ip> google.com
nslookup google.com <pihole-tailscale-ip>
# Test ad blocking
dig @<pihole-tailscale-ip> doubleclick.net
# Should return 0.0.0.0# View logs
docker-compose logs -f pihole
docker-compose logs -f tailscale
# Check health status
docker-compose ps
# Monitor queries in real-time
docker exec pihole pihole tail# Check Tailscale status
docker exec tailscale-pihole tailscale status
# See all peers
docker exec tailscale-pihole tailscale status --peers- URL:
http://<tailscale-ip>/admin - Password: Value from
PIHOLE_PASSWORDin.env
# View Pi-hole status
docker exec pihole pihole status
# View recent queries
docker exec pihole pihole -q
# Update gravity (blocklists)
docker exec pihole pihole -g
# Restart DNS service
docker exec pihole pihole restartdns
# View Pi-hole version
docker exec pihole pihole -v# Pull latest images
docker-compose pull
# Recreate containers
docker-compose up -d
# Clean up old images
docker image prune -fBlocklists update automatically every week. To manually update:
docker exec pihole pihole -g# Backup Pi-hole settings
docker exec pihole pihole -a -t
# Or backup the entire directory
tar -czf pihole-backup-$(date +%Y%m%d).tar.gz etc-pihole etc-dnsmasq.d tailscale-state# All logs
docker-compose logs
# Follow logs
docker-compose logs -f
# Specific service
docker-compose logs pihole
docker-compose logs tailscale- Check interface setting is "Permit all origins"
- Verify Tailscale DNS is configured: https://login.tailscale.com/admin/dns
- Check "Override local DNS" is enabled
- Test direct query:
dig @<pihole-ip> google.com
# Get current Tailscale IP
docker exec tailscale-pihole tailscale ip -4
# Check if Pi-hole container is running
docker-compose ps
# Check logs for errors
docker-compose logs pihole# Check logs for errors
docker-compose logs pihole
# Common issue: Port 53 in use
sudo lsof -i :53
# If found, stop conflicting service
# Check permissions
sudo chown -R 999:999 etc-pihole etc-dnsmasq.d# Check Tailscale logs
docker-compose logs tailscale
# Verify auth key is valid
# Generate new key if expired: https://login.tailscale.com/admin/settings/keys
# Restart Tailscale container
docker-compose restart tailscale- Check Tailscale DNS settings: https://login.tailscale.com/admin/dns
- Ensure "Override local DNS" is enabled
- On device, restart Tailscale:
# Linux/Mac sudo tailscale down && sudo tailscale up # Windows: Restart Tailscale service
# Check upstream DNS is configured
docker exec pihole cat /etc/pihole/setupVars.conf | grep DNS
# Test upstream DNS
docker exec pihole dig @9.9.9.9 google.com
# If fails, update DNS_UPSTREAMS in .env and restart
docker-compose restart piholeCreate custom dnsmasq config files in etc-dnsmasq.d/:
# Example: Local DNS records
cat > etc-dnsmasq.d/04-custom.conf << EOF
# Local domain resolution
address=/mydevice.local/192.168.1.100
# Custom DNS for specific domain
server=/example.com/1.1.1.1
EOF
# Restart to apply
docker-compose restart pihole- Access Pi-hole web interface
- Go to Group Management → Adlists
- Add blocklist URL
- Update gravity:
docker exec pihole pihole -g
Recommended blocklists:
- StevenBlack's Unified: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
- EasyList: https://v.firebog.net/hosts/Easylist.txt
If you want to restrict access further:
# Allow DNS only from Tailscale
sudo ufw allow in on tailscale0 to any port 53
sudo ufw allow in on tailscale0 to any port 80
# Deny from other interfaces (if not needed)
sudo ufw deny 53/tcp
sudo ufw deny 53/udpEdit docker-compose.yml under deploy.resources section:
deploy:
resources:
limits:
cpus: '2' # Adjust as needed
memory: 512M # Adjust as needed
reservations:
cpus: '0.5'
memory: 256M# Stop and remove containers
docker-compose down
# Remove all data (optional)
rm -rf etc-pihole etc-dnsmasq.d tailscale-state
# Remove from Tailscale admin console
# https://login.tailscale.com/admin/machinesThis infrastructure implements multiple security layers to protect your DNS and network.
- Container Hardening: Minimal capabilities (NET_ADMIN only), no privilege escalation
- Resource Limits: CPU, memory, PID, and swap limits prevent resource exhaustion
- Network Isolation: Access restricted to Tailscale VPN only
- Rate Limiting: DNS queries limited to 100/minute to prevent amplification attacks
- DNSSEC Enabled: Protects against DNS spoofing and cache poisoning
- Ephemeral Keys: Tailscale auth keys automatically expire for better security
- Privacy Protection: Query logs retained for only 7 days (configurable)
-
NEVER expose ports 53 or 80 to the public internet
- Only access via Tailscale VPN
- Verify firewall rules block public access
-
Use strong passwords (minimum 20 characters)
# Generate secure password openssl rand -base64 32 -
Use ephemeral Tailscale keys
- Set "Ephemeral: Yes" when generating auth keys
- Automatically handled by docker-compose.yml
-
Disable key expiry after deployment
- Prevents service interruption
- Done via Tailscale admin console
-
Regular updates
docker-compose pull docker-compose up -d
-
Monitor for suspicious activity
docker-compose logs -f pihole docker exec pihole pihole -t
This repository uses pre-commit hooks to ensure code quality and prevent common issues.
Setup (one-time):
# Install pre-commit
pip install pre-commit
# Install git hooks
pre-commit install
# (Optional) Run on all files
pre-commit run --all-filesWhat gets checked:
- YAML syntax and formatting
- Docker Compose file validation
- Markdown linting
- Secret detection (prevents committing sensitive data)
- Trailing whitespace and line endings
- .env file synchronization with .env.example
Daily usage:
Pre-commit runs automatically when you git commit. If checks fail, fix the issues and commit again.
Bypass hooks (emergency only):
git commit --no-verify -m "Emergency fix"- Pi-hole Documentation: https://docs.pi-hole.net/
- Tailscale Documentation: https://tailscale.com/kb/
- Docker Compose Reference: https://docs.docker.com/compose/
- Coolify Documentation: https://coolify.io/docs
This configuration is provided as-is for personal use.