Start here
DNS in one picture
DNS is your network's phone book. Because every connection starts with a lookup, the DNS server is the one place that can see โ and decide โ what your devices talk to.
- Block ads and trackers for every device, even smart TVs.
- Encrypt lookups so your ISP can't read them.
- Spot the gadget that phones home at 3 a.m.
Step 1
Pick where it runs
Every path ends the same way: your router hands out TelltaleDNS as the DNS server, and every device uses it, with its own address visible. Pre-1.0: installs follow builds from main (edge).
Raspberry Pi with Docker Compose ยท the recommended home setup, about 10 minutes
- Prepare the Pi. Raspberry Pi OS (64-bit recommended) on a Pi 3, 4, 5, or Zero 2 W, with a fixed address (set a DHCP reservation in your router), and Docker:
curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # log out and back in - Free port 53. If Pi-hole or another DNS server runs on the Pi, stop it. Check:
sudo ss -ulpn 'sport = :53'should print nothing. - Download the bundle and start it.
mkdir ~/telltale && cd ~/telltale curl -fsSLO https://raw.githubusercontent.com/paimonsoror/telltaledns/main/deploy/compose/compose.yaml curl -fsSLO https://raw.githubusercontent.com/paimonsoror/telltaledns/main/deploy/compose/telltale.toml docker compose up -d docker compose ps # STATUS shows "healthy" within a minute - Test it from another computer (replace the address with your Pi's):
On Windows:dig @192.168.1.2 example.com # an address: it resolves dig @192.168.1.2 doubleclick.net # 0.0.0.0 or NXDOMAIN: the blocklist worksnslookup example.com 192.168.1.2. - Create the admin account. Print the one-time setup token and open the UI at
http://<pi>:8053/:docker compose exec telltale telltale auth setup-token
Updating: docker compose pull && docker compose up -d. Editing the config: change telltale.toml, then docker compose kill -s HUP applies it without dropping a query. On an SD card, the starter config keeps the query log for 7 days; a USB SSD is better for longer history.
Raspberry Pi or Linux, native (systemd)
No Docker: a single static binary as a sandboxed systemd service, on any systemd distribution (x86-64, arm64, or 32-bit ARM). The installer checks the release's signature and checksum before installing anything, so read it, then run it:
curl -fsSLO https://raw.githubusercontent.com/paimonsoror/telltaledns/main/deploy/systemd/install.sh
less install.sh
sudo sh install.sh # the latest release; --edge: builds from main
sudo -u telltale telltale auth setup-token -c /etc/telltale/telltale.toml
- It asks first: press Enter for the recommended settings, or walk me through the options to pick upstreams (encrypted presets or your own server), starting blocklists (or none), the listen address, the first admin (a setup token, or a password now), and the query log's retention and privacy. Every step shows its default; Enter keeps it. Piped or automated runs (and
--yes) ask nothing. - It creates a
telltaleuser, writes the config to/etc/telltale/telltale.toml(never overwriting yours), and keeps data in/var/lib/telltale. - If systemd-resolved holds port 53 (Ubuntu, some Debian setups), it offers to turn the stub listener off (
--disable-resolved-stubdoes it without asking). - Updates:
sudo telltale self-update --restart(add--channel edgeif you installed with--edge). The previous binary is kept astelltale.old. - Logs:
journalctl -u telltale -f. Reload after editing the config:sudo systemctl reload telltale. - Uninstall:
sudo sh install.sh --uninstall. It removes the service and binary, offers to give the machine its own resolver back (systemd-resolved, as it was before), and keeps your config and data unless you ask it to delete them (--purge).
Docker
docker run -d --name telltale --read-only --cap-drop ALL \
-p 53:53/udp -p 53:53/tcp -p 8053:8053 -p 9153:9153 \
-v "$PWD/telltale.toml:/etc/telltale/telltale.toml:ro" \
-v telltale-data:/var/lib/telltale \
ghcr.io/paimonsoror/telltale:latest
docker exec telltale telltale auth setup-token
amd64, arm64, and arm/v7; about 13 MiB compressed, web UI included; runs as a non-root user. Published ports can hide each device's own address behind Docker's (the UI warns if they do): on a home network, --network host (or the Pi Compose bundle) makes sure each device shows up as itself. A starter config is in step 2.
Kubernetes with Helm
helm install telltale oci://ghcr.io/paimonsoror/charts/telltale \
-n telltale --create-namespace \
--set service.dns.annotations."metallb\.universe\.tf/loadBalancerIPs"=192.168.1.53
kubectl -n telltale get svc telltale-dns # EXTERNAL-IP: your DNS server address
kubectl -n telltale exec telltale-0 -- /usr/local/bin/telltale auth setup-token \
-c /etc/telltale/00-chart.toml -c /etc/telltale/10-values.toml
- Every option:
helm show values oci://ghcr.io/paimonsoror/charts/telltale > values.yamlprints the chart's commented values file; the Helm values reference lists each key with its default and meaning. Keep what you change in your own file and pass it with-f. - Chart versions: without
--version, Helm installs the newest stable release (chart and image versions match). Builds from main publishX.Y.Z-edge.Ncharts with the:edgeimage: add--develfor the newest, or pin one with--version. - Keep
externalTrafficPolicy: Local(the default): withCluster, every query looks like it came from a node, and TelltaleDNS shows a "client IPs appear masked" banner. - Your settings go in
config:(TOML, the same keys as the configuration reference); secrets come from existing Secrets. - Encrypted DNS for phones:
encrypted.dot.enabled/encrypted.doh.enabledwith a cert-manager certificate. - Monitoring:
serviceMonitor.enabled,prometheusRule.enabled,grafanaDashboard.enabled. - More pods:
mode: scaledadds resolver pods beside the controller (below).
From source
git clone https://github.com/paimonsoror/telltaledns
cd telltaledns
cargo build --release
sudo ./target/release/telltale run -c telltale.toml # a config from step 2
dig @127.0.0.1 example.com
Needs a recent Rust toolchain (the repository pins it). The first start creates a one-time setup token; telltale auth setup-token prints it. Open the web UI on port 8053 and use it to create your admin.
Check what you run. Release binaries come with a signed SHA256SUMS (the installer and telltale self-update check it for you), and images are signed with cosign through GitHub's keyless signing:
cosign verify ghcr.io/paimonsoror/telltale:<version> \
--certificate-identity-regexp '^https://github\.com/paimonsoror/telltaledns/\.github/workflows/image\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Each binary carries its dependency list (cargo audit bin telltale reads it), every release has a CycloneDX SBOM per binary, and two builds of the same commit are byte for byte identical. How to rebuild and compare.
Step 2
Configure (optional)
The bundles and the installer come with a working configuration. This is a minimal one with encrypted upstreams, saved as telltale.toml:
[[upstream]]
name = "cloudflare"
url = "tls://1.1.1.1"
tls_server_name = "cloudflare-dns.com"
[[upstream]]
name = "quad9"
url = "https://dns.quad9.net/dns-query"
bootstrap = ["9.9.9.9"]
[[upstream_group]]
name = "default"
members = ["cloudflare", "quad9"]
strategy = "fastest"
Upstreams, blocklists, groups, schedules, and alerts can be added or changed later on their pages in the web UI, with a preview before anything is applied. Your config file is never rewritten. Every key is in the configuration reference.
Prefer a config file only, or Git? Choose how you manage settings.
Step 3
Point your network at it
- In your router's DHCP (LAN) settings, set the DNS server to TelltaleDNS's address (the Pi, or the LoadBalancer IP). If there's a second field, use a second TelltaleDNS, or leave it empty: a public resolver there lets devices skip your blocking.
- Renew leases: reconnect Wi-Fi, or wait for leases to expire (often a day).
- Open the dashboard: devices appear under Clients with their own addresses. Give them names and groups there.
Don't point the router's own upstream DNS at TelltaleDNS while devices use the router: every query would then come from the router, and TelltaleDNS would say client IPs appear masked.
Grow
A Pi and Kubernetes, one cluster
telltale cluster init --name home --advertise https://192.168.3.2:8443 on the first node, telltale cluster token create there, then telltale cluster join tt_join_โฆ on the other. Only the first node's port 8443 must be reachable; the others connect out. The first node's configuration, blocklists, users, and tokens then reach every node within seconds (signed, and only what changed), and each node keeps answering from its last copy if the link drops.
- One management plane. Any node's UI shows the whole cluster's dashboard, query log, and live view, and changes made there go to the primary. Sign in to any node with the same account.
- Failover. Promote another node by hand (
telltale cluster promote), or let the cluster elect one (telltale cluster set-failover auto) once it has a third voter, such as a tiny witness (telltale cluster witness). - Node-local settings. Each node keeps its own listeners, retention, and overrides; everything else is shared.
- More pods in Kubernetes.
mode: scaledadds resolver pods beside the controller (resolvers.replicas). They join on their own, share the DNS Service, ship their query log and dashboard counts to the controller, and keep answering for a few seconds while the Service drops them, so rollouts don't lose lookups. For one address for DNS, the UI, and the cluster port, setservice.dns.alsoServe. - See it. The Cluster page draws the cluster live: sites, the primary, pods grouped by Kubernetes node, link round trips, and each node's share of the queries.
- Git as the source. Optionally, the shared configuration can come from a Git repository or a GitOps tool (
config_source = "gitops"). - Maintenance. Rolling upgrades work across one version step (N and Nโ1), and
telltale cluster rotate-careplaces the cluster's certificate authority without downtime.
Coming from Pi-hole or Technitium?
Bring your setup with you
- In Pi-hole, open Settings โ Teleporter and click Export (v6 gives a
.zip, v5 a.tar.gz). - Run
telltale import pihole pi-hole_teleporter.zip -o pihole.toml. It carries over your upstreams, conditional forwarding, local DNS and CNAME records, adlists, allowed and denied domains (exact and regex), groups, clients, and DHCP reservations as named devices. - Read the notes at the top of
pihole.toml: they list everything that has no equivalent (for example clients matched by host name, or the DHCP server). Then add the file to your configuration.
Query history and passwords aren't imported. Keep Pi-hole running until TelltaleDNS answers the way you expect, then point your router at TelltaleDNS.
From Technitium: create an API token in Technitium (Administration โ Sessions), then run TECHNITIUM_TOKEN=โฆ telltale import technitium http://your-server:5380 -o technitium.toml. Forwarders, zones and records, forwarder zones, block lists, Advanced Blocking groups, and DHCP reservations come across. The notes at the top of the file list the rest.
If something's off
Troubleshooting
- "address already in use" on port 53
- Another DNS server runs there: Pi-hole, dnsmasq, or systemd-resolved's stub (
sudo ss -ulpn 'sport = :53'names it). Stop it, or for systemd-resolved addDNSStubListener=nounder[Resolve]in/etc/systemd/resolved.conf.d/telltale.confand restart it. - A site is broken
- Find it in the Query log and press Why?: it names the list and line that blocked it. Then allow it with a quick rule, or add an allow line (
@@||name^) to a list. - Every query shows the same address
- Something in between rewrites addresses: a router forwarding DNS, Docker without host networking, or Kubernetes with
externalTrafficPolicy: Cluster. The UI's banner says which address; the client IP guide has the fixes. - Lost the setup token
telltale auth setup-tokenprints it again until the first admin exists.