BackupSchedule¶
BackupSchedule takes logical backups of one CamundaCluster on a cron schedule. You create it, or another tool creates it for you.
At each trigger the operator creates one backup of the kind that matches the secondary storage of the cluster: LogicalBackupElasticsearch or LogicalBackupRDBMS. That backup then runs on its own. The schedule also deletes its own finished backups beyond spec.retained.
kubectl get backupschedules lists the schedules with Ready, its reason, the cron expression, and the age. kubectl get backupschedules -o wide adds the cluster, the last schedule, and the last backup.
Before you create a schedule, make sure that:
- The
CamundaClusterhasspec.storageRefandspec.backupStorageRef. - The
SecondaryStorageConfigthat the cluster names exists. - The schedule lives in the namespace of the cluster.
The smallest schedule names the cluster and the cron expression:
apiVersion: core.camunda.io/v1
kind: BackupSchedule
metadata:
name: my-cluster-schedule
namespace: my-cluster-ns
spec:
clusterRef:
name: my-cluster
schedule: "0 2 * * *"
graph LR
BS[BackupSchedule] -.->|clusterRef| CC[CamundaCluster]
CC -.->|storageRef| SSC[SecondaryStorageConfig]
BS -->|creates at each trigger| LB["LogicalBackupElasticsearch or LogicalBackupRDBMS"]
BS -->|prunes beyond spec.retained| LB
LB -.->|clusterRef| CC
The cron expression¶
spec.schedule is a five-field cron expression: minute, hour, day of month, month, and day of week. The operator evaluates it in UTC. The time zone of the cluster, of the node, and of your workstation does not change the trigger time.
apiVersion: core.camunda.io/v1
kind: BackupSchedule
metadata:
name: my-cluster-schedule
namespace: my-cluster-ns
spec:
# 02:00 UTC every day.
schedule: "0 2 * * *"
# ... the rest of your schedule
The first trigger comes after the creation of the schedule. Every later trigger follows status.lastScheduleTime.
One trigger creates at most one backup. If the operator is down for a longer time, the triggers of that time do not queue up. Only the latest one fires, and the schedule continues from there.
You can change spec.schedule and spec.retained at any time. The new values apply at once.
The backups that a schedule creates¶
The name of a created backup is <schedule>-<unix-timestamp> of the trigger. The labels name the cluster and the schedule:
apiVersion: core.camunda.io/v1
kind: LogicalBackupRDBMS
metadata:
name: my-cluster-schedule-1787104800
namespace: my-cluster-ns
labels:
camunda.io/cluster: my-cluster
camunda.io/backup-schedule: my-cluster-schedule
spec:
clusterRef:
name: my-cluster
To list the backups of one schedule, select on the label:
For a schedule name of more than 63 characters, the label value is shortened and ends in a hash. kubectl get lbrdbms --show-labels shows it.
Each creation records the Normal event BackupCreated on the schedule. The event names the kind and the backup.
Skipped triggers¶
The operator creates no backup when:
- The cluster is suspended. This is the case with
spec.suspend: trueand while a restore holds the cluster. It is also the case while theReadyreason of the cluster isStorageAlreadyAttachedorWaitingForHandover, because the cluster then waits for its backend. ReadReadyon theCamundaClusterfor the cause. Backups start again at the next trigger after the cluster resumes. - The backup cannot start on the cluster. Both kinds need a usable
status.managementon the cluster. ALogicalBackupRDBMSalso needs theReadycondition of the cluster atTrue. ALogicalBackupElasticsearchneedsstatus.management.backupRepositoryinstead, and does not needReady. The event names the cause and the state of theReadycondition of the cluster. Correct the cause on theCamundaCluster. - A backup of this schedule is not
CompletedorFailedyet. Two backups of one schedule never overlap. If a backup often runs past the next trigger, give the schedule a longer gap. - An object already holds the name of the new backup and belongs to something else. Only that one trigger is skipped, because the name carries the trigger time. Find out what created that object, and remove it if it does not belong there.
- A reference does not resolve. The
CamundaClusteris missing, or it has nospec.storageReforspec.backupStorageRef, or itsSecondaryStorageConfigis missing. TheReadycondition of the schedule names the cause. Create the resource, or set the missing field. spec.scheduleis not a valid cron expression. No trigger fires at all, andReadynamesspec.schedule. Correct the expression.- More than one trigger passed while the operator was not running. The schedule takes the latest of them, see The cron expression. The earlier triggers record no event.
The first three cases record the Normal event TriggerSkipped. A name that belongs to something else records the Warning event BackupNameTaken. A reference that does not resolve and an invalid cron expression show on Ready of the schedule, with no event.
A skipped trigger is not retried. status.lastScheduleTime moves to it, and the next backup runs at the next trigger of the cron expression.
kubectl describe backupschedule my-cluster-schedule -n my-cluster-ns shows the events:
Type Reason Message
Normal BackupCreated Created LogicalBackupRDBMS "my-cluster-schedule-1787104800"
Normal TriggerSkipped Skipped the trigger at 2026-08-20T02:00:00Z: CamundaCluster "my-cluster" is suspended
Normal TriggerSkipped Skipped the trigger at 2026-08-21T02:00:00Z: backup "my-cluster-schedule-1787104800" has not finished
Warning BackupNameTaken Skipped the trigger at 2026-08-22T02:00:00Z: LogicalBackupRDBMS "my-cluster-schedule-1787364000" already exists and belongs to something else
Normal TriggerSkipped Skipped the trigger at 2026-08-23T02:00:00Z: CamundaCluster "my-cluster" is not ready: Ready is False with reason InvalidReference
Retention¶
spec.retained bounds how many backups of this schedule are kept, per terminal phase:
apiVersion: core.camunda.io/v1
kind: BackupSchedule
metadata:
name: my-cluster-schedule
namespace: my-cluster-ns
spec:
retained:
# Two weeks of nightly backups.
completed: 14
failed: 1
# ... the rest of your schedule
The operator deletes the oldest completed backups beyond retained.completed, by completion time. It deletes the oldest failed backups beyond retained.failed the same way. A backup that is still Pending or Running is never deleted. A schedule that sets no bounds keeps 7 completed and 3 failed backups. If you lower a bound, the overflow is deleted at once.
The operator deletes only the backups that carry the label camunda.io/backup-schedule of this schedule. A backup that you create by hand carries no such label, so no schedule deletes it, unless you add that label.
A deleted backup takes its stored artifacts with it. For an Elasticsearch backup these are the snapshots and the partition backup. For a relational backup this is the database dump. If the cluster or a storage contract of the backup is gone, the artifacts stay in the bucket, and the backup records a Warning event. The Deletion section of LogicalBackupElasticsearch and LogicalBackupRDBMS names the cases. Each deletion records the Normal event BackupPruned.
Backups that outlive the primary storage¶
A LogicalBackupRDBMS is one database dump plus the Zeebe backup of its time. The two together are one restore point. Zeebe deletes the primary-storage backups that fall outside spec.backup.primaryStorage.retention.window of the cluster, P7D by default. A dump that is older than that window can no longer be restored.
For a relational cluster the operator compares the age of the oldest retained dump with that window. The age is retained.completed times the interval between two triggers. If the dumps live longer than the window, the operator records the Warning event RetentionWindowExceeded at the trigger:
Type Reason Message
Warning RetentionWindowExceeded the schedule keeps 14 completed backups at one every 24h0m0s, so the oldest
dump lives about 336h0m0s, longer than the primary-storage retention window
P7D of the cluster; dumps older than the window can no longer be restored
To clear the warning, lower retained.completed, take the backups less often, or raise the retention window of the cluster.
The operator records no such warning when spec.backup.primaryStorage.retention.cleanupSchedule of the cluster is none, because Zeebe then deletes no primary-storage backup. The check does not run for an Elasticsearch cluster.
Deletion¶
When you delete a BackupSchedule, its backups and their artifacts stay. No trigger fires again, and no retention applies to those backups. Delete the backups you no longer want. Or create a schedule of the same name again, and its retention applies to them again.
A schedule has no suspend field. To stop the backups for a time, delete the schedule.
Status¶
| Type | Reason | Meaning | What to do |
|---|---|---|---|
Ready |
Healthy |
The references resolve and the schedule can run its backups. | Nothing. |
Ready |
InvalidReference |
The CamundaCluster does not exist, it has no spec.storageRef or spec.backupStorageRef, its SecondaryStorageConfig does not exist, or spec.schedule is not a valid cron expression. The message names the cause. |
Create the resource, set the missing field, or correct spec.schedule. |
Ready reports the references and the cron expression only. A skipped trigger is an event, see Skipped triggers.
A healthy schedule that took a backup reads:
status:
# The most recent trigger that the schedule consumed. A skipped trigger is consumed too.
lastScheduleTime: "2026-08-19T02:00:00Z"
# The backup that the schedule created most recently.
lastBackupName: my-cluster-schedule-1787104800
# The last generation that the operator reconciled.
observedGeneration: 2
conditions:
- type: Ready
status: "True"
reason: Healthy
message: the schedule can run its backups
observedGeneration: 2
lastTransitionTime: "2026-08-19T02:00:00Z"
A schedule whose cluster is gone reads:
status:
lastScheduleTime: "2026-08-19T02:00:00Z"
lastBackupName: my-cluster-schedule-1787104800
observedGeneration: 2
conditions:
- type: Ready
status: "False"
reason: InvalidReference
message: CamundaCluster my-cluster-ns/my-cluster does not exist
observedGeneration: 2
lastTransitionTime: "2026-08-20T02:00:00Z"
Fields¶
The API reference lists every field of BackupSchedule, with its type, whether it is required, its default, and its validation.
Validation rules¶
spec.clusterRef.nameis required and must not be empty. The cluster must live in the namespace of the schedule.spec.scheduleis required. The API server checks the shape only: five fields, separated by whitespace, of the cron characters. A value out of range, for example the minute99, passes admission. The operator then reportsInvalidReferenceonReady. Seconds fields and@dailystyle descriptors are rejected at admission.spec.retained.completedmust be 1 or more.spec.retained.failedmust be 0 or more.- The spec is mutable. Every field can be changed on a live schedule.
A production-shaped example¶
A nightly schedule that keeps two weeks of backups and only the last failure:
apiVersion: core.camunda.io/v1
kind: BackupSchedule
metadata:
name: my-cluster-schedule
namespace: my-cluster-ns
labels:
camunda.io/cluster: my-cluster
spec:
clusterRef:
name: my-cluster
# 02:00 UTC every day, outside business hours.
schedule: "0 2 * * *"
retained:
completed: 14
failed: 1
kubectl get backupschedules -n my-cluster-ns -o wide then shows the cron expression, the cluster, the last schedule, and the last backup that the schedule created.
Related¶
- CamundaCluster: the cluster that the schedule backs up. Its suspension gates the triggers, and its
spec.backup.primaryStorage.retention.windowbounds how long a dump stays restorable. - SecondaryStorageConfig: its
spec.typeselects the backup kind that each trigger creates. - LogicalBackupElasticsearch: the backup kind for an Elasticsearch cluster.
- LogicalBackupRDBMS: the backup kind for a relational cluster.
- Backup guide: how to set up backup storage and take a backup.