blog

Aug 27, 2024 projects

Block ads (almost) everywhere: running Pi-Hole in Docker using Tailscale

Note: this article is currently in progress — I figured I’d publish it now, but I might update it for clarification soon.

Recently, I discovered Tailscale, and I’ve been obsessed since. It’s basically WireGuard without the hassle: install it and your devices are magically able to connect to each other without being exposed to the Internet. And because of its MagicDNS feature, you can do stuff like ssh pi@raspberrypi to connect to your Raspberry Pi, or you can run a web server and load your website just by typing http://my-server. Isn’t that cool?! It feels like, well, magic! (To me, at least!)

Since then, I’ve tried running a couple things on my old MacBook and my Raspberry Pis — stuff like Immich (because sometime soon I’ll run out of iCloud storage), a Minecraft server, sharing hard drives which are plugged into that old MacBook, and running a WebDAV server that helps sync my Notability notes to my Obsidian vault.

But Tailscale also has another awesome feature: setting custom DNS nameservers. And you can set it to override local DNS servers!

If you don’t already know what DNS — the Domain Name System — is: basically, all computers on a network are given an IP address, which is a bunch of numbers that looks like 192.168.0.1. And the Internet is, of course, made up of a bunch of computers all connected to each other — so each one gets an IP address, like an ID number.

When you go to a website, what you’re really doing is asking your computer to go talk to another computer (i.e., the server) and request that it send back the web page. So when you type in the name of a website, like wikipedia.org, your computer asks the Wikipedia computer for its home page. However, you’ll notice: “wikipedia.org” is not a bunch of numbers, which is how computers identify each other. So how do you go from the domain name — wikipedia.org — to the IP address (which right now is 208.80.154.224)?

The answer is DNS (surprise). It’s basically a phonebook, or a spreadsheet that pairs up domain names and IP addresses. There’s a lot more to it, but this is all we need to know for now.

Back to the main story:

Tailscale’s DNS functionality natively supports NextDNS, which is like a hosted version of Pi-hole — and if you don’t want to deal with self-hosting Pi-hole, want more absolute reliability, and don’t mind paying $20/year, I’d totally recommend it. I tried out their free tier (which limits your monthly DNS queries and stops blocking ads after a certain point), and it worked great.

Unfortunately, as a college kid, I didn’t really want to pay that $20/year, but I also got annoyed seeing ads everywhere. So here we are!

The cool thing about a DNS-level ad-blocker is that it blocks ads everywhere (almost). You can install a broswer extension like Ghostery, but then you’ll still get ads in apps like The New York Times. With Pi-hole, they won’t show up even there. (Sadly, it still can’t block YouTube or Instagram ads, though, since those are served from the same place as the videos/photos themselves, so there’s no way to distinguish them.)

Ok, so here’s how to do it:

  1. Get a Raspberry Pi (or just use an old computer). Pi-hole is very lightweight, so you don’t need to worry much about the hardware.
  2. Install Docker. If you’re using a computer only for Pi-hole (like you have a Raspberry Pi Zero or something like that), you could also install it directly on the machine, and then install Tailscale directly too. But I like using Docker, since it keeps things neat and makes it easy to run many things on one computer without messing stuff up. If you’re running Linux on your Pi-hole computer, I’m pretty sure I just installed Docker Engine, not Docker Desktop: follow this guide.
  3. Log into Tailscale and create a tag. Click on Access controls in the menu (it has the lock icon). You can (as of publishing) basically just add this to your Tailscale access control file. Find the part where tagOwners is commented out and add this in:
"tagOwners": {
        "tag:container": ["autogroup:admin"],
    },
  1. Create an OAuth client in Tailscale. Go to Settings (top menu, with the gear) → OAuth Clients (left sidebar menu). Click Generate OAuth client…, type in a description (“pihole in docker” or something), select Devices: Write (which will also select Devices: Read), and click Generate client. You don’t need the client ID, but you do need the client secret, which you should copy down: it won’t be displayed ever again.

  2. Back on the Pi-hole computer, make a directory. mkdir pihole && cd pihole

  3. Add your docker-compose.yml file. This is what mine looks like. Make sure to edit it and replace the TS_AUTHKEY variable with your actual auth key. Also, change the WEBPASSWORD to an actual password (which you’ll use to log into the Pi-hole web interface).

# Guide followed:
# https://tailscale.com/blog/docker-tailscale-guide
# (I used an OAuth token)
services:
  ts-pihole:
    image: tailscale/tailscale:latest
    container_name: ts-pihole
    hostname: pihole
    environment:
      - TS_AUTHKEY=tskey-client-blahblah-blahblahblah?ephemeral=false # replace this, of course
      - TS_EXTRA_ARGS=--advertise-tags=tag:container
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - ./ts-pihole/state:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - sys_module
    restart: unless-stopped
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    environment:
      TZ: 'America/Chicago'
      WEBPASSWORD: 'add-a-password-here'
    # Volumes store your data between container upgrades
    volumes:
      - './etc-pihole:/etc/pihole'
      - './etc-dnsmasq.d:/etc/dnsmasq.d'
    #   https://github.com/pihole/docker-pi-hole#note-on-capabilities
    cap_add:
      - NET_ADMIN # Required if you are using Pi-hole as your DHCP server, else not needed
    restart: unless-stopped
    network_mode: service:ts-pihole
    depends_on:
        - ts-pihole

volumes:
  ts-alpine:
    driver: local
  1. Start it up! Run docker compose up -d.
  2. Log into the web interface. Try visiting http://pihole. If all is well, you should see a login screen. Log in using the password you set in the docker-compose.yml file.
  3. Change your DNS nameserver in Tailscale’s settings. In the admin console, under Machines, first find your new pihole device. If all went well, it should be there. Copy its IP address. Then go to DNS → Nameservers and then click Add nameserverCustom. Paste in that IP address. Finally, switch on Override local DNS.

It’s important that your Pi-hole be the only DNS server, because if you add others, DNS queries blocked by Pi-hole will just get answered by the other nameservers. This does create a single point of failure — if your Pi-hole goes down, you won’t be able to load anything on your devices — but if you do run into this situation, you can easily bypass your Pi-hole simply by disconnecting from Tailscale on your devices. (Or you can log in to the Tailscale admin console and switch Override local DNS back off until you’ve fixed the problem.)

And… that should be everything! Install and set up Tailscale on your other devices if you haven’t already, and you should be good to go.