Skip to content


GitHub /


Starting the container

docker run --rm \
    --name cloudflareddns \
    -e PUID=1000 \
    -e PGID=1000 \
    -e UMASK=002 \
    -e TZ="Etc/UTC" \
    -e INTERVAL=300 \
    -e DETECTION_MODE="dig-whoami.cloudflare" \
    -e LOG_LEVEL=3 \
    -e CF_USER="" \
    -e CF_APIKEY="" \
    -e CF_APITOKEN="" \
    -e CF_APITOKEN_ZONE="" \
    -e CF_HOSTS=";;" \
    -e CF_ZONES=";;" \
    -v /<host_folder_config>:/config \
version: "3.7"

    container_name: cloudflareddns
      - PUID=1000
      - PGID=1000
      - UMASK=002
      - TZ=Etc/UTC
      - INTERVAL=300
      - DETECTION_MODE=dig-whoami.cloudflare
      - LOG_LEVEL=3
      - /<host_folder_config>:/config

Possible values for DETECTION_MODE are,, dig-whoami.cloudflare,,,,, and If you want to get the local ip from a network interface, use something like local:eth0 as DETECTION_MODE.

Notice that we give 3 values each time for CF_HOSTS, CF_ZONES and CF_RECORDTYPES. In our example, the domain belonging to the zone will have its A record updated with an ipv4 ip. If you use CF_APITOKEN, you can leave CF_USER and CF_APIKEY empty.


All the domain names in CF_HOSTS should have properly configured DNS records on Cloudflare, they will not be created.


Tag Description Version Commit SHA Commit Message Last Updated Age


The above table reflects the current state of the Git repository. The docker repository is mainly in sync, unless builds fail or commits are pushed for which no builds are started (usually indicated with [skip ci] in the commit message). You can click on the tag's name to go to its corresponding branch on GitHub, clicking the commit sha brings you to the git diff of that commit.

Zone ID

Instead of the zone_name, you can also fill in a zone_id in CF_ZONES. When using a zone_id, you can use a scoped token (CF_APITOKEN) that only needs the Zone - DNS - Edit permissions. This improves security. The configuration could look like the example below.

-e CF_APITOKEN="azkqvJ86wEScojvSJC8DyY67TwqNwZCtomEVrHwt"
-e CF_HOSTS=";"
-e CF_ZONES="zbpsi9ceikrdnnym27s2xnp6s5dvj6ep;dccbe6grakumohwwd4amh4o46yupepn8"

Seperate API Tokens

If you do not prefer to use a zone_id, but prefer some more security, you can use 2 seperate tokens.

CF_APITOKEN configured with:

Zone - DNS - Edit
Zone Resources
Include - Specific zone -
Include - Specific zone -

CF_APITOKEN_ZONE configured with:

Zone - Zone - Read
Zone Resources
Include - All zones

Leaving CF_APITOKEN_ZONE blank would mean that only CF_APITOKEN will be used and thus that token should have all required permissions. Which usually means that the token could edit all zones or not be able to fetch the zone_id from the zone_name.

Configuration combination examples

Below are some example configuration combinations, ordered from most secure to least secure.

  • We use a zone_id so that our token only needs the permissions Zone - DNS - Edit.
-e CF_APITOKEN="azkqvJ86wEScojvSJC8DyY67TwqNwZCtomEVrHwt"
-e CF_HOSTS=";"
-e CF_ZONES="zbpsi9ceikrdnnym27s2xnp6s5dvj6ep;axozor886pyja7nmbcvu5kh7dp9557j4"
  • We use additionally a CF_APITOKEN_ZONE with the permissions Zone - Zone - Read to query the zones and getting the zone_id.
-e CF_APITOKEN="azkqvJ86wEScojvSJC8DyY67TwqNwZCtomEVrHwt"
-e CF_APITOKEN_ZONE="8m4TxzWb9QHXEpTwQDMugkKuHRavsxoK8qmJ4P7M"
-e CF_HOSTS=";"
-e CF_ZONES=";axozor886pyja7nmbcvu5kh7dp9557j4"
  • We use only CF_APITOKEN, but with the permissions Zone - DNS - Edit and Zone - Zone - Read.
-e CF_APITOKEN="azkqvJ86wEScojvSJC8DyY67TwqNwZCtomEVrHwt"
-e CF_HOSTS=";"
-e CF_ZONES=";axozor886pyja7nmbcvu5kh7dp9557j4"
  • We use CF_USER and CF_APIKEY, basically giving full control over our account.
-e CF_USER=""
-e CF_HOSTS=";"
-e CF_ZONES=";axozor886pyja7nmbcvu5kh7dp9557j4"

Example of the log output

2020-05-17 17:20:54 -    INFO - IPv4 detected by [dig-whoami.cloudflare] is [].
2020-05-17 17:20:54 -    INFO - [1/1] [A] [] Reading zone list from Cloudflare.
2020-05-17 17:20:54 -    INFO - [1/1] [A] [] Retrieved zone list from Cloudflare.
2020-05-17 17:20:54 -    INFO - [1/1] [A] [] Zone ID [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] found for zone [].
2020-05-17 17:20:54 -    INFO - [1/1] [A] [] Reading DNS record from Cloudflare.
2020-05-17 17:20:55 -    INFO - [1/1] [A] [] Writing DNS record to cache file [/config/].
2020-05-17 17:20:55 -    INFO - [1/1] [A] [] Checking if update is needed.
2020-05-17 17:20:55 -    INFO - [1/1] [A] [] No update needed.
2020-05-17 17:20:55 -    INFO - Going to sleep for [300] seconds...

Log levels

For LOG_LEVEL you can pick 0, 1, 2 or 3.

  • 0 will give no log output. It's not recommended to use.

  • 1 will give you the following output types. It's the recommended value when all things are configured and running as expected.

  • 2 will give you the following output types. Use this if you always wanna see what's going on, but 3 gives you too much output.
  • 3 will give you the following output types. This is the default.

JSON log

Every IP update is also logged to /config/cf-ddns-updates.json. This can be used with the Telegraf JSON parser and the tail input, to get your domain updates into InfluxDB. Example output below.


Cached results from Cloudflare

The returned results from Cloudflare are cached. This means minimal api calls to Cloudflare. If you have made any manual changes to the IP on the Cloudflare webinterface, for instance when wanting to test an update, a container restart is needed to clear the cache.

The proxy setting (orange cloud) and TTL is also cached and re-set based on the previous value, so if you made any modifications to these settings, you should restart the container so that the script is aware of the new settings.

Sending notifications

You can send notifications when a DNS record gets updated with a new IP using Apprise. Use the environment variable APPRISE to configure notifications, see below for some examples.

-e APPRISE="pover://user@token"
-e APPRISE="pover://user@token;discord://webhook_id/webhook_token"