A webhook provider for ExternalDNS that manages DNS records in DigitalOcean.
- Full DigitalOcean DNS API support
- Automatic retry with exponential backoff for rate limits (429) and server errors (5xx)
- Configurable retry parameters
- Graceful error handling with SoftError support
- Runs as a sidecar container alongside ExternalDNS
| Variable | Required | Default | Description |
|---|---|---|---|
DO_TOKEN |
Yes | - | DigitalOcean API token |
DO_DOMAIN_FILTER |
No | - | Comma-separated list of domains to manage |
DO_DRY_RUN |
No | false |
Enable dry-run mode |
DO_API_PAGE_SIZE |
No | 200 |
API pagination size |
DO_HTTP_RETRY_MAX |
No | 3 |
Maximum HTTP retries |
DO_HTTP_RETRY_WAIT_MIN |
No | 1s |
Minimum wait between retries |
DO_HTTP_RETRY_WAIT_MAX |
No | 30s |
Maximum wait between retries |
DO_WORKERS |
No | 10 |
Number of concurrent workers used to fetch records |
LOG_LEVEL |
No | info |
Log level (debug, info, warn, or error) |
LOG_FORMAT |
No | text |
Log format (text or json) |
WEBHOOK_HOST |
No | 127.0.0.1 |
Webhook API listener host |
WEBHOOK_PORT |
No | 8080 |
Webhook API listener port |
HEALTH_HOST |
No | 0.0.0.0 |
Health and metrics listener host |
HEALTH_PORT |
No | 8888 |
Health and metrics listener port |
--log-level Log level (debug, info, warn, error) [default: info]
--log-format Log format (text, json) [default: text]
--host Webhook API server host [default: 127.0.0.1]
--port Webhook API server port [default: 8080]
--health-host Health and metrics server host [default: 0.0.0.0]
--health-port Health and metrics server port [default: 8888]
--dry-run Enable dry-run mode
--retry-max Maximum HTTP retries [default: 3]
--retry-wait-max Maximum wait between retries [default: 30s]
Command-line flags take precedence over their corresponding environment variables. The server exposes two separate listeners:
- Webhook API (
/,/records,/adjustendpoints) — bound to127.0.0.1:8080by default. These endpoints are consumed only by ExternalDNS and should not be reachable from outside the pod. - Health & metrics (
/healthz,/metrics) — bound to0.0.0.0:8888by default, so probes and Prometheus can reach them without exposing the webhook API.
You can deploy ExternalDNS with this webhook using the official ExternalDNS Helm chart.
Create a values.yaml file:
provider:
name: webhook
webhook:
image:
repository: ghcr.io/amoniacou/external-dns-digitalocean-webhook
tag: latest
env:
- name: DO_TOKEN
valueFrom:
secretKeyRef:
name: digitalocean-credentials
key: token
- name: DO_DOMAIN_FILTER
value: "example.com"
- name: DO_HTTP_RETRY_MAX
value: "5"
args:
- --port=8080
- --host=localhost
- --health-port=8888
- --health-host=0.0.0.0
- --log-level=info
securityContext:
runAsUser: 65532
runAsGroup: 65532
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /healthz
port: 8888
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8888
initialDelaySeconds: 5
periodSeconds: 5
policy: sync
registry: txt
txtOwnerId: my-cluster
extraArgs:
webhook-provider-url: http://localhost:8080
domainFilters:
- example.com
sources:
- ingress
- crdNote: ExternalDNS reaches the webhook API over
http://localhost:8080in the same pod. Health checks and metrics use the separate0.0.0.0:8888listener. Probes and Prometheus must therefore target port8888, not the webhook API port.
Install the chart:
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm upgrade --install external-dns external-dns/external-dns \
--version 1.21.1 \
--namespace external-dns \
--create-namespace \
-f values.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
template:
spec:
containers:
# ExternalDNS container
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.20.0
args:
- --source=ingress
- --source=crd
- --provider=webhook
- --webhook-provider-url=http://localhost:8080
- --policy=sync
- --registry=txt
- --txt-owner-id=my-cluster
- --interval=1m
# DigitalOcean Webhook sidecar
- name: digitalocean-webhook
image: ghcr.io/amoniacou/external-dns-digitalocean-webhook:latest
securityContext:
runAsUser: 65532
runAsGroup: 65532
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
args:
- --port=8080
- --host=localhost
- --health-port=8888
- --health-host=0.0.0.0
- --log-level=info
- --retry-max=5
- --retry-wait-max=60s
env:
- name: DO_TOKEN
valueFrom:
secretKeyRef:
name: digitalocean-credentials
key: token
- name: DO_DOMAIN_FILTER
value: "example.com,example.org"
ports:
- containerPort: 8080
name: http-webhook
- containerPort: 8888
name: http-health
livenessProbe:
httpGet:
path: /healthz
port: http-health
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: http-health
initialDelaySeconds: 5
periodSeconds: 5apiVersion: v1
kind: Secret
metadata:
name: digitalocean-credentials
type: Opaque
stringData:
token: "your-digitalocean-api-token"- Go 1.26+
- Make
- GoReleaser (required for building Docker images)
# Run tests
make test
# Build binary locally (outputs to bin/webhook)
make build
# Build Docker image (uses GoReleaser to prepare artifacts)
make docker-build
# Run locally
DO_TOKEN=your-token make runThe webhook exposes Prometheus metrics at http://localhost:8888/metrics by default (the health and metrics listener, separate from the webhook API). These metrics help track interactions with the DigitalOcean API and monitor rate limits.
| Metric | Description | Labels |
|---|---|---|
digitalocean_api_requests_total |
Total number of requests to DigitalOcean API | action (HTTP method + path) |
digitalocean_api_errors_total |
Total number of failed requests (4xx/5xx) | action |
digitalocean_api_rate_limits_total |
Total number of rate limit hits (HTTP 429) | action |
- Independent release cycle - No waiting for upstream ExternalDNS releases
- Custom features - Rate limiting, retry logic, enhanced error handling
- Better control - Configure retry parameters for your specific needs
- Upstream policy - ExternalDNS prefers webhook providers for new/updated providers
This webhook uses the built-in retry mechanism from the godo library:
- Automatically retries on HTTP 429 (rate limit) and 5xx errors
- Exponential backoff between retries
- Configurable max retries and wait times
If all retries are exhausted, the error is returned as a SoftError, allowing ExternalDNS to retry on the next reconciliation cycle.
This project is based on the original in-tree DigitalOcean provider code from ExternalDNS. We have adapted it to run as a standalone webhook provider with enhanced features and independent lifecycle.
Apache License 2.0. See LICENSE for details.