Git backup
Git backup mirrors the cluster's configuration into a Git repository you own. If every node is lost, you can rebuild the cluster from that repository. It also lets you edit stacks in Git and have the cluster pick the change up.
It backs up configuration, not your data. For drive data, see Backups.
What is exported
The leader pushes after every change, to the branch you configure:
| Path in the repository | Content |
|---|---|
cluster.yaml | The cluster config, plaintext. |
stacks/<name>.yaml | Each stack spec, plaintext. |
configs/<name> | Each config, plaintext. |
secrets/<name>.age | Each secret, encrypted with age to your recipient key. |
.clouduler-secret-revisions.json | Bookkeeping. |
Only the cluster config, stacks, configs and secrets are exported. Backup destinations and schedules are not. Export runs on the leader only and pulls first; a commit that fails validation is parked on a branch named rejected/<branch>-<sha12>. The deploy key is a reserved secret named _clouduler_git_deploy_key.
Nothing is committed when nothing changed. Files in the repository that Clouduler does not manage are kept. Keep the repository private: configs and stack files are readable by anyone with access.
Secrets are encrypted to a key whose private half you keep. Clouduler never stores the age identity, so without it the secret values in Git cannot be recovered (stack and config files still can).
Set it up
- Web UI
- CLI / config
Open Settings, Sign-in and backup (/settings/security), page Git backup.
- Tick Back up cluster state to git.
- Repository URL: SSH form, for example
[email protected]:ops/clouduler-state.git. HTTP(S) is rejected. - Encrypt secrets for: paste an age public key (
age1...), or click Generate age key. The browser creates a key pair and downloadsclouduler-age-key.txt(the private key never leaves your browser). Store that file safely, outside the cluster. - Branch (default
main). - Save. A deploy key public key appears; click Copy public key and add it to the repository as a deploy key with write access.
- Wait for the first export. Last export shows success or
Last export failed: ....
backup:
age_recipient: age1example0000000000000000000000000000000000000000000000000
branch: main
clouduler config reload
The key is backup: and its three fields sit directly under it. A leftover interval key from older configs is accepted and ignored. Generate an age key pair yourself with age-keygen.
The deploy key is generated by the leader on the first export and stored as a reserved secret, so the first push fails until you add it to the repository.
Webhooks and pull
Git is also an input. When you push a change to the branch, the cluster can pull it, validate it and apply it. The pull is triggered by a webhook, or by hand.
- Web UI
- CLI / config
On the same page, set a webhook token and click Save webhook token. Configure your Git host to POST to /api/git-backup/webhook/<token>. The UI shows the URL pattern. Make sure that address is reachable from the Git host; see the git webhooks guide.
The UI has no buttons for export now or pull now.
clouduler git-backup pull # pull from git now
clouduler git-backup pull --via boreas
clouduler git-backup reconcile # rebuild the repo from cluster state, commit and push
The webhook accepts any request with the right token (no event filtering, no signature). A wrong token returns 401 bad webhook token. The token is in the URL, so it can end up in the access logs of any reverse proxy in front of the dashboard.
The webhook is part of the dashboard API, so it is reachable wherever the dashboard is. With the default web.through_proxy: true, that is the dashboard hostname on the web.network you chose, plus the direct dashboard port (8443 by default) on every address of the node. A Git host on the internet can only reach it if the dashboard is on a public network or that port is forwarded to it. The daemon's own log does not record request URLs at the default log level.
What a pull does
- It applies a changed
cluster.yaml(through the same generation check as other config edits, so a stale pull is rejected), redeploys only stacks whose spec changed, removes stacks no longer in Git, and updates or removes non-reserved configs. - It validates first and rejects the commit otherwise, moving it to a branch named
rejected/<branch>-<sha12>:no cluster.yaml in the pulled treethe pulled tree has no stacks/ at all, which would remove all N stack(s) this cluster runs - remove stacks through the api, not by emptying the repostacks/NAME.yaml: config C has no content under configs/secret S does not already exist in the cluster - git cannot supply new secret content, create it through the api first- a commit that changes anything under
secrets/:... secret rotation must go through the api, not git
- Secret values never come from Git. Rotate them in the UI or with
clouduler secret set.
Export is leader-only and runs a pull first, so it does not overwrite a manual push. A new leader builds its first export on top of the remote branch, not on an empty history. Older versions had races here that redeployed every stack on each pull and reverted manual pushes; they are fixed.
Disaster recovery
If the cluster's content is gone from every node, rebuild it from the repository. Clouduler's own deploy key is stored in the lost state, so clone the repository yourself first.
sudo systemctl stop clouduler.service # the daemon must be stopped
sudo clouduler restore --age-identity-file ~/clouduler-age-key.txt \
--branch main --yes ~/clouduler-state
sudo systemctl start clouduler.service
restore takes the repository path (or an ssh:///git@ URL, cloned with your SSH agent). Use --age-identity-stdin instead of the file to pipe the identity in. The identity is read once, used in memory, and each secret is re-sealed under this node's cluster key. Secrets that cannot be decrypted are listed as skipped. restore refills content only; it does not repair quorum. If the cluster also lost its managers, run emergency mode first. Then restore your data from Backups.
Options
backup.repo_urlrequiredstringno defaultbackup.age_recipientrequiredage public keyno defaultstringdefault mainpath / flagno defaultnode namedefault any manager