DNS publishing
When you publish a service as vault.example.com, something has to turn that name into the address of one of your nodes. Clouduler can do this for you: it writes the A, AAAA and CNAME records into your Cloudflare zone and keeps them correct as services move, nodes fail and stacks are removed.
It only ever deletes records it created itself. Everything else in your zone is left alone.
How it works
- Desired records. From the replicated state, Clouduler works out which hostnames exist (stack routes and the dashboard) and which node addresses each should point at.
- On a subnetted network (internal or private) the record is an A or AAAA pointing at the node's IP on that network, with TTL 1 (automatic) and not proxied by Cloudflare.
- On a public network the records come from the node's public address block:
ipv4becomes an A record,ipv6an AAAA record,domaina CNAME.
- Which nodes. The
proxy.dns_publishmode decides which nodes are listed (see below). - Reconcile. Every 60 seconds the leader compares the desired records with the live zone. It deletes records it owns that are no longer wanted, creates missing ones, then updates changed ones. A "Reconcile DNS" button runs it immediately.
- Ownership tag. For each name Clouduler also writes a TXT record at
_clouduler.<fqdn>with content like"cluster=homelab;key=...", and sets the record comment tomanaged by clouduler (homelab). Only records carrying the tag are ever deleted. - Zone matching. The longest configured zone that covers a hostname is used. A hostname outside every zone is skipped with the status
no cloudflare zone covers this hostname.
DNS publishing and certificate issuance share the same Cloudflare token and zone list. See Proxy and TLS.
Set it up
- Web UI
- CLI / config
- Create a Cloudflare API token (guide).
- Open Settings > Network and the Cloudflare DNS page. Switch on Manage DNS with Cloudflare, list your DNS zones, paste the API token and press Save token.
- On Settings > Network > Networks, set a DNS domain on each network that serves routes. Leave Publish to DNS provider (Cloudflare) on for the networks whose names you want in Cloudflare.
- Apply. Open DNS (
/dns) to watch the records.
The DNS records panel lists every record Clouduler manages with its state. Reconcile DNS runs a pass now. Copy fingerprint under TLS fingerprint copies the dashboard certificate fingerprint you need for clouduler init --join.
cloudflare:
zones:
- example.com
networks:
wan:
type: public
domain: example.com
lan:
type: private
subnet: 192.168.1.0/24
domain: lan.example.com
publish_dns: true # default; false keeps this network's names out of Cloudflare
proxy:
dns_publish: all-nodes # or owning-node
clouduler config reload
clouduler status # "certificates & dns" section lists pending and failed records
The token has no CLI command; set it in the UI.
Publish modes
proxy.dns_publish has two values. It has no control in the web UI, so edit cluster.yaml and run clouduler config reload.
| Mode | Records published for a route | Trade-off |
|---|---|---|
all-nodes (default) | Every node that serves the route gets an A or AAAA record, whether or not it runs the container. | Simple and resilient: any node can proxy the request to wherever the container is. More records, and traffic takes an extra hop through the cluster network. |
owning-node | Only the nodes that actually run the container. Stale records are deleted. | Clients connect straight to the node that runs the service. Requires the co-location rule below. |
The co-location constraint
With owning-node, all services that share a proxy hostname must run on the same node, so there is one clear owner. The scheduler enforces this. If it cannot place a service on the same node as the others it shares a host with, placement fails with:
must share a node with {keys} already placed on {anchors}
Pins, constraints, drives or capacity that rule out that node make the stack unplaceable. See Placement. Switch back to all-nodes if you want services with a shared hostname to spread across nodes.
Switching modes is safe: records that are no longer wanted are deleted first on the next reconcile pass (every 60 seconds, leader only). Switching to owning-node deletes the records of nodes that do not run the service; switching back creates them again.
Reconcile behaviour
- Prefers an existing match. When a live record already matches a desired one, Clouduler keeps it instead of picking an arbitrary candidate. This avoids a bug where duplicate-content errors blocked deletes forever.
- Hand-made records are left alone. An identical record you created yourself is noted as unclaimed and not touched. Records Clouduler does not own are never deleted.
- CNAME exclusivity. A CNAME cannot share a name with other records. If both are wanted, the address record wins over the CNAME. Conflicting records that Clouduler owns are deleted.
- Order. Deletes first, then creates, then updates. Benign duplicate errors from Cloudflare (codes 81057, 81044, 81058) are treated as "already there".
- Error attribution. A failure that affects the whole zone (for example bad credentials) is shown against the zone. A per-record failure is shown against the affected hostnames only.
- The dashboard hostname is published from the leader.
- Retries. The Cloudflare client makes at most 5 attempts (4 retries, with exponential backoff) on 429 and 5xx responses.
Names for clients without Cloudflare
If you do not use Cloudflare, or set publish_dns: false on a network, nothing is written to a public zone. Your clients still need a way to resolve vault.lan.example.com. Options: your own DNS, /etc/hosts, or Clouduler's built-in cluster DNS server.
- Web UI
- CLI / config
Open Settings > Network > Reverse proxy. Switch on Run a cluster DNS server, tick the DNS server networks to listen on, and optionally change the DNS server port (default 53). Then point your router or clients at a node's address on one of those networks.
proxy:
dns_server:
enabled: true
networks: [lan] # empty means every subnetted network the node has an address on
port: 53
The server answers according to the network the client is on. A client on a subnetted network gets the node's own LAN address for a public hostname (so LAN traffic does not hairpin through your router), and names the cluster does not own are forwarded upstream. It returns REFUSED to clients outside the configured networks, and for names it does not own to clients outside a subnetted network, so a public listener cannot be used as an open resolver. networks defaults to every subnetted network. It re-reads the config every 5 seconds. Ports below 1024 need root, which the daemon has. Remember to open port 53 on the nodes' firewall for your LAN (see Ports and firewall).
Options
enumdefault all-nodesbooldefault falselist of network namesdefault [] (every subnetted network the node has an address on)portdefault 53booldefault trueobjectdefault absentlist of zone namesno defaultTroubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Record never appears | Hostname outside every cloudflare.zones entry, or the network has no domain. | Check DNS status for no cloudflare zone covers this hostname. Add the zone. |
cloudflare auth failed: ... | Bad or under-privileged token. The token is not verified when saved. | Rotate the token under Settings > Network > Cloudflare DNS. It needs Zone, DNS and Edit permissions. |
zone X not found | The zone name does not exist in the account behind the token. | Fix the zone list or token. |
cloudflare api error CODE: MESSAGE | Cloudflare rejected a record. | Read the code; several failures are summarised as N records failed to apply: .... |
this node holds no cluster key when saving a token | The node you are using has no cluster key. | Use a node that holds the key. |
| Old records remain | They were not created by Clouduler, or the tag is missing. | Delete them in Cloudflare yourself. |
Placement fails with must share a node with ... | owning-node co-location. | Free the anchor node or switch to all-nodes. |
| A name resolves to the wrong place | Another record (CNAME or hand-made) conflicts. | Remove or fix the foreign record, then press Reconcile DNS. |