Skip to content

🌟 A small, feature-rich, and robust Cloudflare DDNS updater

License

Notifications You must be signed in to change notification settings

favonia/cloudflare-ddns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

🌟 Cloudflare DDNS

Github Source Go Reference Codecov Docker Image Size OpenSSF Best Practices OpenSSF Scorecard

A feature-rich and robust Cloudflare DDNS updater with a small footprint. The program will detect your machine's public IP addresses and update DNS records using the Cloudflare API.

πŸ“œ Highlights

⚑ Efficiency

  • 🀏 The Docker image takes less than 5 MB after compression.
  • πŸ” The Go runtime re-uses existing HTTP connections.
  • πŸ—ƒοΈ Cloudflare API responses are cached to reduce the API usage.

πŸ’― Complete Support of Domain Names

  • 😌 You can simply list domains (e.g., www.a.org, hello.io) without knowing their DNS zones.
  • 🌍 Internationalized domain names (e.g., 🐱.example.org and ζ—₯本qcoqjp) are fully supported.
  • πŸƒ Wildcard domains (e.g., *.example.org) are also supported.
  • πŸ•ΉοΈ You can toggle IPv4 (A records) and IPv6 (AAAA records) for each domain.

πŸŒ₯️ Cloudflare-specific Features

πŸ‘οΈ Integration with Notification Services

  • 🩺 The updater can report to Healthchecks or Uptime Kuma so that you receive notifications when it fails to update IP addresses.
  • πŸ“£ The updater can also actively update you via any service supported by the shoutrrr library, including emails, major notification services, major messaging platforms, and generic webhooks.

πŸ•΅οΈ Minimum Privacy Impact

By default, public IP addresses are obtained via Cloudflare debugging page. 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 cannot update DNS records anyways.

πŸ›‘οΈ Attention to Security

  • πŸ›‘οΈ The updater uses only HTTPS or DNS over HTTPS to detect IP addresses. This makes it harder for someone else to trick the updater into updating your DNS records with wrong IP addresses. See the Security Model for more information.

  • ✍️ You can verify the Docker images were built from this repository using the cosign tool (click to expand)
    cosign verify favonia/cloudflare-ddns:latest \
      --certificate-identity-regexp https://github.com/favonia/cloudflare-ddns/ \
      --certificate-oidc-issuer https://token.actions.githubusercontent.com

    Note: this only proves that the Docker image is from this repository, assuming that no one hacks into GitHub or the repository. It does not prove that the code itself is secure.

  • πŸ“š The updater uses only established open-source Go libraries (click to expand)
    • cloudflare-go:
      The official Go binding of Cloudflare API v4.
    • cron:
      Parsing of Cron expressions.
    • go-retryablehttp:
      HTTP clients with automatic retries and exponential backoff.
    • go-querystring:
      A library to construct URL query parameters.
    • shoutrrr:
      A notification library for sending general updates.
    • ttlcache:
      In-memory cache to hold Cloudflare API responses.
    • mock (for testing only):
      A comprehensive, semi-official framework for mocking.
    • testify (for testing only):
      A comprehensive tool set for testing Go programs.

⛷️ Quick Start

(Click to expand the following items.)

πŸ‹ Directly run the Docker image.
docker run \
  --network host \
  -e CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN \
  -e DOMAINS=example.org,www.example.org,example.io \
  -e PROXIED=true \
  favonia/cloudflare-ddns:latest
🧬 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=example.org,www.example.org,example.io \
  PROXIED=true \
  go run github.com/favonia/cloudflare-ddns/cmd/ddns@latest

πŸ‹ Deployment with Docker Compose

πŸ“¦ Step 1: Updating the Compose File

Incorporate the following fragment into the compose file (typically docker-compose.yml or docker-compose.yaml). The template may look a bit scary, but only because it includes various optional flags for extra security protection.

services:
  cloudflare-ddns:
    image: favonia/cloudflare-ddns:latest
    # Choose the appropriate tag based on your need:
    # - "latest" for the latest stable version (which could become 2.x.y
    #   in the future and break things)
    # - "1" for the latest stable version whose major version is 1
    # - "1.x.y" to pin the specific version 1.x.y
    network_mode: host
    # This bypasses network isolation and makes IPv6 easier (optional; see below)
    restart: always
    # Restart the updater after reboot
    user: "1000:1000"
    # Run the updater with specific user and group IDs (in that order).
    # You can change the two numbers based on your need.
    read_only: true
    # Make the container filesystem read-only (optional but recommended)
    cap_drop: [all]
    # Drop all Linux capabilities (optional but recommended)
    security_opt: [no-new-privileges:true]
    # Another protection to restrict superuser privileges (optional but recommended)
    environment:
      - CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
        # Your Cloudflare API token
      - DOMAINS=example.org,www.example.org,example.io
        # Your domains (separated by commas)
      - PROXIED=true
        # Tell Cloudflare to cache webpages and hide your IP (optional)

