Skip to main content

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.

CRDPurpose
MSSQLBackupOne-shot full, differential, or log backup
MSSQLBackupScheduleRecurring backups on a cron cadence, with retention
MSSQLRestoreOne-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 URL with the s3:// 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.compression unset/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​

FieldTypeDefaultDescription
typestrings3Destination kind. Only s3 is supported
s3.endpointstring—Required. S3 endpoint host[:port] (connected over HTTPS)
s3.bucketstring—Required. Destination bucket (must already exist)
s3.prefixstring—Optional key prefix (folder) within the bucket
s3.regionstringus-east-1S3 region
s3.urlStylestringvirtualHostvirtualHost (bucket as subdomain) or path (bucket as first path segment — typical for MinIO)
credentialSecretRefstring—Required. Secret with keys accessKeyId and secretAccessKey
caBundleSecretRefstring—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​

FieldTypeDefaultDescription
instanceRef.namestring—Required. Target MSSQLInstance/MSSQLAvailabilityGroup
instanceRef.kindstringMSSQLInstanceMSSQLInstance or MSSQLAvailabilityGroup
targetstring—Required. Name of a destination under the target's spec.backup.targets
typestringfullfull, differential, or log
databases[]stringall user DBsDatabases to back up
enginestringnativeOverrides the instance default. Only native
agReplicaPreferencestringpreferredFor AG targets: preferred (uses sys.fn_hadr_backup_is_preferred_replica, COPY_ONLY on a secondary) or primary
options.compression*booltrueEnable backup compression
options.copyOnlyboolfalseForce COPY_ONLY (does not affect the backup chain)
options.maxTransferSizeBytesintSQL 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 (or BULK_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​

FieldTypeDefaultDescription
instanceRef, target, databases, engine, agReplicaPreference, options——Same as MSSQLBackup, applied to every run
typestringfullBackup type for the flat form. Ignored with full/differential/log
schedulestring—Cron expression for the flat form
full / differential / logobject—Per-type cron schedules (.schedule) for the PITR form
retention.keepLastint0 (off)Keep at most this many recent successful runs
retention.keepDaysint0 (off)Keep runs newer than this many days
retention.deleteFromStoreboolfalseAlso delete backup objects from S3 when pruning (default only prunes the Kubernetes record)
concurrencyPolicystringForbidForbid skips a run while a previous one is active; Allow permits overlap
suspendboolfalsePause 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​

FieldTypeDefaultDescription
instanceRefobject—Required. Instance/AG to restore into
targetstring—Required. Destination (under the target's spec.backup.targets) where the backups live
source.backupRefstring—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.pointInTimestring (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
withReplaceboolfalseOverwrite an existing database of the same name (WITH REPLACE)
recoverystringrecoveryrecovery 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 without source.pointInTime) targets a standalone MSSQLInstance; Availability Group targets are rejected for now.
  • Chain assembly uses MSSQLBackup records. The full→log chain is discovered from the MSSQLBackup resources 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.