A small and fast DDNS updater for CloudFlare.
π CloudFlare DDNS
π₯· Dropping privileges . . .
π₯· Priviledges after dropping:
πΈ Effective UID: 1000
πΈ Effective GID: 1000
πΈ Supplementary GIDs: (empty)
π Quiet mode enabled.
π£ Added a new A record of β¦β¦ (ID: β¦β¦).
π£ Added a new AAAA record of β¦β¦ (ID: β¦β¦).
- Ultra-small Docker images (about 2 MB) for all architectures.
- Ability to update multiple domains across different zones.
- Ability to enable or disable IPv4 and IPv6 individually.
- Support of internationalized domain names.
- Ability to remove stale records or choose to remove records on exit/stop.
- Ability to obtain IP addresses from CloudFlare, ipify, or local network interfaces.
- Support of timezone and Cron expressions.
- Full configurability via environment variables.
- Ability to pass API tokens via a file instead of an environment variable.
- Local caching to reduce CloudFlare API usage.
By default, public IP addresses are obtained using CloudFlare via DNS-over-HTTPS. This minimizes the impact on privacy because we are already using the CloudFlare API to update DNS records. Moreover, if CloudFlare servers are not reachable, chances are you could not update DNS records anyways. You can also configure the tool to use ipify, which claims not to log any visitor information.
π· The superuser privilege is immediately dropped after the updater starts.
The updater honors PGID
and PUID
and will drop Linux capabilities (divided superuser privileges).
π The source code depends on five external libraries (outside the Go project).
- cap:
Manipulation of Linux capabilities. - cloudflare-go:
The official Go binding of CloudFlare API v4. It provides robust handling of pagination, rate limiting, and other tricky details. - cron:
Parsing of Cron expressions. - go-cache:
Essentiallymap[string]interface{}
with expiration times. - assert (only for testing):
A comprehensive tool set for testing Go programs.
The upstream cloudflare-go library (up to version 0.19.0) contains bugs that could lead to deadlocks in rare cases. The fixes have been sent to the upstream as the pull request #674.
π Directly run the provided Docker images.
docker run \
--network host \
-e CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
-e DOMAINS=www.example.org \
-e PROXIED=true \
favonia/cloudflare-ddns
𧬠Directly run the updater from its source.
You need the Go tool to run the updater from its source.
CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
DOMAINS=www.example.org \
PROXIED=true \
go run ./cmd/*.go
Incorporate the following fragment into the compose file (typically docker-compose.yml
or docker-compose.yaml
).
version: "3"
services:
cloudflare-ddns:
image: favonia/cloudflare-ddns:latest
network_mode: host
restart: always
security_opt:
- no-new-privileges:true
environment:
- PGID=1000
- PUID=1000
- CF_API_TOKEN
- DOMAINS
- PROXIED=true
π‘ Use network_mode: host
(as a hack) to enable IPv6 or read more.
The setting network_mode: host
is for IPv6. If you wish to keep the network separated from the host network, check out the proper way to enable IPv6 support.
π Use restart: always
to automatically restart the updater on system reboot.
Dockerβs default restart policies should prevent excessive logging when there are configuration errors.
π‘οΈ Use no-new-privileges:true
, PUID
, and PGID
to protect yourself.
Change 1000
to the user or group IDs you wish to use to run the updater. The setting no-new-privileges:true
provides additional protection, especially when you run the container as a non-superuser. The updater itself will read PUID
and PGID
and attempt to drop all those privileges as much as possible.
π Use PROXIED=true
to hide your IP addresses.
The setting PROXIED=true
instructs CloudFlare to cache webpages on your machine and hide your actual IP addresses. If you wish to bypass that and expose your actual IP addresses, simply remove PROXIED=true
. (The default value of PROXIED
is false
.)
Add these lines to your environment file (typically .env
):
CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
DOMAINS=example.org,www.example.org,example.io
π CF_API_TOKEN
is your CloudFlare API token.
The value of CF_API_TOKEN
should be an API token (not an API key), which can be obtained from the API Tokens page. Use the Edit zone DNS template to create and copy a token into the environment file.
π DOMAINS
contains the domains to update.
The value of DOMAINS
should be a list of fully qualified domain names separated by commas. For example, DOMAINS=example.org,www.example.org,example.io
instructs the tool to manage the domains example.org
, www.example.org
, and example.io
. These domains do not have to be in the same zone---the tool will identify their zones automatically.
docker-compose pull cloudflare-ddns
docker-compose up --detach --build cloudflare-ddns
Kubernetes offers great flexibility in assembling different objects together. The following shows a minimum setup.
Save the following configuration as cloudflare-ddns.yaml
.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflare-ddns
labels:
app: cloudflare-ddns
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: cloudflare-ddns
template:
metadata:
name: cloudflare-ddns
labels:
app: cloudflare-ddns
spec:
restartPolicy: Always
containers:
- name: cloudflare-ddns
image: favonia/cloudflare-ddns:latest
securityContext:
allowPrivilegeEscalation: false
runAsUser: 1000
runAsGroup: 1000
env:
- name: "IP6_POLICY"
value: "unmanaged"
- name: "PROXIED"
value: "true"
- name: "CF_API_TOKEN"
value: YOUR-CLOUDFLARE-API-TOKEN
- name: "DOMAINS"
value: "example.org,www.example.org,example.io"
π Use restartPolicy: Always
to automatically restart the updater on system reboot.
Kubernetesβs default restart policies should prevent excessive logging when there are configuration errors.
π‘οΈ Use runAsUser
, runAsGroup
, and allowPrivilegeEscalation: false
to protect yourself.
Kubernetes comes with built-in support to drop superuser privileges. The updater itself will also attempt to drop all of them.
π‘ Use IP6_POLICY: "unmanaged"
to disable IPv6 management.
The support of IPv6 in Kubernetes has been improving, but a working setup still takes effort. Since Kubernetes 1.21+, the IPv4/IPv6 dual stack is enabled by default, but a setup which allows IPv6 egress traffic (e.g., to reach CloudFlare servers to detect public IPv6 addresses) still requires deep understanding of Kubernetes and is beyond this simple guide. The popular tool minicube, which implements a simple local Kubernetes cluster, unfortunately does not support IPv6 yet. Until there is an easy way to enable IPv6 in Kubernetes, the template here will have IPv6 disabled.
If you manage to enable IPv6, congratulations. Feel free to remove IP6_POLICY: "unmanaged"
to detect and update both A
and AAAA
records. There is almost no danger in enabling IPv6 even when the IPv6 setup is not working. In the worst case, the updater will remove all AAAA
records associated with the domains in DOMAINS
and IP6_DOMAINS
because those records will appear to be βstale.β The deleted records will be recreated once the updater correctly detects the IPv6 addresses.
π Use PROXIED: "true"
to hide your IP addresses.
The setting PROXIED: "true"
instructs CloudFlare to cache webpages on your machine and hide your actual IP addresses. If you wish to bypass that and expose your actual IP addresses, simply remove PROXIED: "true"
. (The default value of PROXIED
is false
.)
π CF_API_TOKEN
is your CloudFlare API token.
The value of CF_API_TOKEN
should be an API token (not an API key), which can be obtained from the API Tokens page. Use the Edit zone DNS template to create and copy a token into the environment file.
π DOMAINS
contains the domains to update.
The value of DOMAINS
should be a list of fully qualified domain names separated by commas. For example, DOMAINS=example.org,www.example.org,example.io
instructs this tool to manage the domains example.org
, www.example.org
, and example.io
. These domains do not have to be in the same zone---this tool will identify their zones automatically.
kubectl create -f cloudflare-ddns.yaml
π CloudFlare accounts and API tokens
Name | Valid Values | Meaning | Required? | Default Value |
---|---|---|---|---|
CF_ACCOUNT_ID |
CloudFlare Account IDs | The account ID used to distinguish multiple zone IDs with the same name | No | "" (unset) |
CF_API_TOKEN_FILE |
Paths to files containing CloudFlare API tokens | A file that contains the token to access the CloudFlare API | Exactly one of CF_API_TOKEN and CF_API_TOKEN_FILE should be set |
N/A |
CF_API_TOKEN |
CloudFlare API tokens | The token to access the CloudFlare API | Exactly one of CF_API_TOKEN and CF_API_TOKEN_FILE should be set |
N/A |
In most cases, CF_ACCOUNT_ID
is not needed.
π Policies (strategies to detect IP addresses) and domains
Name | Valid Values | Meaning | Required? | Default Value |
---|---|---|---|---|
DOMAINS |
Comma-separated fully qualified domain names | The domains this tool should manage | (See below) | N/A |
IP4_DOMAINS |
Comma-separated fully qualified domain names | The domains this tool should manage for A records |
(See below) | N/A |
IP4_POLICY |
cloudflare , ipify , local , and unmanaged |
How to detect IPv4 addresses. (See below) | No | cloudflare |
IP6_DOMAINS |
Comma-separated fully qualified domain names | The domains this tool should manage for AAAA records |
(See below) | N/A |
IP6_POLICY |
cloudflare , ipify , local , and unmanaged |
How to detect IPv6 addresses. (See below) | No | cloudflare |
π Available policies for
IP4_POLICY
andIP6_POLICY
cloudflare
Get the public IP address by queryingwhoami.cloudflare.
against CloudFlare via DNS-over-HTTPS and update DNS records accordingly.ipify
Get the public IP address via ipifyβs public API and update DNS records accordingly.local
Get the address via local network interfaces and update DNS records accordingly. When multiple local network interfaces or in general multiple IP addresses are present, the tool will use the address that would have been used for outbound UDP connections to CloudFlare servers.β οΈ You need access to the host network (such asnetwork_mode: host
in Docker Compose orhostNetwork: true
in Kubernetes) for this policy, for otherwise the tool will detect the addresses inside the bridge network in Docker or the default namespaces in Kubernetes instead of those in the host network.unmanaged
Stop the DNS updating completely. Existing DNS records will not be removed.The option
IP4_POLICY
is governing IPv4 addresses andA
-type records, while the optionIP6_POLICY
is governing IPv6 addresses andAAAA
-type records. The two options act independently of each other.
π At least one of
DOMAINS
andIP4/6_DOMAINS
must be non-empty.At least one domain should be listed in
DOMAINS
,IP4_DOMAINS
, orIP6_DOMAINS
, for otherwise this updater has nothing to do. It is fine to list the same domain in bothIP4_DOMAINS
andIP6_DOMAINS
, which is equivalent to listing it inDOMAINS
. This updater supports internationalized domain names; Punycode will be decoded and processed using x/net/idna.
β³ Schedules, timeouts, and parameters of new DNS records
Name | Valid Values | Meaning | Required? | Default Value |
---|---|---|---|---|
CACHE_EXPIRATION |
Positive time duration with a unit, such as 1h or 10m . See time.ParseDuration |
The expiration of cached CloudFlare API responses | No | 6h0m0s (6 hours) |
DELETE_ON_STOP |
1 , t , T , TRUE , true , True , 0 , f , F , FALSE , false , and False |
Whether managed DNS records should be deleted on exit | No | false |
DETECTION_TIMEOUT |
Positive time duration with a unit, such as 1h or 10m . See time.ParseDuration |
The timeout of each attempt to detect IP addresses | No | 5s (5 seconds) |
PROXIED |
1 , t , T , TRUE , true , True , 0 , f , F , FALSE , false , and False |
Whether new DNS records should be proxied by CloudFlare | No | false |
TTL |
Time-to-live (TTL) values in seconds | The TTL values used to create new DNS records | No | 1 (This means βautomaticβ to CloudFlare) |
TZ |
Recognized timezones, such as UTC |
The timezone used for logging and parsing UPDATE_CRON |
No | UTC |
UPDATE_CRON |
Cron expressions; documentation of cron. | The schedule to re-check IP addresses and update DNS records (if necessary) | No | @every 5m (every 5 minutes) |
UPDATE_ON_START |
1 , t , T , TRUE , true , True , 0 , f , F , FALSE , false , and False |
Whether to check IP addresses on start regardless of UPDATE_CRON |
No | true |
Note that the update schedule does not take the time to update records into consideration. For example, if the schedule is βfor every 5 minutesβ, and if the updating itself takes 2 minutes, then the actual interval between adjacent updates is 3 minutes, not 5 minutes.
π‘οΈ Dropping superuser privileges
Name | Valid Values | Meaning | Required? | Default Value |
---|---|---|---|---|
PGID |
Non-zero POSIX group ID | The group ID this tool should assume | No | Effective group ID; if it is zero, then the real group ID; if it is still zero, then 1000 |
PUID |
Non-zero POSIX user ID | The user ID this tool should assume | No | Effective user ID; if it is zero, then the real user ID; if it is still zero, then 1000 |
The updater will also try to drop supplementary group IDs.
π Quiet mode
Name | Valid Values | Meaning | Required? | Default Value |
---|---|---|---|---|
QUIET |
1 , t , T , TRUE , true , True , 0 , f , F , FALSE , false , and False |
Whether the updater should reduce the logging | No | false |
If you are using Docker Compose, run docker-compose up --detach
after changing the settings.
If you are using Kubernetes, run kubectl replace -f cloudflare-ddns.yaml
after changing the settings.
I am migrating from oznu/cloudflare-ddns.
Old Parameter | New Paramater | |
---|---|---|
API_KEY=key |
βοΈ | Use CF_API_TOKEN=key |
API_KEY_FILE=file |
βοΈ | Use CF_API_TOKEN_FILE=file |
ZONE=example.org and SUBDOMAIN=sub |
βοΈ | Use DOMAINS=sub.example.org directly |
PROXIED=true |
βοΈ | Same |
RRTYPE=A |
βοΈ | Both IPv4 and IPv6 are enabled by default; use IP6_POLICY=unmanaged to disable IPv6 |
RRTYPE=AAAA |
βοΈ | Both IPv4 and IPv6 are enabled by default; use IP4_POLICY=unmanaged to disable IPv4 |
DELETE_ON_STOP=true |
βοΈ | Same |
INTERFACE=iface |
βοΈ | Not required for local policies; we can handle multiple network interfaces |
CUSTOM_LOOKUP_CMD=cmd |
β | There is not even a shell in the minimum Docker image. |
DNS_SERVER=server |
β | Only the CloudFlare server is supported. |
Questions, suggestions, feature requests, and contributions are all welcome! Feel free to open a GitHub issue.