(Click to expand the following important tips.)

πŸ”‘ 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. (The less secure API key authentication is deliberately not supported.)

  • To update only DNS records, use the Edit zone DNS template to create a token.
  • To update only WAF lists, choose Create Custom Token and then add the Account - Account Filter Lists - Edit permission to create a token.
  • To update DNS records and WAF lists, use the Edit zone DNS template and then add the Account - Account Filter Lists - Edit permission when creating the token.

You can also adjust the permissions of existing tokens at any time!

πŸ“ DOMAINS is the list of domains to update

The value of DOMAINS should be a list of fully qualified domain names (FQDNs) separated by commas. For example, DOMAINS=example.org,www.example.org,example.io instructs the updater to manage the domains example.org, www.example.org, and example.io. These domains do not have to share the same DNS zone---the updater will take care of the DNS zones behind the scene.

🚨 Remove PROXIED=true if you are not running a web server

The setting PROXIED=true instructs Cloudflare to cache webpages and hide your IP addresses. If you wish to bypass that and expose your actual IP addresses, remove PROXIED=true. If your traffic is not HTTP(S), then Cloudflare cannot proxy it and you should probably turn off the proxying by removing PROXIED=true. The default value of PROXIED is false.

πŸ“΄ Add IP6_PROVIDER=none if you want to disable IPv6 completely

The updater, by default, will attempt to update DNS records for both IPv4 and IPv6, and there is no harm in leaving the automatic detection on even if your network does not work for one of them. However, if you want to disable IPv6 entirely (perhaps to avoid seeing the detection errors), add IP6_PROVIDER=none.

πŸ“‘ Expand this if you want IPv6 without bypassing network isolation (without network_mode: host)

The easiest way to enable IPv6 is to use network_mode: host so that the updater can access the host IPv6 network directly. This has the downside of bypassing the network isolation. If you wish to keep the updater isolated from the host network, remove network_mode: host and follow the steps in the official Docker documentation to enable IPv6. Do use newer versions of Docker that come with much better IPv6 support!

πŸ›‘οΈ Change user: "1000:1000" to the user and group IDs you want to use

Change 1000:1000 to USER:GROUP for the USER and GROUP IDs you wish to use to run the updater. The settings cap_drop, read_only, and no-new-privileges in the template provide additional protection, especially when you run the container as a non-superuser.

πŸš€ Step 2: Building and Running the Container

docker-compose pull cloudflare-ddns
docker-compose up --detach --build cloudflare-ddns

❓ Frequently Asked Questions

(Click to expand the following items.)

❔ I simulated an IP address change by editing the DNS records, but the updater never picked it up!

Please rest assured that the updater is working as expected. It will update the DNS records immediately for a real IP change. Here is a detailed explanation. There are two causes of an IP mismatch:

  1. A change of your actual IP address (a real change), or
  2. A change of the IP address in the DNS records (a simulated change).

The updater assumes no one will actively change the DNS records. In other words, it assumes simulated changes will not happen. It thus caches the DNS records and cannot detect your simulated changes. However, when your actual IP address changes, the updater will immediately update the DNS records. Also, the updater will eventually check the DNS records and detect simulated changes after CACHE_EXPIRATION (six hours by default) has passed.

If you really wish to test the updater with simulated IP changes in the DNS records, you can set CACHE_EXPIRATION=1ns (all cache expiring in one nanosecond), effectively disabling the caching. However, it is recommended to keep the default value (six hours) to reduce your network traffic.

❔ How can I see the timestamps of the IP checks and/or updates?

The updater does not itself add timestamps because all major systems already timestamp everything:

❔ Why did the updater detect a public IP address different from the WAN IP address on my router?

Is your β€œpublic” IP address on your router between 100.64.0.0 and 100.127.255.255? If so, you are within your ISP’s CGNAT (Carrier-grade NAT). In practice, there is no way for DDNS to work with CGNAT, because your ISP does not give you a real public IP address, nor does it allow you to forward IP packages to your router using cool protocols such as Port Control Protocol. You have to give up DDNS or switch to another ISP. You may consider other services such as Cloudflare Tunnel that can work around CGNAT.

