An external-dns webhook provider that manages DNS records through the NextDNS Rewrites API.
graph LR
A[External-DNS] -->|HTTP| B[This webhook]
B -->|API| C[NextDNS]
- Syncs A, AAAA, and CNAME records from Kubernetes to NextDNS
- Blocks overwrites of existing records unless you opt in per-Ingress via annotation
- Retries transient API failures (5xx, 429) with exponential backoff
- Supports dry-run mode to preview changes before applying them
- Runs as a sidecar alongside external-dns
- Kubernetes cluster
- NextDNS account with API access
- NextDNS Profile ID and API Key
All configuration is through environment variables.
| Variable | Description |
|---|---|
NEXTDNS_API_KEY |
Your NextDNS API key (found at bottom of account page) |
NEXTDNS_PROFILE_ID |
Your NextDNS profile ID |
| Variable | Default | Description |
|---|---|---|
SERVER_PORT |
8888 |
Webhook API port (localhost only) |
HEALTH_PORT |
8080 |
Health check port (exposed for k8s probes) |
DRY_RUN |
false |
Preview changes without applying them |
LOG_LEVEL |
info |
One of: trace, debug, info, warn, error |
DOMAIN_FILTER |
Comma-separated list of domains to manage | |
SUPPORTED_RECORDS |
A,AAAA,CNAME |
Record types to handle |
NEXTDNS_BASE_URL |
https://api.nextdns.io |
API base URL |
flox activate
export NEXTDNS_API_KEY="your-api-key"
export NEXTDNS_PROFILE_ID="your-profile-id"
just build && just rundocker build -t external-dns-nextdns-webhook:latest .
docker run -d \
-e NEXTDNS_API_KEY="your-api-key" \
-e NEXTDNS_PROFILE_ID="your-profile-id" \
-p 8080:8080 \
external-dns-nextdns-webhook:latestDeploy as a sidecar with external-dns using the manifests in deploy/kubernetes/:
# Using kustomize
cd deploy/kubernetes
cp secret.yaml.example secret.yaml
# Edit secret.yaml with your credentials
kubectl apply -k .Or apply the manifests individually:
kubectl apply -f deploy/kubernetes/namespace.yaml
cp deploy/kubernetes/secret.yaml.example deploy/kubernetes/secret.yaml
# Edit secret.yaml
kubectl apply -f deploy/kubernetes/secret.yaml
kubectl apply -f deploy/kubernetes/serviceaccount.yaml
kubectl apply -f deploy/kubernetes/clusterrole.yaml
kubectl apply -f deploy/kubernetes/clusterrolebinding.yaml
kubectl apply -f deploy/kubernetes/configmap.yaml
kubectl apply -f deploy/kubernetes/service.yaml
kubectl apply -f deploy/kubernetes/deployment.yamlSee deploy/kubernetes/README.md for troubleshooting.
By default, the provider won't overwrite a DNS record that already exists in NextDNS. To allow it for a specific Ingress, add this annotation:
metadata:
annotations:
external-dns.alpha.kubernetes.io/nextdns-allow-overwrite: "true"When an overwrite is blocked, you'll see a log like:
WARNING: Record already exists and will NOT be overwritten.
DNS Name: app.example.com
Record Type: A
Current Value: 192.168.1.100
Planned Value: 192.168.1.200
To allow overwrite, add annotation: external-dns.alpha.kubernetes.io/nextdns-allow-overwrite: "true"
Set DRY_RUN=true to preview what would change without touching NextDNS. It fetches current records (read-only) and logs what it would do:
=== DRY RUN PREVIEW ===
INFO Would create record action=CREATE dns_name=new.example.com record_type=A target=1.2.3.4
INFO Would update record action=UPDATE dns_name=existing.example.com current=10.0.0.1 planned=10.0.0.2
INFO Would delete record action=DELETE dns_name=old.example.com record_type=A target=192.168.1.99
=== END DRY RUN PREVIEW ===
Failed API calls are retried 3 times with backoff delays of 1s, 2s, 4s. Only transient errors are retried (network timeouts, 5xx, 429). Client errors like 401 or 404 fail immediately.
Run just to see all available commands. The main ones:
just build # Build binary
just test # Run tests
just check # Format + vet + lint
just dev # Run with hot-reload
just docker-build # Build Docker imageThis project uses Flox for the dev environment and Just as a task runner.
cmd/webhook/main.go Entry point
internal/nextdns/
config.go Configuration from env vars
client.go NextDNS API client with retry
provider.go external-dns provider interface
pkg/webhook/server.go HTTP servers (API + health)
deploy/kubernetes/ Kustomize manifests
Renovate handles Go modules, Docker base images, and GitHub Actions automatically. It runs weekly on Mondays at 6am UTC. See renovate.json.
Flox packages aren't supported by Renovate yet (tracking issue), so there's a separate GitHub Actions workflow (.github/workflows/flox-update.yml) that runs weekly to update them.
Apache License 2.0. See LICENSE for details.