Skip to main content

MSSQL Custom Resource Reference

The operator exposes two deployment CRDs that share a common spec:

  • MSSQLInstance — one or more independent standalone SQL Server instances.
  • MSSQLAvailabilityGroup — an Always-On Availability Group across replicas. A dedicated CRD, decoupled from MSSQLInstance with no enabled toggle: it is always an AG, adds an availabilityGroup section, and requires size >= 2.

Three additional CRDs manage backup and restore operations against those deployments — MSSQLBackup, MSSQLBackupSchedule, and MSSQLRestore. See backups.md.

Minimal example (MSSQLInstance)​

apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLInstance
metadata:
name: my-mssql
spec:
size: 1
acceptEULA: true
saPasswordSecret: my-mssql-secret
storage:
size: 8Gi

Full example (MSSQLAvailabilityGroup)​

apiVersion: mssql.solanica.io/v1alpha1
kind: MSSQLAvailabilityGroup
metadata:
name: my-mssql
spec:
size: 3
acceptEULA: true
saPasswordSecret: my-mssql-secret
# image: mcr.microsoft.com/mssql/server:2025-latest
# port: 1433
# podManagementPolicy: Parallel

resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "2"
memory: 2Gi

storage:
size: 8Gi
# storageClassName: standard
# --- OR use separate volumes (immutable after creation) ---
# volumes:
# data:
# storageClassName: fast-ssd
# resources:
# requests:
# storage: 50Gi
# log:
# storageClassName: fast-ssd
# resources:
# requests:
# storage: 20Gi
# tempdb:
# emptyDir:
# sizeLimit: 10Gi

expose:
enabled: true
type: ClusterIP
# type: LoadBalancer
# annotations:
# service.beta.kubernetes.io/aws-load-balancer-type: nlb
# loadBalancerSourceRanges:
# - 10.0.0.0/8

tls:
enabled: true
forceEncryption: true
# certificateSecret: my-tls-certs

availabilityGroup:
name: mssql-ag
availabilityMode: synchronous
# endpointPort: 5022

# databases:
# - mydb

The referenced SA password Secret must contain MSSQL_SA_PASSWORD:

apiVersion: v1
kind: Secret
metadata:
name: my-mssql-secret
type: Opaque
stringData:
MSSQL_SA_PASSWORD: "YourStrong@Passw0rd"

Note: SQL Server enforces a strong password policy — at least 8 characters including uppercase, lowercase, digit, and symbol.


Spec fields​

Core​

FieldTypeDefaultDescription
sizeint1Number of SQL Server instances (StatefulSet replicas)
pausedboolfalseSuspend the cluster by scaling the StatefulSet to 0 replicas while keeping all other resources (PVCs, Secrets, ConfigMaps, Services). Set back to false to resume at size. See paused (stop / start)
acceptEULAbool—Required. Must be true to accept the SQL Server EULA
saPasswordSecretstring—Required. Name of a Secret containing key MSSQL_SA_PASSWORD
imagestringmcr.microsoft.com/mssql/server:2025-latestSQL Server container image
imagePullSecrets[]LocalObjectReference—Secret names used to pull images from a private/air-gapped registry. Applies to every operator-managed container (SQL Server, monitoring exporter, AG sidecar). See private / air-gapped registries
imagePullPolicystringKubernetes defaultPull policy for operator-managed containers: Always, IfNotPresent, or Never. When unset, Kubernetes defaults apply (Always for the :latest tag, IfNotPresent otherwise)
editionstringDeveloperSQL Server edition (MSSQL_PID): Developer, Express, Standard, Enterprise, or EnterpriseCore. Developer and Express are free. Express cannot be used with MSSQLAvailabilityGroup (no Always-On) and lacks SQL Server Agent and backup compression
portint1433SQL Server listen port
podManagementPolicystringParallelStatefulSet pod management: Parallel or OrderedReady
resourcesResourceRequirements—CPU/memory requests and limits
storage.sizeQuantity—PVC size per instance (single-PVC mode). Ignored when volumes is set
storage.storageClassNamestringcluster defaultStorage class (single-PVC mode). Ignored when volumes is set
storage.accessModes[]string[ReadWriteOnce]PVC access modes (single-PVC mode). Ignored when volumes is set

paused (stop / start)​

spec.paused lets you stop a cluster without deleting it. When set to true the operator scales the underlying StatefulSet to 0 replicas, so every SQL Server pod is terminated and the compute is released. Everything else is preserved:

  • PersistentVolumeClaims (your databases) — StatefulSets never delete PVCs on scale-down, so all data is retained.
  • Secrets (SA password, TLS certificates), ConfigMaps (mssql.conf, monitoring config).
  • Services (headless, exposed, and AG primary/replicas), so DNS names stay stable.

spec.size is not changed while paused — it is remembered so that clearing the flag restores the previous replica count.

spec:
size: 3
paused: true # scale to 0 pods, keep storage + config; resume later with paused: false

