Backups & Restores
The operator backs up and restores SQL Server databases to S3-compatible object
storage using SQL Server's native BACKUP ... TO URL engine. Backups and
restores are decoupled from the deployment CRDs: the destination lives on the
MSSQLInstance / MSSQLAvailabilityGroup under spec.backup, and each
operation is its own custom resource.
| CRD | Purpose |
|---|---|
MSSQLBackup | One-shot full, differential, or log backup |
MSSQLBackupSchedule | Recurring backups on a cron cadence, with retention |
MSSQLRestore | One-shot restore into an instance or Availability Group |
Both MSSQLInstance and MSSQLAvailabilityGroup can be backup/restore targets;
an InstanceRef selects the target by name and kind.
Requirements
- SQL Server 2022 (16.x) or later — required for S3 (
BACKUP TO URLwith thes3://scheme). - An S3-compatible endpoint reachable over HTTPS. SQL Server always connects
over TLS; there is no option to skip certificate verification. For self-signed
endpoints (for example MinIO) supply the CA via
caBundleSecretRef. - The bucket must already exist. SQL Server never creates buckets.
- Express edition lacks backup compression; leave
options.compressionunset/false on Express.
1. Declare a backup destination
Backup destinations are declared once on the deployment CR under
spec.backup.targets. A backup, schedule, or restore then references a target by
its map key.
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLInstance
metadata:
name: my-mssql
spec:
size: 1
acceptEULA: true
saPasswordSecret: my-mssql-secret
storage:
size: 20Gi
backup:
engine: native # only "native" is supported
targets:
s3: # <-- referenced by name from MSSQLBackup/Restore
type: s3
s3:
endpoint: s3.us-west-2.amazonaws.com # host[:port], HTTPS only
bucket: my-mssql-backups # must already exist
prefix: prod/my-mssql # optional key prefix (folder)
region: us-west-2 # defaults to us-east-1
urlStyle: virtualHost # virtualHost | path
credentialSecretRef: my-mssql-s3-creds # accessKeyId / secretAccessKey
# caBundleSecretRef: my-mssql-s3-ca # ca.crt for self-signed TLS
spec.backup.targets.<name> fields
| Field | Type | Default | Description |
|---|---|---|---|
type | string | s3 | Destination kind. Only s3 is supported |
s3.endpoint | string | — | Required. S3 endpoint host[:port] (connected over HTTPS) |
s3.bucket | string | — | Required. Destination bucket (must already exist) |
s3.prefix | string | — | Optional key prefix (folder) within the bucket |
s3.region | string | us-east-1 | S3 region |
s3.urlStyle | string | virtualHost | virtualHost (bucket as subdomain) or path (bucket as first path segment — typical for MinIO) |
credentialSecretRef | string | — | Required. Secret with keys accessKeyId and secretAccessKey |
caBundleSecretRef | string | — | Secret with key ca.crt to trust a self-signed endpoint. Mounted into the SQL Server pod's trust store |
Credential Secret
apiVersion: v1
kind: Secret
metadata:
name: my-mssql-s3-creds
type: Opaque
stringData:
accessKeyId: AKIA...
secretAccessKey: "wJalr..."
Self-signed endpoints (MinIO, on-prem S3)
SQL Server requires HTTPS and validates the endpoint certificate. Put the CA (or
the self-signed server certificate) under key ca.crt in a Secret and reference
it with caBundleSecretRef; the operator mounts it into the SQL Server pod's
trust store.
apiVersion: v1
kind: Secret
metadata:
name: my-mssql-s3-ca
type: Opaque
stringData:
ca.crt: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
For MinIO, also set s3.urlStyle: path and use the service host:port as the
endpoint (for example minio.default.svc:9000).
2. On-demand backups (MSSQLBackup)
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLBackup
metadata:
name: salesdb-full
spec:
instanceRef:
name: my-mssql # kind defaults to MSSQLInstance
target: s3 # a key under spec.backup.targets
type: full # full | differential | log
databases: # omit to back up all online user databases
- salesdb
options:
compression: true # default true; unsupported on Express
# copyOnly: false
# maxTransferSizeBytes: 10485760 # 5–20 MiB, requires compression
MSSQLBackup spec fields
| Field | Type | Default | Description |
|---|---|---|---|
instanceRef.name | string | — | Required. Target MSSQLInstance/MSSQLAvailabilityGroup |
instanceRef.kind | string | MSSQLInstance | MSSQLInstance or MSSQLAvailabilityGroup |
target | string | — | Required. Name of a destination under the target's spec.backup.targets |
type | string | full | full, differential, or log |
databases | []string | all user DBs | Databases to back up |
engine | string | native | Overrides the instance default. Only native |
agReplicaPreference | string | preferred | For AG targets: preferred (uses sys.fn_hadr_backup_is_preferred_replica, COPY_ONLY on a secondary) or primary |
options.compression | *bool | true | Enable backup compression |
options.copyOnly | bool | false | Force COPY_ONLY (does not affect the backup chain) |
options.maxTransferSizeBytes | int | SQL default (10 MiB) | Transfer/part size (5–20 MiB); requires compression |
An MSSQLBackup is terminal: once it reaches Succeeded or Failed it is
never re-run.
Backup type notes
- Full — a complete copy; the base of every restore chain.
- Differential — changes since the last full backup. Restore requires the base full backup first.
- Log — transaction log backup. Requires the database to be in the
FULL(orBULK_LOGGED) recovery model. Enables point-in-time recovery when replayed in order.
Availability Group targets
For an MSSQLAvailabilityGroup, agReplicaPreference: preferred (the default)
lets SQL Server pick the configured backup-preferred replica and automatically
adds COPY_ONLY when the backup runs on a secondary. Use primary to force the
backup onto the primary replica. status.replica reports the pod that ran the
backup.
3. Scheduled backups (MSSQLBackupSchedule)
A schedule creates MSSQLBackup resources on a cron cadence and prunes old ones
per its retention policy. It inherits the same target/database/options fields as
MSSQLBackup.
Flat form (single backup type)
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLBackupSchedule
metadata:
name: salesdb-nightly-full
spec:
instanceRef:
name: my-mssql
target: s3
databases: [salesdb]
type: full
schedule: "0 2 * * *" # every day at 02:00 (5-field cron)
retention:
keepLast: 14 # keep the 14 most recent runs
keepDays: 30 # and/or keep runs newer than 30 days
deleteFromStore: false # also delete objects from S3 when pruning
Point-in-time-recovery cadence (full + differential + log)
Use the full / differential / log blocks instead of the flat type +
schedule form to run a layered PITR cadence. The two forms are mutually
exclusive.
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLBackupSchedule
metadata:
name: salesdb-pitr
spec:
instanceRef:
name: my-mssql
target: s3
databases: [salesdb]
full:
schedule: "0 1 * * 0" # weekly full, Sunday 01:00
differential:
schedule: "0 1 * * 1-6" # daily differential, Mon–Sat 01:00
log:
schedule: "*/15 * * * *" # log backup every 15 minutes
retention:
keepDays: 14
concurrencyPolicy: Forbid # Forbid | Allow
suspend: false
MSSQLBackupSchedule spec fields
| Field | Type | Default | Description |
|---|---|---|---|
instanceRef, target, databases, engine, agReplicaPreference, options | — | — | Same as MSSQLBackup, applied to every run |
type | string | full | Backup type for the flat form. Ignored with full/differential/log |
schedule | string | — | Cron expression for the flat form |
full / differential / log | object | — | Per-type cron schedules (.schedule) for the PITR form |
retention.keepLast | int | 0 (off) | Keep at most this many recent successful runs |
retention.keepDays | int | 0 (off) | Keep runs newer than this many days |
retention.deleteFromStore | bool | false | Also delete backup objects from S3 when pruning (default only prunes the Kubernetes record) |
concurrencyPolicy | string | Forbid | Forbid skips a run while a previous one is active; Allow permits overlap |
suspend | bool | false | Pause the schedule without deleting it |
Cron expressions are standard 5-field and evaluated in the operator's timezone.
status.lastScheduleTime, status.lastSuccessfulTime, and status.active track
progress.
4. Restores (MSSQLRestore)
A restore reads backups from a destination (target) and restores them into the
instance or AG named by instanceRef. Because instanceRef and source are
independent, you can restore into the same instance (in place) or a
different one (clone / cross-instance), as long as the destination and its
credentials are declared on the restore target's spec.backup.targets.
In-place restore from a backup
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLRestore
metadata:
name: salesdb-restore
spec:
instanceRef:
name: my-mssql
target: s3
source:
backupRef: salesdb-full # a completed MSSQLBackup in this namespace
withReplace: true # overwrite an existing DB of the same name
recovery: recovery # recovery (online) | norecovery (stay restoring)
Cross-instance restore (clone into a different deployment)
Restore a backup taken on one instance into another. The destination
MSSQLInstance/AG must declare a spec.backup.targets entry (named here as
target) pointing at the same bucket with valid credentials.
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLRestore
metadata:
name: salesdb-clone
spec:
instanceRef:
name: my-mssql-staging # a different deployment
target: s3
source:
backupRef: salesdb-full
recovery: recovery
Disaster recovery from explicit URLs
When no MSSQLBackup record survives, restore directly from backup URLs. List
every stripe of a striped backup. Exactly one databases mapping is required to
name the target database.
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLRestore
metadata:
name: salesdb-dr
spec:
instanceRef:
name: my-mssql
target: s3
source:
urls:
- s3://s3.us-west-2.amazonaws.com/my-mssql-backups/prod/my-mssql/salesdb_full.bak
databases:
- sourceName: salesdb
targetName: salesdb
withReplace: true
recovery: recovery
Rename on restore
Provide databases mappings to restore a source database under a different name
(data/log files are relocated automatically):
databases:
- sourceName: salesdb
targetName: salesdb_copy
Point-in-time recovery (source.databases + pointInTime)
Restore by database name and let the operator assemble the backup chain
(full → differential → log) from the MSSQLBackup records for that database on
the target, rolling forward to a specific instant (source.pointInTime, applied
as SQL Server STOPAT) or, when pointInTime is omitted, to the most recent log
backup. Ordering uses the LSN metadata captured on each backup, falling back to
backup timestamps. This targets a standalone MSSQLInstance (AG targets are not
supported yet) and requires log backups in the chain (RECOVERY FULL).
apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLRestore
metadata:
name: salesdb-pitr
spec:
instanceRef:
name: my-mssql
target: s3
source:
databases:
- salesdb
pointInTime: 2026-01-02T03:04:05Z # omit to restore to the latest log
databases: # optional: restore under a new name
- sourceName: salesdb
targetName: salesdb_pit
recovery: recovery
The operator runs RESTORE DATABASE ... WITH NORECOVERY, replays each log
WITH NORECOVERY (and STOPAT when pointInTime is set), and brings the
database online with WITH RECOVERY on the final step.
MSSQLRestore spec fields
| Field | Type | Default | Description |
|---|---|---|---|
instanceRef | object | — | Required. Instance/AG to restore into |
target | string | — | Required. Destination (under the target's spec.backup.targets) where the backups live |
source.backupRef | string | — | Restore from a completed MSSQLBackup in this namespace |
source.urls | []string | — | Restore directly from explicit backup URLs (all stripes) |
source.databases | []string | — | Restore by database name; the operator assembles the full→log chain from the MSSQLBackup records. Standalone MSSQLInstance only |
source.pointInTime | string (RFC 3339) | — | With source.databases, roll the log chain forward to this instant (STOPAT). Omit to restore to the latest log |
databases | []object | — | Optional sourceName→targetName rename mappings. Required (exactly one) with source.urls |
withReplace | bool | false | Overwrite an existing database of the same name (WITH REPLACE) |
recovery | string | recovery | recovery brings the DB online; norecovery leaves it restoring for further restores |
source must set exactly one of backupRef, urls, or databases
(enforced by the CRD). A restore is terminal: once Succeeded or Failed it is
never re-run.
Current limitations
- S3 only. Azure Blob and local/file destinations are not implemented.
- HTTPS required, no skip-verify. Self-signed endpoints need
caBundleSecretRef(ca.crt). This is a SQL Server engine constraint. - Bucket must pre-exist. The operator does not create buckets.
- Point-in-time recovery is standalone-only. Chain restore via
source.databases(with or withoutsource.pointInTime) targets a standaloneMSSQLInstance; Availability Group targets are rejected for now. - Chain assembly uses
MSSQLBackuprecords. The full→log chain is discovered from theMSSQLBackupresources in the namespace, so those records (and their backup objects) must still exist. Schedule retention that prunes the base full backup or a log in the chain breaks PITR for times it no longer covers.
Monitoring
# Backups
kubectl get mssqlbackups
kubectl describe mssqlbackup salesdb-full
# Schedules
kubectl get mssqlbackupschedules
# Restores
kubectl get mssqlrestores
kubectl describe mssqlrestore salesdb-restore
status.phase is one of Pending, Running, Succeeded, or Failed.
Per-database results (URLs, size, timings, errors) are reported under
status.databases.
See cr-reference.md for the spec.backup destination fields
and availability-groups.md for AG-specific behavior.