# traefik-cloudflare-dns **Cloudflare DNS records that follow your Traefik containers.** traefik-cloudflare-dns watches Docker for containers whose Traefik router rules name a host, creates a Cloudflare record for each new host, and deletes the records it created once their host has been gone long enough. - **Follows Docker events.** It reacts when a container starts, stops or is removed, and resyncs every minute in case an event was missed. - **Never touches records it didn't make.** It only treats a record as its own when the record points at this instance's `TARGET` and carries its comment and tags. Records made by hand, or by another instance pointing at another server, stay as they are. - **Waits before deleting.** A host must be down for `DELETE_AFTER` (1 hour by default) before its record is removed, so redeploys and restarts never cause a DNS gap. - **Small and safe.** A static, distroless, non-root image with no ports, built to sit behind a read-only Docker socket proxy. ## How it decides what to create Every running container's `traefik.http.routers..rule` labels are read. Each hostname in a `Host(...)` matcher that is `DOMAIN` or a name under it gets a record: | `TARGET` | Record | | --- | --- | | IPv4 address | `A` | | IPv6 address | `AAAA` | | hostname | `CNAME` | If a record with that name already exists and isn't owned by this instance, it is left alone, and a warning is logged once. A typical setup is one wildcard record (`*.example.com`) for your main server, plus one instance of this tool on every other server, with `TARGET` set to that server's IP. Explicit records take priority over the wildcard, so each app's name resolves to the server it runs on. ## Quick start The repo's [`compose.yml`](compose.yml) is ready to deploy as is, for example as a Docker Compose app in Coolify on each server. Copy [`.env.example`](.env.example) to `.env` and fill in the four required values. The minimal form is: ```yaml services: traefik-cloudflare-dns: image: ghcr.io/tiennm99/traefik-cloudflare-dns:1 restart: unless-stopped environment: CF_API_TOKEN: ${CF_API_TOKEN:?required} CF_ZONE_ID: ${CF_ZONE_ID:?required} DOMAIN: example.com TARGET: 192.0.2.10 DOCKER_HOST: tcp://dockerproxy:2375 depends_on: - dockerproxy dockerproxy: image: tecnativa/docker-socket-proxy:latest restart: unless-stopped environment: CONTAINERS: 1 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro ``` The proxy only needs `CONTAINERS`. Events and ping are allowed by the proxy's defaults, and every `POST` is refused, so the tool can read Docker but never change it. Create the Cloudflare API token with **Zone → DNS → Edit**, limited to the one zone. ## Configuration | Variable | Default | Purpose | | --- | --- | --- | | `CF_API_TOKEN` | required | Cloudflare API token with DNS edit on the zone | | `CF_ZONE_ID` | required | Zone ID, from the zone's overview page | | `DOMAIN` | required | Only hosts equal to or under this name are managed | | `TARGET` | required | Record content: this server's IP, or a hostname for a CNAME | | `PROXIED` | `false` | Create orange-cloud (proxied) records | | `TTL` | `1` | Record TTL in seconds; `1` means automatic | | `RECORD_COMMENT` | `managed by traefik-cloudflare-dns` | Comment set on every record, and part of ownership | | `RECORD_TAGS` | empty | Comma-separated `name:value` tags, set on every record and part of ownership | | `DELETE_AFTER` | `1h` | How long a host must be down before its record is deleted | | `RESYNC_INTERVAL` | `1m` | Full resync period, on top of Docker events | | `DRY_RUN` | `false` | Log creates and deletes without making them | | `DOCKER_HOST` | `unix:///var/run/docker.sock` | Docker API endpoint, `unix://` or `tcp://` | | `LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` | ### Comments and tags Cloudflare allows record comments on every plan. They are limited to 100 characters on Free and 500 on paid plans. **Tags are only available on paid plans**, and on a Free zone Cloudflare rejects any record that has them, so leave `RECORD_TAGS` empty there. Ownership is matched exactly. If you change `RECORD_COMMENT` or `RECORD_TAGS`, records created under the old values are no longer this instance's, so they won't be deleted automatically. Update or remove them by hand. ### Deletion timing The clock for `DELETE_AFTER` starts when a resync finds an owned record whose host no longer belongs to any running container. It resets if the host comes back. The timers live in memory, so a restart starts them over. That can only delay a deletion, never bring one forward. Give each server's instance its own `TARGET`. Two instances pointing at the same target with the same comment would each treat the other's records as their own. ## Development ```bash go vet ./... go test -race ./... docker build -t traefik-cloudflare-dns:dev . ``` CI runs the same checks on every push. Pushes to `main` publish `ghcr.io/tiennm99/traefik-cloudflare-dns:edge`. A `vX.Y.Z` tag publishes `X.Y.Z`, `X.Y` and `X`. ## License [Apache 2.0](LICENSE)