While paused:

  • status.state is paused (the Paused column shows true); the Available and Progressing conditions report reason Paused.
  • For MSSQLAvailabilityGroup, AG reconciliation is skipped (no pods to talk to) and the last-known AG status snapshot is preserved. On resume, the operator re-establishes the Availability Group as the replicas come back.

To start the cluster again, set paused: false (or remove the field). The StatefulSet scales back to size on the same PersistentVolumeClaims.

edition and image (MSSQL_PID mechanics)​

Microsoft ships one SQL Server container image (mcr.microsoft.com/mssql/server); the edition is selected at container start via the MSSQL_PID environment variable, not by using a different image. There is no separate "Standard image" or "Enterprise image".

The operator passes spec.edition straight through to MSSQL_PID (and always sets ACCEPT_EULA=Y and MSSQL_SA_PASSWORD from the referenced Secret). So a mixed fleet of editions runs from the same image tag — only spec.edition differs:

spec:
edition: Standard # -> MSSQL_PID=Standard
# image: mcr.microsoft.com/mssql/server:2025-latest # same image for every edition
spec.editionMSSQL_PIDLicensedNotes
Developer (default)DeveloperFree (non-production)Full Enterprise feature set; not for production use
ExpressExpressFreeFeature-limited: no Always-On AG, no SQL Server Agent, no backup compression; capped memory/CPU/DB size. Standalone MSSQLInstance only
StandardStandardPaidSupports Always-On AG
EnterpriseEnterprisePaidFull feature set
EnterpriseCoreEnterpriseCorePaidEnterprise, core-based licensing

Only these named editions are accepted (CRD enum). Supplying a 25-character product key via MSSQL_PID is not currently supported; for the Linux container the edition name alone activates the edition, and licensing/compliance is your responsibility — the operator does not enforce or validate entitlements.

Edition-driven behavior the operator sets automatically:

  • MSSQL_AGENT_ENABLED=1 on every edition except Express.
  • MSSQL_ENABLE_HADR=1 only for MSSQLAvailabilityGroup resources. Express is rejected for AGs at admission.
  • MSSQL_MEMORY_LIMIT_MB is capped to ~80% of the container memory limit (when a limit is set) so SQL Server respects the cgroup instead of the node's total RAM.

