Backup and disaster recovery

Back up the small set of state the controller cannot reconstruct, then rebuild a
lost cluster by restoring that state in the right order. Most of what a StageSet
manages is recomputed on the next reconcile from your Git repository and your Flux
sources, so it needs no backup of its own. Three things are the exception, and
losing any of them causes silent data damage rather than a clean failure.
What the controller rebuilds, and what it cannot
A StageSet is a function of its source content: given the same spec and the same
artifacts, a reconcile re-fetches, re-renders, and re-applies the same objects.
The applied workloads, the StageSet status, the webhook’s self-signed CA, and the
leader lease are all reconstructed automatically. Four pieces of state need
backup attention — the first self-heals for the common case, the next two are
unrecoverable if lost, and the fourth applies only when a stage uses a once-ever
action:
StageInventoryresources — the record of exactly which objects each stage owns, read to prune and to tear down. If it is lost while the stage’s objects are still live, the controller self-heals: the next reconcile rebuilds the inventory from the objects’ owner and per-stage labels (across the GVKs the current render touches), emits anInventoryReconstructedevent, and defers pruning that pass so it never deletes against a best-effort rebuild; the following reconcile prunes normally. The residual gap is a kind the current render no longer contains at all — those objects still carry the labels but aren’t swept, so they stay orphaned. Back up the inventory so that gap, and reverse-order teardown, stay correct. It lives inspecprecisely so a spec-restoring backup tool captures it.- Encryption key Secrets — the age or PGP private keys named by
spec.decryption.secretRef. They are unrecoverable if lost. A rollback that must re-decrypt a prior revision fails closed with reasonPreviousRevisionUnavailable, and any new run that decrypts SOPS files fails until the keys are restored. Cloud-KMS keys live outside the cluster; back up the KMS key policy or ARN instead of the key material. - Rollback store contents — when
spec.rollbackOnFailure: trueand the producer retains only one revision, the rollback store (a RWXPersistentVolumeor an S3 bucket) holds the sole copy of the last-good rendered output. It is encrypted at rest because it contains decrypted Secrets. StageLedgercompletions — when a stage uses ascope: Lifetimeaction, its once-ever completion lives in aStageLedger. Entries the operator asserted viaspec.baselinecome back from Git; entries the controller ran itself (origin: Executed) live only in etcd. A cluster rebuilt from Git alone would re-run that bootstrap against state that survived the disaster — an external database, an object store — so export the completions to a committable baseline as part of your backup routine (below).
State inventory
| Component | Source of truth | Regeneratable? | Back up? |
|---|---|---|---|
StageSet spec | your Git repository / Flux source | yes — re-applied by Flux | through the GitOps repo |
StageSet status | rebuilt by reconcile | yes — but status.lastAppliedSnapshot is the rollback target | via an etcd snapshot |
StageInventory | the controller (cluster state only) | best-effort self-heal from live objects | recommended (covers the residual-GVK gap + teardown) |
StageLedger | spec.baseline in Git + controller-run status in etcd | partly — Baselined entries from Git; Executed entries need an export or etcd snapshot | yes if any stage uses scope: Lifetime |
| Encryption key Secrets | spec.decryption.secretRef | no — unrecoverable | yes (critical) |
| Rollback store | RWX PVC or S3 bucket | no when it is the only copy | yes if rollbackOnFailure is used |
| Applied workloads | stage sources | yes — re-applied from sources | no |
| Webhook self-signed CA | the controller | yes — regenerated on startup | no |
| Leader lease | the controller | yes — re-elected | no |
What to back up
Your GitOps repository. It holds every
StageSetspec and the Flux source objects (GitRepository,OCIRepository,Bucket,ExternalArtifactproducers) the stages reference. This is the primary source of truth — keep it backed up the way you back up any Git repository.StageInventoryresources. These are cluster state, not in Git. Capture them with a cluster backup tool (for example Velero) or an etcd snapshot. Because the inventory deliberately lives inspec— notstatus— any backup tool that restoresspeccaptures the prune history. The relevant scope is everyStageInventoryin eachStageSet’s namespace.Encryption key Secrets. Back up the Secrets named by
spec.decryption.secretRefin eachStageSet’s namespace. For a cloud-KMS-only setup, back up the KMS key policy and ARN rather than key material.Rollback-store contents and credentials, if
spec.rollbackOnFailure: true. Snapshot the RWX volume or replicate the S3 bucket, and back up the bucket credentials Secret. Skip this if every producer retains more than one revision — rollback then re-fetches from the producer and the store is only a fast path.StageLedgercompletions, if any stage usesscope: Lifetime. Commit the ledger’sspec.baselinealongside itsStageSet, and export the controller-run completions periodically so a Git rebuild does not re-run a bootstrap whose effect survived the disaster:stagesetctl baseline <name> --export > <name>-ledger.yamlOn restore, apply the exported ledger before the
StageSetreconciles (below). Its entries return asorigin: Baselined— the guarantee survives, the provenance downgrades honestly to “we assert this ran”.
Rebuilding a cluster
Restore in the order below. The order matters: the encryption keys and the
inventory must exist before the controller reconciles a StageSet, or the first
reconcile does damage.
- Install the controller from the Helm chart. See Production for the hardened install.
- Restore the encryption key Secrets. Keys must exist before any reconcile,
because a stage that decrypts SOPS files fails until its
secretRefSecret is present, and arollbackOnFailurerestore that re-decrypts a prior revision fails withPreviousRevisionUnavailablewithout them. See Secrets encryption . - Restore the rollback-store credentials and contents if you use one, so a run that fails immediately after rebuild can still restore its last-good output. See Rollback .
- Restore or re-sync the Flux sources. Let Flux reconcile the
GitRepository/OCIRepository/Bucketobjects and the producers so each stage’sExternalArtifactbecomesReadyagain. - Restore the
StageSetresources — normally by letting Flux apply them from your GitOps repository. - Restore the
StageInventoryresources before the controller reconciles theStageSets, so the first reconcile can prune correctly and a later deletion can tear down. Restoring the capturedspecis sufficient — that is where the prune history lives. If aStageSetreconciles before its inventory is back, the run applies and verifies but cannot prune; objects a newer revision dropped are left orphaned until you restore the inventory and reconcile again. - Restore the
StageLedgerresources if any stage usesscope: Lifetime— apply the exported ledger (orstagesetctl baseline <name> --stage S --action Aper completion) before theStageSetreconciles. Otherwise the first reconcile sees no completion and re-runs the once-ever bootstrap against state that survived the disaster.
If you suspend each StageSet (spec.suspend: true) before step 5 and resume only
after step 7, you remove the race entirely.
Failure modes
| Lost item | What happens |
|---|---|
StageInventory | the next reconcile applies and verifies, but the prune step has nothing to diff against, so removed objects are orphaned; StageSet deletion cannot reverse-order tear stages down |
| Encryption key Secret | new runs that decrypt SOPS files fail; a rollbackOnFailure restore that must re-decrypt fails with reason PreviousRevisionUnavailable |
| Rollback store | a rollback falls back to the producer’s retained revisions; with single-revision retention there is nothing to restore and the rollback fails |
Verifying recovery
After a rebuild, confirm the controller is back in a healthy steady state:
# Every StageSet reports Ready=True with reason Succeeded.
kubectl --namespace payments get stagesets
# The inventory is present for each stage.
kubectl --namespace payments get stageinventories
Then confirm pruning works end to end: change a stage’s source so it renders one
fewer object, let the StageSet reconcile, and check the dropped object is gone. A
correct prune proves the restored inventory is intact — the controller’s own
disaster-recovery test
pins exactly this guarantee: with
the inventory restored, a removed object is pruned; with the inventory lost, the
same removed object is orphaned.
Related
- Inventory and pruning
— what the
StageInventoryrecords and how the prune diff works. - Secrets encryption
— how
spec.decryption.secretRefkeys are read and used. - Rollback
— the rollback store and
rollbackOnFailurebehavior. - Production — hardening a production install.
- Scale and capacity — sizing replicas and the state a multi-replica install keeps reachable.
PreviousRevisionUnavailablerunbook — remediating a rollback that cannot restore.