Skip to content

amoniacou/external-dns-digitalocean-webhook

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

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: 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 to 127.0.0.1:8888 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:8080 by 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.

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

Note: --port=8080 puts the webhook API on the port the ExternalDNS Helm chart expects (http-webhook); ExternalDNS reaches it over localhost in the same pod. Health checks and metrics are served on a separate listener, moved here to 8888 via --health-port so it does not collide with the API port. The webhook API host defaults to 127.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.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: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: 5

Secret

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

Building

Prerequisites

  • Go 1.25+
  • 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: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

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.

Releases

Packages

Used by

Contributors

Languages