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 |
--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: 8888]
--health-host Health and metrics server host [default: 0.0.0.0]
--health-port Health and metrics server port [default: 8080]
--dry-run Enable dry-run mode
--retry-max Maximum HTTP retries [default: 3]
--retry-wait-max Maximum wait between retries [default: 30s]
The server exposes two separate listeners:
- Webhook API (
/,/records,/adjustendpoints) — bound to127.0.0.1:8888by 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:8080by default, so probes and Prometheus can reach them without exposing the webhook API.
Each host/port can also be set via the WEBHOOK_HOST, WEBHOOK_PORT, HEALTH_HOST and HEALTH_PORT environment variables.
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
- --health-port=8888
- --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: 5Note:
--port=8080puts the webhook API on the port the ExternalDNS Helm chart expects (http-webhook); ExternalDNS reaches it overlocalhostin the same pod. Health checks and metrics are served on a separate listener, moved here to8888via--health-portso it does not collide with the API port. The webhook API host defaults to127.0.0.1, so probes and scraping must target the health port (8888), not the 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 -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:8888
- --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:
- --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: 8888
name: http-webhook
- containerPort: 8080
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.25+
- 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:8080/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.