Dynamic DNS with Cloudflare and curl

I have a homelab (a.k.a. a bunch of servers at home).

My internet plan gets me a sticky dynamic IP address (hey, at least it's IPv4). Dynamic IP addressing is great for ISPs, but us homelabbers hate it, because it means our uptime, depends on users, and their configured DNS servers.

There are some services I want to host, but due to their nature, I can't justify the cost of running them on the cloud. They don't require excellent uptime, so hosting them at home, makes sense.

That means: I have to set up dynamic DNS!


My DNS provider for is Cloudflare. I want my services to be available at, so I need to setup dynamic DNS with Cloudflare.

Fortunately, Cloudflare offers a great API for it's users, so updating an existing record is a simple HTTP request.

The script

I wrote a simple bash script with curl that regurarly checks my public IP address with, and, if it changed, sends and authenticated HTTP PUT request to Cloudflare and updates the record.

The script is available on GitHub Gist.

Bonus: running it on Docker

One of the servers in my homelab runs a Docker Swarm. Swarm is great because I can write out my stack's configuration in a single Compose file (see Infrastructure as Code) and deploy to a well-known environment.

I run Swarm single-node, you can run multi-node swarms, but for multi-node deployments check out Kubernetes.

The image

To run this script I needed a Docker image with sh/bash and curl, therefore, three options:

Instead of building an image just with bash and curl, what I ended up doing is using the alpine image (which includes sh), installing curl during runtime and running the script after that.

The stack file

I run one service per sub-domain, which may sound crazy but this image runs on just 3MB of memory and I'll probably never have more than 10-15 DNS'd services ever.

version: '3.4'

    image: alpine
    command: sh -c 'apk add curl; sh /'
      - DOMAIN=...
      - ZONE_ID=...
      - DOMAIN_ID=...
      - CLOUDFLARE_KEY=...
      - /path/to/
        failure_action: rollback
        order: start-first
          cpus: '0.05'
          memory: 10M