❔ How should I install this updater in Kubernetes?

Due to high maintenance, the instructions for Kubernetes were removed, but you can generate Kubernetes configurations from the provided Docker Compose template. One popular conversion tool is Kompose. (However, only the recent versions of Kompose support the user: "UID:GID" attribute with GID; see my own pull request that added this feature to Kompose.)

Note that you only need a Kubernetes Deployment. You do not need Kubernetes Services or open ports for inbound network traffic in general.

❔ Help! I got exec /bin/ddns: operation not permitted

Some Docker installations seem to have trouble with the extra security protection no-new-privileges. Chances are you cannot run any Docker image (not only this updater) with no-new-privileges. If that is the case, there is not much this updater can do, and the best known workaround is to remove security_opt: [no-new-privileges:true] completely. It will be slightly less secure, but better than not running at all. If only this updater does not run, please open a GitHub issue so that we can investigate the bug.

πŸŽ›οΈ Further Customization

βš™οΈ All Settings

The emoji β€œπŸ§ͺ” indicates experimental features and the emoji β€œπŸ€–β€ indicates technical details.

(Click to expand the following items.)

πŸ”‘ The Cloudflare API token

Exactly one of the following variables should be set.

Name Meaning
CF_API_TOKEN The Cloudflare API token to access the Cloudflare API
CF_API_TOKEN_FILE A path to a file that contains the Cloudflare API token to access the Cloudflare API
  • πŸ”‘ To update DNS records, the updater needs the Account - Account Filter Lists - Edit permission.
  • πŸ”‘ To manipulate WAF lists, the updater needs the Zone - DNS - Edit permission.
πŸ“ DNS domains and WAF lists to update

You need to specify at least one thing in DOMAINS, IP4_DOMAINS, IP6_DOMAINS, or πŸ§ͺ WAF_LISTS for the updater to update.

Name Meaning
DOMAINS Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for both A and AAAA records. Listing a domain in DOMAINS is equivalent to listing the same domain in both IP4_DOMAINS and IP6_DOMAINS.
IP4_DOMAINS Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for A records
IP6_DOMAINS Comma-separated fully qualified domain names or wildcard domain names that the updater should manage for AAAA records
πŸ§ͺ WAF_LISTS πŸ§ͺ Comma-separated references of WAF lists the updater should manage. A list reference is written in the format <account-id>/<list-name> where account-id is your account ID and list-name is the list name; it should look like 0123456789abcdef0123456789abcdef/mylist. If the referenced WAF list does not exist, the updater will try to create it. πŸ’‘ See how to find your account ID. πŸ§ͺ This feature to manipulate WAF lists is highly experimental (introduced in 1.14.0) and is subject to changes. Please open a GitHub issue to provide feedback. Thanks!

πŸƒπŸ€– Wildcard domains (*.example.org) represent all subdomains that would not exist otherwise. Therefore, if you have another subdomain entry sub.example.org, the wildcard domain is independent of it, because it only represents the other subdomains which do not have their own entries. Also, you can only have one layer of *---*.*.example.org would not work.

πŸŒπŸ€– Internationalized domain names are handled using the nontransitional processing (fully compatible with IDNA2008). At this point, all major browsers and whatnot have switched to the same nontransitional processing. See this useful FAQ on internationalized domain names.

πŸ€– Technical notes on WAF lists:

  1. Cloudflare does not allow single IPv6 addresses in a WAF list, and thus the updater will use the smallest IP range allowed by Cloudflare that contains the detected IPv6 address.
  2. The updater will delete IP addresses belonging to unmanaged IP families from the specified WAF lists (e.g., if you disable IPv6 with IP6_PROVIDER=none, then existing IPv6 addresses or IPv6 ranges in the lists will be deleted). The idea is that the list should contain only detected IP addresses.
πŸ” IP address providers
Name Meaning Default Value
IP4_PROVIDER This specifies how to detect the current IPv4 address. Available providers include cloudflare.doh, cloudflare.trace, local, url:<URL>, and none. The special none provider disables IPv4 completely. See below for a detailed explanation. cloudflare.trace
IP6_PROVIDER This specifies how to detect the current IPv6 address. Available providers include cloudflare.doh, cloudflare.trace, local, url:<URL>, and none. The special none provider disables IPv6 completely. See below for a detailed explanation. cloudflare.trace

