dnsweaver
Automatic DNS records for containers, VMs & clusters.
dnsweaver reads hostnames from seven sources and writes records to eleven DNS providers. A container starts with a Traefik label or a Proxmox VM boots, and the record appears; when they go away, so does it. Internal and external records come from the same labels, so split-horizon costs nothing extra.
Why dnsweaver?
Think of dnsweaver as external-dns for the homelab. Where most tools solve a slice of the problem, dnsweaver covers the parts self-hosters actually run into:
- It does Proxmox. Auto-create A records for VMs and LXCs from the PVE API. Almost no other DNS automation tool offers it.
- It speaks self-hosted DNS. First-class Technitium, Pi-hole, AdGuard Home, and dnsmasq support. Not an afterthought, and not alpha.
- It's multi-source. Traefik, Caddy, nginx-proxy, native labels, Kubernetes, Proxmox VE, and Incus in one binary. Run one or all of them at once.
external-dnsis Kubernetes-only. - It does split-horizon out of the box. Internal and external records from the same labels. Route private hostnames to Technitium and public ones to Cloudflare simultaneously.
- It's a single static Go binary. Multi-arch (amd64/arm64), with no separate application runtime or sidecar required.
Features
- 🔀 Multi-Provider Support — Route domains to matching providers, with optional workload instance selection in the unreleased source
- 🌐 Split-Horizon DNS — Internal and external records from the same container labels
- 🧩 Seven Sources — Traefik, Caddy, nginx-proxy, native labels, Kubernetes, Proxmox VE, and Incus, in one binary. See Supported Sources
- 🏗️ Separate ownership namespaces — Use distinct instance IDs and non-conflicting record scopes for multiple dnsweaver processes; this does not provide leader election or active-active HA
- 🔒 Socket Proxy Compatible — Connect via TCP to a Docker socket proxy for improved security
- 🛡️ Hardened TLS — Unified per-instance TLS controls (custom CA, mTLS client certs, SNI override, configurable min version; TLS 1.2 floor by default) for every HTTP-based provider and the Proxmox and Incus sources
- 📊 Observable — Prometheus metrics, health endpoints, structured logging
- 🔑 Secrets Support — Docker secrets and Kubernetes Secrets via
_FILEsuffix variables
Supported Sources
Sources are where hostnames come from. Enable any combination with DNSWEAVER_SOURCES.
| Source | Reads | Runs over |
|--------|-------|-----------|
| traefik | traefik.http.routers.*.rule labels, plus Traefik dynamic config files | Docker, Swarm, Incus |
| caddy | caddy / caddy_ labels from caddy-docker-proxy | Docker, Swarm, Incus |
| nginx-proxy | VIRTUAL_HOST labels from jwilder/nginx-proxy | Docker, Swarm, Incus |
| dnsweaver | Native dnsweaver.* labels for explicit record configuration | Docker, Swarm, Incus |
| kubernetes | Ingress, IngressRoute, HTTPRoute, and Service resources | Kubernetes |
| proxmox | PVE API: QEMU VMs via guest agent, and LXC containers | Proxmox VE |
| incus | Incus API: system containers and VMs, local socket or remote HTTPS | Incus |
Supported Providers
Providers are where records get written. Run several at once to split internal and external DNS.
| Provider | Record Types | Notes | |----------|--------------|-------| | Technitium | A, AAAA, CNAME, SRV, TXT | Full-featured self-hosted DNS | | Cloudflare | A, AAAA, CNAME, SRV, TXT | With optional proxy support | | OVHcloud | A, AAAA, CNAME, SRV, TXT | Public DNS for OVH-hosted domains | | RFC 2136 | A, AAAA, CNAME, SRV, TXT | HMAC-TSIG-compatible BIND, PowerDNS, Knot, Technitium; Windows secure updates are not supported | | PowerDNS | A, AAAA, CNAME, SRV, TXT | Native Authoritative HTTP API | | Pi-hole | A, CNAME | API or file mode | | AdGuard Home | A, AAAA, CNAME | DNS rewrite management | | OPNsense | A, AAAA | Unbound or Dnsmasq host overrides via REST API | | pfSense | A, AAAA | Unbound or Dnsmasq host overrides via REST API (community pfSense-pkg-RESTAPI) | | dnsmasq | A, CNAME | File-based configuration | | Webhook | Any | Custom integrations |
Quick Start
Before upgrading
In v3.0.0, health, readiness and metrics listen on loopback by default. Remote scrapers and probes need an explicit network listener and access restrictions. See the management-listener migration guide. The bundled container and Kubernetes probes use local checks.
The same changes reject invalid TLS settings and unreadable credential files instead of falling back. Remote dnsmasq updates require an SFTP server with the OpenSSH POSIX rename extension; see the provider requirements. Check the release notes for your image tag before applying these settings.
Installation
# Docker Hub
docker pull maxamill/dnsweaver:latest
GitHub Container Registry
docker pull ghcr.io/maxfield-allison/dnsweaver:latest
Basic Example
services:
dnsweaver:
image: maxamill/dnsweaver:latest
restart: unless-stopped
environment:
- DNSWEAVER_INSTANCES=internal-dns
- DNSWEAVER_INTERNAL_DNS_TYPE=technitium
- DNSWEAVER_INTERNAL_DNS_URL=http://dns.internal:5380
- DNSWEAVER_INTERNAL_DNS_TOKEN_FILE=/run/secrets/technitium_token
- DNSWEAVER_INTERNAL_DNS_ZONE=home.example.com
- DNSWEAVER_INTERNAL_DNS_RECORD_TYPE=A
- DNSWEAVER_INTERNAL_DNS_TARGET=192.0.2.100
- DNSWEAVER_INTERNAL_DNS_DOMAINS=*.home.example.com
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
secrets:
- technitium_token
secrets:
technitium_token:
external: true
How It Works
flowchart LR
subgraph sources["7 sources"]
direction TB
A["traefik · caddy · nginx-proxy · dnsweaver<br/>labels on Docker, Swarm, Incus"]
D["kubernetes<br/>Ingress · IngressRoute · HTTPRoute · Service"]
P["proxmox · incus<br/>API: VMs · LXC · system containers"]
end
subgraph engine["dnsweaver"]
direction TB
W["Watch<br/>events"] --> M["Match hostname<br/>to domain patterns"] --> R["Reconcile<br/>desired vs. live"]
end
subgraph providers["11 providers · split-horizon"]
direction TB
INT["Internal instance<br/>e.g. technitium · pi-hole · adguard"]
EXT["External instance<br/>e.g. cloudflare · ovhcloud"]
end
A --> W
D --> W
P --> W
R -->|create / update / delete| INT
R -->|create / update / delete| EXT
- A container starts with a Traefik label (or a Kubernetes Ingress/HTTPRoute is created):
labels:
- "traefik.http.routers.myapp.rule=Host(myapp.home.example.com)"
- dnsweaver extracts the hostname and matches it against configured provider domain patterns
- The matching provider creates the DNS record:
myapp.home.example.com → 192.0.2.100
- CNAME: myapp.example.com → proxy.example.com
- When the container stops (or the Kubernetes resource is deleted), the DNS record is automatically cleaned up
Documentation
| Topic | Description | |-------|-------------| | Getting Started | Installation and first configuration | | Configuration | Environment variables reference | | Providers | Provider-specific setup guides | | Sources | All seven sources, what each reads, and the capability matrix | | Kubernetes | Kubernetes deployment with Helm/Kustomize | | Proxmox VE | Auto-DNS for VMs and LXC containers | | Incus | Auto-DNS for system containers and VMs | | Split-Horizon DNS | Internal + external records | | Docker Swarm | Swarm deployment guide | | Observability | Metrics, logging, and health checks | | FAQ | Common questions and troubleshooting |
TLS Configuration
Every HTTP-based provider (Technitium, AdGuard Home, Cloudflare, OVHcloud, PowerDNS, Pi-hole, OPNsense, pfSense, Webhook) and the Proxmox and Incus sources share a single TLS configuration surface:
| Env key (per instance) | Purpose |
|------------------------|---------|
| DNSWEAVER_ | Trust a private CA bundle (PEM) |
| DNSWEAVER_ / _TLS_KEY_FILE | Present a client certificate (mTLS) |
| DNSWEAVER_ | Override SNI / hostname verification |
| DNSWEAVER_ | Disable verification (development only) |
| DNSWEAVER_ | 1.2 (default) or 1.3 |
The Proxmox and Incus sources use the same keys under DNSWEAVER_PROXMOX_TLS_ and DNSWEAVER_INCUS_TLS_ (Incus adds DNSWEAVER_INCUS_TLS_PIN_SHA for certificate pinning). The legacy *_INSECURE_SKIP_VERIFY and DNSWEAVER_PROXMOX_VERIFY_TLS variables are still accepted but emit a deprecation warning at startup — migrate to the unified TLS_SKIP_VERIFY keys. See the Environment Reference and SECURITY.md for full details and recipes.
Mounting certs? The container drops privileges to uid/gid1000, so cert/key files must be readable by that user — aroot:root 0600key yieldspermission denied. See TLS Certificate File Permissions.
Kubernetes Quick Start
Deploy dnsweaver to watch Kubernetes resources for DNS management:
# Using Kustomize
kubectl apply -k https://github.com/maxfield-allison/dnsweaver/deploy/kustomize/base
Using Helm
helm install dnsweaver deploy/helm/dnsweaver/ \
--namespace dnsweaver --create-namespace
dnsweaver automatically detects hostnames from Ingress, IngressRoute (Traefik), HTTPRoute (Gateway API), and Service resources. Use dnsweaver.dev/* annotations for per-resource overrides:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
dnsweaver.dev/record-type: "A"
dnsweaver.dev/target: "192.0.2.100"
spec:
rules:
- host: app.example.com
See the Kubernetes deployment guide for full configuration.
Split-Horizon DNS Example
Manage internal and external DNS from the same container labels:
environment:
- DNSWEAVER_INSTANCES=internal,external
# Internal: Technitium → private IP
- DNSWEAVER_INTERNAL_TYPE=technitium
- DNSWEAVER_INTERNAL_RECORD_TYPE=A
- DNSWEAVER_INTERNAL_TARGET=192.0.2.100
- DNSWEAVER_INTERNAL_DOMAINS=*.example.com
# External: Cloudflare → tunnel CNAME
- DNSWEAVER_EXTERNAL_TYPE=cloudflare
- DNSWEAVER_EXTERNAL_RECORD_TYPE=CNAME
- DNSWEAVER_EXTERNAL_TARGET=tunnel.example.com
- DNSWEAVER_EXTERNAL_DOMAINS=*.example.com
With this configuration, when app.example.com starts:
- Internal DNS →
Arecord →192.0.2.100 - External DNS →
CNAMErecord →tunnel.example.com
Contributing
Contributions are welcome! See CONTRIBUTING for guidelines.
Support
dnsweaver is free and MIT-licensed. If it saves you time, you can support ProbablyFine through GitHub Sponsors. Sponsorship helps cover code-signing and other development costs. It doesn't buy priority support, roadmap influence, or promised features.
Star History
If dnsweaver saves you time, a ⭐ helps others find it.