Skip to content

Repository files navigation

External DNS DigitalOcean Webhook

A webhook provider for ExternalDNS that manages DNS records in DigitalOcean.

Features

  • 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

Configuration

Environment Variables

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

Command Line Flags

--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 to 127.0.0.1:8080 by default. These endpoints are consumed only by ExternalDNS and should not be reachable from outside the pod.
  • Health & metrics (/healthz, /metrics) — bound to 0.0.0.0:8888 by default, so probes and Prometheus can reach them without exposing the webhook API.

Deployment

Using Helm

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
  - crd

Note: ExternalDNS reaches the webhook API over http://localhost:8080 in the same pod. Health checks and metrics use the separate 0.0.0.0:8888 listener. Probes and Prometheus must therefore target port 8888, 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.yaml

Kubernetes Deployment (Sidecar) - Manual

apiVersion: 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: 5

Secret

apiVersion: v1
kind: Secret
metadata:
  name: digitalocean-credentials
type: Opaque
stringData:
  token: "your-digitalocean-api-token"

Building

Prerequisites

  • Go 1.26+
  • Make
  • GoReleaser (required for building Docker images)

Commands

# 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 run

Metrics

The 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

Why Webhook Instead of In-Tree Provider?

  1. Independent release cycle - No waiting for upstream ExternalDNS releases
  2. Custom features - Rate limiting, retry logic, enhanced error handling
  3. Better control - Configure retry parameters for your specific needs
  4. Upstream policy - ExternalDNS prefers webhook providers for new/updated providers

Rate Limiting

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.

Credits

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.

License

Apache License 2.0. See LICENSE for details.

About

External DNS DigitalOcean Webhook

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages