Cloudflare DDNS updater for A/AAAA records on a schedule, with Discord/Slack notifications
190
Keeps a Cloudflare A (IPv4) and/or AAAA (IPv6) record pointed at your current public IP, so a home server stays reachable at your own domain without a static IP. It runs on a schedule (every 5 minutes by default), only updates the record when the IP changes, and can notify Discord or Slack.
linux/amd64 and linux/arm64PUID/PGID); logs to a file on the hostunhealthy after a failed updateSource, full documentation and issues: github.com/Chaos-man/cloudflare-ddns-updater
The DNS record must already exist in Cloudflare; this updates it, it doesn't create it.
compose.yaml
services:
cloudflare-ddns:
image: chaosmanorg/cloudflare-ddns:latest
container_name: cloudflare-ddns
restart: unless-stopped
environment:
PUID: ${PUID:?Set PUID in .env}
PGID: ${PGID:?Set PGID in .env}
CF_API_TOKEN: ${CF_API_TOKEN:?Set CF_API_TOKEN in .env}
CF_ZONE_ID: ${CF_ZONE_ID:?Set CF_ZONE_ID in .env}
CF_RECORD_NAME: ${CF_RECORD_NAME:-}
# Optional, e.g.:
# DISCORD_URI: ${DISCORD_URI}
volumes:
- ./logs:/logs
.env (next to compose.yaml; chmod 600 .env)
PUID=1000 # user/group the container runs as, e.g. from `id`
PGID=1000
CF_API_TOKEN=your-api-token # scoped API token with Zone.DNS edit permission
CF_ZONE_ID=your-zone-id # Cloudflare dashboard > your domain > Overview
CF_RECORD_NAME=home.example.com
docker compose up -d
docker compose logs # startup summary and first-run result
tail -f logs/ddns.log # every run
| Variable | Description |
|---|---|
PUID, PGID | Numeric user and group IDs the container runs as; ./logs is owned by them |
CF_API_TOKEN | Cloudflare API token with Zone.DNS edit permission (or a Global API Key, see CF_AUTH_METHOD) |
CF_ZONE_ID | Zone ID from the domain's Overview page in the Cloudflare dashboard |
CF_RECORD_NAME | A record to update. Leave empty for IPv6 only |
Add a line under environment: for each one you use (e.g. DISCORD_URI: ${DISCORD_URI}) and set its value in .env.
| Variable | Default | Description |
|---|---|---|
CF_RECORD_NAME_V6 | AAAA record to update (needs network_mode: host, see below) | |
STATIC_IPV6_MODE | false | true reads the address from the host's interfaces instead of an external service |
LAST_NOTABLE_HEXES | ffff:ffff | With STATIC_IPV6_MODE: the address suffix to pick |
CF_TTL | 3600 | Record TTL in seconds (60-86400), or 1 for automatic |
CF_PROXY | false | true to proxy the record through Cloudflare |
CF_AUTH_METHOD | token | global to use a Global API Key in CF_API_TOKEN |
CF_AUTH_EMAIL | Cloudflare login email; required with CF_AUTH_METHOD=global | |
DISCORD_URI | Discord webhook URL | |
SLACK_URI, SLACK_CHANNEL | Slack webhook URL and channel | |
SITENAME | record name | Name used in notifications |
DDNS_SCHEDULE | 5m | 5m, 1h, 1d, or a cron expression such as */15 * * * * |
RUN_ON_START | true | Run an update when the container starts |
TZ | UTC | Timezone for logs and the schedule, e.g. America/Chicago |
LOG_FILE | /logs/ddns.log | Log file; set empty to log to docker logs instead |
LOG_MAX_SIZE | 1048576 | Rotate the log to ddns.log.1 past this many bytes |
Docker's default network has no IPv6, so for an AAAA record add CF_RECORD_NAME_V6 and run on the host network:
services:
cloudflare-ddns:
network_mode: host
environment:
CF_RECORD_NAME_V6: ${CF_RECORD_NAME_V6}
docker compose logs: startup summary, configuration errors, and whether the first run succeeded../logs/ddns.log: every run (fetched IP, unchanged/updated, notification results, errors). Rotated at LOG_MAX_SIZE.unhealthy until an update succeeds.Run an update by hand:
docker exec cloudflare-ddns /app/entrypoint.sh run cloudflare-ddns # IPv4
docker exec cloudflare-ddns /app/entrypoint.sh run cloudflare-ddns-v6 # IPv6
latest: the newest release1.0.0, …: fixed versions; pin one in compose.yaml for predictable upgradesBased on K0p1-Git/cloudflare-ddns-updater. MIT licensed.
Content type
Image
Digest
sha256:e0efde749…
Size
14.6 MB
Last updated
1 day ago
docker pull chaosmanorg/cloudflare-ddns