Version note: the default image is 2025-latest. You may pin an older tag via spec.image (e.g. 2022-latest or 2019-latest), but S3 backups (BACKUP ... TO URL with s3://) and Contained Availability Groups require SQL Server 2022 (16.x) or later. The image is Linux-only.

Private / air-gapped registries​

Clusters without direct access to the public registries (mcr.microsoft.com, Docker Hub) can pull every image from a private or mirrored registry and authenticate with an image pull Secret.

1. Mirror the images into your registry and point each image field at it. The operator uses up to three images per SQL Server pod:

ImageField to overridePublic default
SQL Serverspec.imagemcr.microsoft.com/mssql/server:2025-latest
Monitoring exporterspec.monitoring.imageburningalchemist/sql_exporter:0.16.0
AG sidecar (= operator image)spec.availabilityGroup.sidecarImage, or the OPERATOR_IMAGE env var on the manageroperator image

2. Create a pull Secret in the same namespace as the resource:

kubectl create secret docker-registry registry-creds \
--docker-server=registry.internal \
--docker-username=<user> \
--docker-password=<password> \
--namespace=<cr-namespace>

3. Reference it on the CR. imagePullSecrets is pod-level, so a single list covers all containers; imagePullPolicy is applied to the operator-managed containers (useful when nodes pre-load images):

spec:
image: registry.internal/mssql/server:2025-latest
imagePullSecrets:
- name: registry-creds
imagePullPolicy: IfNotPresent
monitoring:
image: registry.internal/burningalchemist/sql_exporter:0.16.0

The AG sidecar runs the operator image inside the SQL Server pod, so it is pulled using spec.imagePullSecrets — no extra configuration is needed for it. The operator's own Deployment (the manager pod) must be given its pull Secret separately at install time (via your Kustomize/Helm values for config/manager), since that is deploy-time configuration rather than part of the CR.

storage.volumes​

Optional. When set, the operator creates separate PVCs for data, log, and tempdb instead of a single PVC. Immutable after creation.

FieldTypeDefaultDescription
storage.volumes.data.storageClassNamestringcluster defaultStorage class for data PVC
storage.volumes.data.accessModes[]string[ReadWriteOnce]Access modes for data PVC
storage.volumes.data.resources.requests.storageQuantity—Required. Size of data PVC
storage.volumes.log.storageClassNamestringcluster defaultStorage class for log PVC
storage.volumes.log.accessModes[]string[ReadWriteOnce]Access modes for log PVC
storage.volumes.log.resources.requests.storageQuantity—Required. Size of log PVC
storage.volumes.tempdb.persistentVolumeClaimVolumeSpec—PVC config for tempdb (mutually exclusive with emptyDir)
storage.volumes.tempdb.emptyDirEmptyDirVolumeSource—Ephemeral emptyDir for tempdb (mutually exclusive with persistentVolumeClaim)

See storage.md for full details.

expose​

FieldTypeDefaultDescription
expose.enabledboolfalseCreate an additional non-headless Service for client access
expose.typestringClusterIPService type: ClusterIP, LoadBalancer, or NodePort
expose.annotationsmap—Annotations merged onto the exposed Service (e.g. load balancer provider config)
expose.loadBalancerSourceRanges[]string—CIDR ranges allowed to reach a LoadBalancer Service

tls​

FieldTypeDefaultDescription
tls.enabled*booltrueEnable TLS encryption for client connections
tls.forceEncryption*booltrueRequire all clients to use TLS
tls.certificateSecretstring—User-provided Secret with tls.crt and tls.key (optionally ca.crt)

See tls.md for full details.

configuration​

Declarative SQL Server startup configuration merged into the operator-managed mssql.conf. Because these settings are read when the SQL Server process starts, changing them triggers a rolling restart of the pods.

FieldTypeDefaultDescription
configuration.traceFlags[]int—SQL Server trace flag numbers enabled at startup, rendered into the [traceflag] section (e.g. 1117, 1118, 3226)
configuration.mssqlConfmap[string]map[string]string—Free-form mssql.conf settings keyed by INI section then setting name. Merged with the operator's own sections
spec:
configuration:
traceFlags:
- 1117
- 3226
mssqlConf:
memory:
memorylimitmb: "4096" # overrides the operator's automatic ~80% memory cap
sqlagent:
enabled: "true" # start SQL Server Agent
telemetry:
customerfeedback: "false"

Merge and precedence. The final mssql.conf is the union of the operator's managed sections and your mssqlConf. You may add new sections ([sqlagent], [tempdb], [telemetry], …) or additional keys to a section the operator also writes. Section and setting names must be lowercase INI identifiers ([a-z0-9][a-z0-9._-]*) and values may not contain newlines.

Note: mssql.conf [tempdb] settings such as datafilecount and datafilesize are applied by SQL Server at first-time initialization, but the container image may auto-provision tempdb files based on the CPU count and override them. Trace flags and settings like sqlagent.enabled are honoured deterministically.

Reserved keys. Keys the operator owns are rejected by the admission webhook — configure them through their dedicated spec fields instead:

Section / keyWhen reservedUse instead
[network] tlscert, tlskey, tlsprotocols, forceencryptionalwaysspec.tls
[network] tcpportalwaysspec.port
[filelocation] defaultdatadir, defaultlogdir, defaultdumpdir, errorlogfilewhen spec.storage.volumes is set(managed automatically)
[hadr] hadrenabledon MSSQLAvailabilityGroup(managed automatically)
[traceflag] (whole section)alwaysspec.configuration.traceFlags

Note: configuration.mssqlConf.memory.memorylimitmb is not reserved. Setting it takes precedence over the operator's automatic MSSQL_MEMORY_LIMIT_MB cap (~80% of the container memory limit), which is then omitted.

availabilityGroup​

Only available on the MSSQLAvailabilityGroup CRD. Requires size >= 2.

FieldTypeDefaultDescription
availabilityGroup.namestring<cr-name>-agAG name inside SQL Server. Immutable
availabilityGroup.databases[]string—Databases to add to the AG (automatic seeding)
availabilityGroup.availabilityModestringsynchronoussynchronous (zero data loss) or asynchronous (faster, possible data loss). Immutable
availabilityGroup.readableSecondariesstringallSecondary read access exposed via the <cr-name>-replicas Service: all, readOnly, or no
availabilityGroup.containedboolfalseSQL Server 2022 Contained AG (replicates master/msdb). Immutable
availabilityGroup.endpointPortint5022Database mirroring endpoint port. Immutable

See availability-groups.md for full details, including failover tuning (allowDataLoss, lease timings), the health-check sidecar, and the preStop failover hook.

backup​

Optional. Declares named S3-compatible backup destinations on an MSSQLInstance or MSSQLAvailabilityGroup. Backups, schedules, and restores are then driven by the separate MSSQLBackup, MSSQLBackupSchedule, and MSSQLRestore CRDs.

FieldTypeDefaultDescription
backup.enginestringnativeBackup engine. Only native (SQL Server BACKUP ... TO URL) is supported
backup.targetsmap—Named destinations. Each entry has type: s3, an s3 block (endpoint, bucket, prefix, region, urlStyle), a credentialSecretRef, and an optional caBundleSecretRef for self-signed TLS

See backups.md for full details and CR examples.


Status fields​

status:
ready: 3 # number of ready instances
size: 3 # desired size
state: ready # initializing | ready | error
message: "3/3 instances ready"
availabilityGroup: # only on MSSQLAvailabilityGroup
state: healthy # healthy | degraded | pending | configuring | error
primary: my-mssql-0.my-mssql.default.svc.cluster.local
synchronizedReplicas: 3
message: "3/3 replicas synchronized"