πŸ‘‰ The option IP4_PROVIDER governs A-type DNS records and IPv4 addresses in WAF lists, while the option IP6_PROVIDER governs AAAA-type DNS records and IPv6 addresses in WAF lists. The two options act independently of each other. You can specify different address providers for IPv4 and IPv6.

πŸ“‘ Available IP address providers:

Provider Name Explanation
cloudflare.doh Get the IP address by querying whoami.cloudflare. against Cloudflare via DNS-over-HTTPS. πŸ€– The updater will connect 1.1.1.1 for IPv4 and 2606:4700:4700::1111 for IPv6. Since version 1.9.3, the updater will switch to 1.0.0.1 for IPv4 if 1.1.1.1 appears to be blocked or intercepted by your ISP or your router (which is still not uncommon). Since version 1.14.0, the blockage detection uses a variant of the Happy Eyeballs algorithm to reduce delay.
cloudflare.trace Get the IP address by parsing the Cloudflare debugging page. This is the default provider. πŸ€– The updater will connect 1.1.1.1 for IPv4 and 2606:4700:4700::1111 for IPv6. Since version 1.9.3, the updater will switch to 1.0.0.1 for IPv4 if 1.1.1.1 appears to be blocked or intercepted by your ISP or your router (which is still not uncommon). Since version 1.14.0, the blockage detection uses a variant of the Happy Eyeballs algorithm to reduce delay.
local Get the IP address via local network interfaces. When multiple local network interfaces or in general multiple IP addresses are present, the updater will use the address that would have been used for outbound UDP connections to Cloudflare servers. (No data will be transmitted.) ⚠️ You need access to the host network (such as network_mode: host in Docker Compose) for this policy, for otherwise the updater will detect the addresses inside the default bridge network in Docker instead of those in the host network.
url:<URL> Fetch the content at URL via the HTTP(S) protocol treat the content as the IP address. The provider format is url: followed by the URL. For example, IP4_PROVIDER=url:https://api4.ipify.org will fetch the IPv4 address from https://api4.ipify.org, a server maintained by ipify. 🐞 KNOWN ISSUE: Currently, the updater will not force IPv4 or IPv6 when retrieving the IPv4 or IPv6 address at URL. Therefore, for IP4_PROVIDER=url:URL, the updater might use IPv6 (instead of the expected IPv4) to connect to URL, and if the server returns an IPv6 address because of this, the updating will fail. The server at URL must either restrict its access to the expected IP network or return a valid IP address in the expected IP network regardless of what IP network is used for connection. As a working example, https://api4.ipify.org has restricted its access to IPv4, and thus it’s impossible to use the wrong IP network (IPv6) to connect to it. πŸ§ͺ This is a known issue and may be fixed in the future.
none Stop the DNS updating for the specified IP version completely. For example IP4_PROVIDER=none will disable IPv4 completely. Existing DNS records will not be removed. ⚠️ The IP addresses of the disabled IP version will be removed from WAF lists; so IP4_PROVIDER=none will remove all IPv4 addresses from all managed WAF lists. πŸ§ͺ As the support of WAF lists is experimental, this behavior is subject to changes and please provide feedback.
πŸ“… Scheduling of IP detections and updates
Name Meaning Default Value
CACHE_EXPIRATION The expiration of cached Cloudflare API responses. It can be any positive time duration accepted by time.ParseDuration, such as 1h or 10m. 6h0m0s (6 hours)
DELETE_ON_STOP Whether managed DNS records and WAF lists should be deleted on exit. It can be any boolean value accepted by strconv.ParseBool, such as true, false, 0 or 1. If a WAF list is used in a rule expression, the list cannot be deleted (for otherwise the rule expression would be broken), but the updater will try to remove all IP addresses from the list. false
TZ The timezone used for logging messages and parsing UPDATE_CRON. It can be any timezone accepted by time.LoadLocation, including any IANA Time Zone. πŸ€– The pre-built Docker images come with the embedded timezone database via the time/tzdata package. UTC
UPDATE_CRON The schedule to re-check IP addresses and update DNS records and WAF lists (if needed). The format is any cron expression accepted by the cron library or the special value @once. The special value @once means the updater will terminate immediately after updating the DNS records or WAF lists, effectively disabling the scheduling feature. πŸ€– The update schedule does not take the time to update records into consideration. For example, if the schedule is @every 5m, and if the updating itself takes 2 minutes, then the actual interval between adjacent updates is 3 minutes, not 5 minutes. @every 5m (every 5 minutes)
UPDATE_ON_START Whether to check IP addresses (and possibly update DNS records and WAF lists) immediately on start, regardless of the update schedule specified by UPDATE_CRON. It can be any boolean value accepted by strconv.ParseBool, such as true, false, 0 or 1. true
⏳ Timeouts of various operations
Name Meaning Default Value
DETECTION_TIMEOUT The timeout of each attempt to detect IP address, per IP version (IPv4 and IPv6). It can be any positive time duration accepted by time.ParseDuration, such as 1h or 10m. 5s (5 seconds)
UPDATE_TIMEOUT The timeout of each attempt to update DNS records, per domain and per record type, or per WAF list. It can be any positive time duration accepted by time.ParseDuration, such as 1h or 10m. 30s (30 seconds)
🐣 Parameters of new DNS records and WAF lists

πŸ‘‰ The updater will preserve existing parameters (TTL, proxy states, DNS record comments, etc.). Only when it creates new DNS records and new WAF lists, the following settings will apply. To change existing parameters, you can go to your Cloudflare Dashboard and change them directly. If you think you have a use case where the updater should actively overwrite existing parameters in addition to IP addresses, please let me know. 🐞πŸ§ͺ KNOWN ISSUE: existing comments attached to stale WAF list items (not WAF lists themselves) will not be transferred to new list items for new IP addresses because the Cloudflare API does not provide an easy way to update them. The comments will be lost when the updater deletes stale list items and create new ones.

Name Meaning Default Value
PROXIED Whether new DNS records should be proxied by Cloudflare. It can be any boolean value accepted by strconv.ParseBool, such as true, false, 0 or 1. πŸ§ͺ It can also be a domain-dependent boolean expression as described below. false
TTL The time-to-live (TTL) (in seconds) of new DNS records. 1 (This means β€œautomatic” to Cloudflare)
RECORD_COMMENT The record comment of new DNS records. ""
πŸ§ͺ WAF_LIST_DESCRIPTION πŸ§ͺ The text description of new WAF lists. ""

πŸ€–πŸ§ͺ For advanced users: the PROXIED can be a boolean expression involving domains! This allows you to enable Cloudflare proxying for some domains but not the others. Here are some example expressions:

  • PROXIED=is(example.org): proxy only the domain example.org
  • PROXIED=is(example1.org) || sub(example2.org): proxy only the domain example1.org and subdomains of example2.org
  • PROXIED=!is(example.org): proxy every managed domain except for example.org
  • PROXIED=is(example1.org) || is(example2.org) || is(example3.org): proxy only the domains example1.org, example2.org, and example3.org

A boolean expression must be one of the following forms (all whitespace is ignored):

Syntax Meaning
Any string accepted by strconv.ParseBool, such as true, false, 0, or 1 Logical truth or falsehood
is(d) Matching the domain d. Note that is(*.a) only matches the wildcard domain *.a; use sub(a) to match all subdomains of a (including *.a).
sub(d) Matching subdomains of d, such as a.d, b.c.d, and *.d. It does not match the domain d itself.
! e Logical negation of the boolean expression e
e1 || e2 Logical disjunction of the boolean expressions e1 and e2
e1 && e2 Logical conjunction of the boolean expressions e1 and e2

One can use parentheses to group expressions, such as !(is(a) && (is(b) || is(c))). For convenience, the parser also accepts these short forms:

Short Form Equivalent Full Form
is(d1, d2, ..., dn) is(d1) || is(d2) || ... || is(dn)
sub(d1, d2, ..., dn) sub(d1) || sub(d2) || ... || sub(dn)

For example, these two settings are equivalent:

  • PROXIED=is(example1.org) || is(example2.org) || is(example3.org)
  • PROXIED=is(example1.org,example2.org,example3.org)
πŸ‘οΈ Message logging options
Name Meaning Default Value
EMOJI Whether the updater should use emojis in the logging. It can be any boolean value accepted by strconv.ParseBool, such as true, false, 0 or 1. true
QUIET Whether the updater should reduce the logging. It can be any boolean value accepted by strconv.ParseBool, such as true, false, 0 or 1. false
πŸ“£ External notifications (Healthchecks, Uptime Kuma, and shoutrrr)

πŸ§ͺ The integration with shoutrrr is still somewhat experimental (introduced in 1.12.0).

Name Meaning
HEALTHCHECKS The Healthchecks ping URL to ping when the updater successfully updates IP addresses, such as https://hc-ping.com/<uuid> or https://hc-ping.com/<project-ping-key>/<name-slug> ⚠️ The ping schedule should match the update schedule specified by UPDATE_CRON. πŸ€– The updater can work with any server following the same notification protocol, including but not limited to self-hosted instances of Healthchecks. Both UUID and Slug URLs are supported, and the updater works regardless whether the POST-only mode is enabled.
UPTIMEKUMA The Uptime Kuma’s Push URL to ping when the updater successfully updates IP addresses, such as https://<host>/push/<id>. You can directly copy the β€œPush URL” from the Uptime Kuma configuration page. ⚠️ Remember to change the β€œHeartbeat Interval” to match the update schedule specified by UPDATE_CRON.
πŸ§ͺ SHOUTRRR πŸ§ͺ A list of notifications services the updater should send messages to when it updates IP addresses. The format is newline-separated shoutrrr URLs, such as discord://<token>@<id>.

⚠️ Please note that a failure in handling IPv6 will cause the status to be reported as down even if IPv4 records are updated successfully (and similarly if IPv6 works but IPv4 fails). If your network does not support IPv6, add IP6_PROVIDER=none to disable IPv6 completely.

πŸ”‚ Restarting the Container

If you are using Docker Compose, run docker-compose up --detach to reload settings.

🚡 Migration Guides

(Click to expand the following items.)

I am migrating from oznu/cloudflare-ddns (now archived)

⚠️ oznu/cloudflare-ddns relies on the insecure DNS protocol to obtain public IP addresses; a malicious hacker could more easily forge DNS responses and trick it into updating your domain with any IP address. In comparison, we use only verified responses from Cloudflare, which makes the attack much more difficult. See the design document for more information on security.

Old Parameter Note
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 (PROXIED=true)
RRTYPE=A βœ”οΈ Both IPv4 and IPv6 are enabled by default; use IP6_PROVIDER=none to disable IPv6
RRTYPE=AAAA βœ”οΈ Both IPv4 and IPv6 are enabled by default; use IP4_PROVIDER=none to disable IPv4
DELETE_ON_STOP=true βœ”οΈ Same (DELETE_ON_STOP=true)
INTERFACE=iface βœ”οΈ Not required for local providers; we can handle multiple network interfaces
CUSTOM_LOOKUP_CMD=cmd ❌ There are no shells in the minimal Docker image
DNS_SERVER=server ❌ Only Cloudflare is supported, except the url:URL provider via HTTP(S)
I am migrating from timothymiller/cloudflare-ddns
Old JSON Key Note
cloudflare.authentication.api_token βœ”οΈ Use CF_API_TOKEN=key
cloudflare.authentication.api_key ❌ Please use the newer, more secure API tokens
cloudflare.zone_id βœ”οΈ Not needed; automatically retrieved from the server
cloudflare.subdomains[].name βœ”οΈ Use DOMAINS with fully qualified domain names (FQDNs) directly; for example, if your zone is example.org and your subdomain is sub, use DOMAINS=sub.example.org
cloudflare.subdomains[].proxied βœ”οΈ Write boolean expressions for PROXIED to specify per-domain settings; see above for the detailed documentation for this experimental feature
load_balancer ❌ Not supported yet; please make a request if you want it
a βœ”οΈ Both IPv4 and IPv6 are enabled by default; use IP4_PROVIDER=none to disable IPv4
aaaa βœ”οΈ Both IPv4 and IPv6 are enabled by default; use IP6_PROVIDER=none to disable IPv6
proxied βœ”οΈ Use PROXIED=true or PROXIED=false
purgeUnknownRecords ❌ The updater never deletes unmanaged DNS records

πŸ“œ Some historical notes: This updater was originally written as a Go clone of the Python program timothymiller/cloudflare-ddns because the Python program always purged unmanaged DNS records back then and it was not configurable via environment variables. There were feature requests to address these issues but the author timothymiller seemed to ignore them; I thus made my Go clone after unsuccessful communications. Understandably, timothymiller did not seem happy with my cloning and my other critical comments towards other aspects of the Python updater. Eventually, an option purgeUnknownRecords was added to the Python program to disable the unwanted purging, and it became configurable via environment variables, but my Go clone already went on its way. I believe my Go clone is now a much better choice, but my opinions are biased and you should check the technical details by yourself. πŸ˜‰

πŸ’– Feedback

Questions, suggestions, feature requests, and contributions are all welcome! Feel free to open a GitHub issue.