Whitepapers / SQL Server on Kubernetes

Running SQL Server on Kubernetes

A complete architecture guide

Microsoft SQL Server has run on Linux since 2017, and the same engine ships as an official container image. Running it on Kubernetes works well in production, as long as you plan for the parts Kubernetes doesn't give you: failover without a Windows cluster, memory limits, and licensing.

For DBAs, SREs, and platform engineers. Covers SQL Server 2022 and 2025 on Linux, with notes on 2019.

1. Introduction: SQL Server meets Kubernetes

SQL Server is one of the most widely used commercial databases, and for most of its history it ran only on Windows. Since SQL Server 2017 the same database engine also runs on Linux, and Microsoft publishes official container images on mcr.microsoft.com/mssql/server. One image covers every edition. You pick the edition when the container starts.

Is SQL Server on Kubernetes production-ready?

Yes, with caveats worth knowing up front. The engine is the same one that runs on Windows, and Microsoft documents running it on Kubernetes with StatefulSets. Always On availability groups, the feature most production deployments rely on, work on Linux and in containers.

What's thinner is the tooling around it. PostgreSQL and MySQL each have several mature open source operators. SQL Server doesn't. Microsoft doesn't ship a general-purpose operator, and the options that exist are mostly commercial. Section 6 covers them.

Teams move SQL Server to Kubernetes for the same reasons as other databases:

  1. It avoids lock-in to Azure SQL, Amazon RDS, or Cloud SQL. The same deployment runs on any cloud or on-prem.
  2. Operations are automated. An operator handles failover, backups, and upgrades through Kubernetes resources instead of runbooks.
  3. It gives you control over cost. Free Developer edition covers every non-production environment, and production runs on licenses you already own.

What's different about SQL Server

Three things set SQL Server apart from the open source databases in our other guides:

  • Licensing. Every production core needs a license, and how you size containers decides how many you buy. Section 3 covers the rules for containers.
  • Linux feature gaps. SQL Server on Linux doesn't support FILESTREAM and FileTable, merge replication, linked servers to databases other than SQL Server, or the CmdExec, PowerShell, and SSIS job steps in SQL Server Agent. Features that depend on Azure Arc, including Microsoft Entra ID authentication, aren't available in containers.
  • No Windows cluster. On Windows, availability groups fail over through Windows Server Failover Clustering. On Kubernetes, something else has to make that decision. That's the central design question, and section 2 covers it.

2. SQL Server on Kubernetes architectures

There are two ways to deploy SQL Server on Kubernetes: a standalone instance that relies on Kubernetes to restart it, or an availability group that replicates each database to other instances and fails over between them.

Standalone instances

The simplest deployment is a StatefulSet with one SQL Server pod and a persistent volume mounted at /var/opt/mssql. This is what Microsoft's own Kubernetes guidance starts with. If the SQL Server process crashes, Kubernetes restarts the container. If the node fails, the pod is rescheduled on another node, the network volume is re-attached, and SQL Server runs crash recovery before it accepts connections.

That recovery is slower than it sounds. Kubernetes won't start a replacement StatefulSet pod until it's certain the old one has stopped, so when a node becomes unreachable, the pod can stay stuck in Terminating until the node is removed or marked out of service. Recovery takes minutes, and sometimes a person. Standalone instances are a good fit for development, testing, and workloads that can tolerate that. Production databases usually need an availability group.

Always On availability groups

An availability group (AG) is a set of SQL Server instances, called replicas, each with its own copy of the databases on its own storage. The primary sends transaction log records to the secondaries, which apply them continuously. Each secondary runs in one of two modes:

  • Synchronous commit. The primary waits for the secondary to harden each transaction to its log before confirming the commit. Failover to it loses no committed data. The cost is a network round trip per commit.
  • Asynchronous commit. The primary doesn't wait. It's faster over long distances, but transactions not yet sent are lost if you fail over to it.

Secondaries can also serve read-only queries, and new replicas can copy databases from the primary automatically with automatic seeding.

Who decides to fail over: the cluster type

Every availability group has a cluster type that tells SQL Server who's in charge of failover:

Cluster typeWho fails overOn Kubernetes
WSFCWindows Server Failover ClusteringNot available. Windows only
EXTERNALAn external cluster manager: Pacemaker on Linux VMs, or an operator on KubernetesThe production choice
NONENobody. Failover is a manual T-SQL commandFor read scale-out or migrations, not high availability

On Linux VMs, Microsoft pairs EXTERNAL availability groups with Pacemaker and its mssql-server-ha resource agent. Pacemaker doesn't fit inside Kubernetes, so on Kubernetes an operator takes over that role. To do it safely, the operator has to do five things:

  1. Detect failure. Notice that the primary is gone, or alive but no longer making progress. sp_server_diagnostics reports the engine's own health and catches a frozen primary that still accepts TCP connections.
  2. Pick a safe target. Promote only a synchronous secondary that was synchronized, so no committed transaction is lost.
  3. Promote it. Run ALTER AVAILABILITY GROUP ... FAILOVER on the new primary.
  4. Fence the old primary. With the EXTERNAL cluster type, a restarted primary comes back in the RESOLVING state and waits to be told its role, rather than accepting writes on its own.
  5. Move traffic. Point applications at the new primary.

An availability group for an operator to manage looks like this:

CREATE AVAILABILITY GROUP [ag1]
  WITH (CLUSTER_TYPE = EXTERNAL, DB_FAILOVER = ON)
  FOR DATABASE [sales]
  REPLICA ON
    N'mssql-0' WITH (ENDPOINT_URL = N'tcp://mssql-0.mssql:5022',
      AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, FAILOVER_MODE = EXTERNAL,
      SEEDING_MODE = AUTOMATIC),
    N'mssql-1' WITH (ENDPOINT_URL = N'tcp://mssql-1.mssql:5022',
      AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, FAILOVER_MODE = EXTERNAL,
      SEEDING_MODE = AUTOMATIC),
    N'mssql-2' WITH (ENDPOINT_URL = N'tcp://mssql-2.mssql:5022',
      AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, FAILOVER_MODE = EXTERNAL,
      SEEDING_MODE = AUTOMATIC);

On Linux, replicas authenticate to each other with certificates, not Windows accounts. The same certificate has to be imported on every replica before the database mirroring endpoint is created.

One more setting matters for data safety: REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT. With a value of 1, the primary holds commits whenever no synchronous secondary is synchronized. That guarantees every committed transaction exists on two replicas, at the cost of pausing writes when the secondaries fall behind.

Routing without a listener

On Windows, applications connect to an availability group listener, a virtual network name that the cluster moves to the current primary. On Kubernetes, a Service does that job. A sidecar or the operator labels each pod with its current role, and the Service selects the primary by that label:

apiVersion: v1
kind: Service
metadata:
  name: mssql-primary
spec:
  selector:
    app.kubernetes.io/instance: mssql
    role: primary
  ports:
  - port: 1433

A second Service that selects secondaries gives you a read-only endpoint. Applications send reporting queries there directly, because read-only routing with ApplicationIntent=ReadOnly depends on a listener. Configure clients to retry: after a failover, open connections to the old primary are dropped and have to reconnect.

Kubernetes cluster: 3 nodes across 3 availability zonesmssql-primaryServicemssql-replicasServiceZone A · node-1Zone B · node-2Zone C · node-3Primary (read/write)Secondary (sync, readable)Secondary (sync, readable)mssql-0mssql-1mssql-2sqlservrsqlservrsqlservr+ AG sidecar+ AG sidecar+ AG sidecarlog records, synchronous commitPersistent volumePersistent volumePersistent volumeLease renewed by the primary
A three-replica availability group across three zones. Each pod runs SQL Server and a sidecar that reports the replica's role. The primary's sidecar renews a Kubernetes Lease. If the Lease expires, the operator promotes a synchronized secondary and the Services follow the role labels.

Editions and availability groups

What you can build depends on the edition. The same image runs every edition, selected with the MSSQL_PID environment variable.

EditionAvailability groupsLicense
EnterpriseUp to 8 secondaries, 5 of them synchronous. Readable secondaries, contained and distributed availability groupsPaid, per core
StandardBasic availability groups only: two replicas, one database per group, no readable secondaryPaid, per core
DeveloperSame as EnterpriseFree, not for production
ExpressNoneFree, limited to 4 cores, about 1.4 GB of buffer pool, and 50 GB per database in 2025

A three-replica availability group with readable secondaries, as in the diagram above, needs Enterprise edition in production.

Contained availability groups

A classic availability group replicates user databases only. Logins, SQL Server Agent jobs, and linked servers live in the master and msdb databases of each instance, and someone has to keep them in sync. Otherwise, after a failover, applications can't log in. SQL Server 2022 added contained availability groups, which carry their own master and msdb, so logins and jobs fail over with the data. They need Enterprise edition, and they're the simplest way to avoid login drift between replicas.

Testing failover

Test failover the way it will happen in production, not just by deleting a pod. In our own testing of a three-replica availability group, we ran pod deletes, node drains, hard kills, and network partitions while a client wrote a row every 100 ms. Planned failovers paused writes for 2 to 10 seconds, a hard kill for about 14 seconds in most runs, and a network partition for up to 78 seconds. Across 20 runs on the final build, no acknowledged write was lost. The method and results are in Chaos Testing SQL Server Availability Groups on Kubernetes.

Pod anti-affinity and zones

Each replica has to be on its own node, or a single node failure can take out the primary and its synchronous secondary together. Use required, not preferred, anti-affinity in production:

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - topologyKey: kubernetes.io/hostname
      labelSelector:
        matchLabels:
          app.kubernetes.io/instance: mssql

Spreading replicas across availability zones with topologySpreadConstraints protects against a zone outage. Keep in mind that every synchronous commit now crosses a zone boundary, so measure commit latency on your cloud before you commit to it.

Disaster recovery across clusters

An availability group lives inside one Kubernetes cluster. For a second region or cluster, there are three options:

  • Distributed availability groups link two availability groups, each in its own cluster, with asynchronous replication between them. They need Enterprise edition, and few operators automate them, so plan for manual failover.
  • Log shipping restores the primary's log backups on a standby instance on a schedule. It's simple and works on Standard edition.
  • Restoring from backups in another region. It's the slowest to recover, but it needs only object storage replicated to that region.

3. Compute and storage strategy

Getting resources right is the difference between a solid database and one that the Linux OOM killer stops at 3 AM. With SQL Server, memory needs the most care, and CPU sizing is also a licensing decision.

Guaranteed QoS (requests equal limits)

Set requests equal to limits for CPU and memory in every container of the pod, including sidecars. That puts the pod in the Guaranteed QoS class, the last to be killed or evicted when a node runs low on memory. A Burstable SQL Server pod, with limits higher than requests, is one of the first to go.

resources:
  requests:
    memory: 32Gi
    cpu: "8"
  limits:
    memory: 32Gi
    cpu: "8"

Memory: make SQL Server see the container limit

SQL Server on Linux caps its own memory with memory.memorylimitmb, which defaults to 80% of the memory it can see. The catch is what it can see. SQL Server honors cgroup v2 memory limits starting with SQL Server 2022 CU20 and SQL Server 2025. Current Kubernetes distributions use cgroup v2, and on those nodes older builds see the node's total memory instead of the container limit, size their caches for it, and get OOM-killed when they cross the limit.

Set the limit explicitly so it holds on every version. The MSSQL_MEMORY_LIMIT_MB environment variable sets memory.memorylimitmb, and about 80% of the container limit leaves room for memory SQL Server uses outside its own accounting:

env:
- name: MSSQL_MEMORY_LIMIT_MB
  value: "26214"   # ~80% of a 32Gi limit

If you also set max server memory with sp_configure, keep it below that value. Check the setting again whenever you change the container's memory limit.

CPU and licensing

SQL Server licensing in containers follows Microsoft's rules for virtual environments. You either license the cores allocated to each container, with a minimum of four core licenses per container, or license every physical core of the hosts with Enterprise edition and Software Assurance and run as many containers as you like. So:

  • Set CPU limits in whole cores. A container limited to 2 CPUs still needs 4 core licenses, so production instances smaller than 4 cores rarely save money.
  • With Software Assurance, a passive secondary used only for failover doesn't need its own license. Readable secondaries do.
  • Use Developer edition in every non-production environment. It's free and has the full Enterprise feature set.
  • Standard edition is capped at 32 cores and a 256 GB buffer pool in SQL Server 2025, and 24 cores and 128 GB in 2022. Beyond that, extra resources go unused.
  • Set MAXDOP to match the container's CPU count, not the node's.

Separate data, log, and tempdb

By default, SQL Server keeps everything under /var/opt/mssql on one volume. For production, give its three kinds of files their own volumes:

FilesI/O patternStorage
Transaction logSmall sequential writes. Every commit waits for them, on the primary and on each synchronous secondaryThe lowest-latency volume you have
Data filesRandom reads and writes, in bursts at checkpointsNetwork or local SSD, sized for capacity and IOPS
tempdbHeavy, short-lived reads and writesLocal SSD or an emptyDir. tempdb is rebuilt on every restart, so it doesn't need to survive

Point SQL Server at the volumes in mssql.conf with filelocation.defaultdatadir and filelocation.defaultlogdir, and move tempdb with ALTER DATABASE. SQL Server runs as user ID 10001 in the official image, so set fsGroup: 10001 in the pod's security context, or it can't write to the volumes. Use XFS or ext4.

Network-attached or local NVMe

Network-attached volumes such as EBS, Azure Disk, or Ceph survive a node failure: the replacement pod re-attaches the same volume, and an availability group replica only has to catch up on the log it missed. Local NVMe is much faster, but a node failure takes the data with it, and the replacement replica has to copy every database from the primary with automatic seeding. For a large database that takes hours and loads the primary. Use local NVMe only with availability groups of at least three replicas, and with current backups.

Network-attachedLocal NVMe SSD
LatencyAbout 1 msAbout 0.1 ms
Node failureVolume re-attaches, and the replica catches up from the logData is lost, and the replica is re-seeded in full
Standalone instancesRecover on another nodeRecover from backup only
Best forMost workloads, and every standalone instanceLatency-sensitive availability groups with three or more replicas

4. Monitoring SQL Server on Kubernetes

SQL Server exposes nearly everything through dynamic management views (DMVs). On Kubernetes, the usual approach is sql_exporter, which runs T-SQL queries against those views and publishes the results as Prometheus metrics. Run it as a sidecar in each SQL Server pod, and let Prometheus Operator find it with a PodMonitor, so you never hardcode scrape targets.

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
spec:
  selector:
    matchLabels:
      app.kubernetes.io/instance: mssql
  podMetricsEndpoints:
  - port: metrics
    path: /metrics

For query-level analysis, use Query Store. It's on by default for new databases since SQL Server 2022, and it keeps plan and runtime history inside each database, so it fails over with the availability group.

SQL Server-specific metrics

Generic Kubernetes metrics tell you the pod is alive. These tell you whether SQL Server is healthy:

MetricSourceWhat to watch
Availability group healthsynchronization_health, log_send_queue_size, and redo_queue_size in sys.dm_hadr_database_replica_statesAlert when a replica stops being healthy or synchronized, or when either queue keeps growing. A large redo queue means a long failover.
Wait statisticssys.dm_os_wait_statsThe top waits show the bottleneck. WRITELOG points to log storage, HADR_SYNC_COMMIT to the synchronous secondary, and PAGEIOLATCH_* to data reads.
Memory pressurePage life expectancy and memory grants pending, from sys.dm_os_performance_countersA falling page life expectancy means the working set doesn't fit in the buffer pool. Pending grants mean queries are waiting for memory.
Transaction logsys.dm_db_log_space_usage, and log_reuse_wait_desc in sys.databasesLOG_BACKUP means log backups stopped and the log will grow until the volume fills. AVAILABILITY_REPLICA means a secondary is behind.
Blocking and deadlockssys.dm_exec_requests and the deadlock counterThe trend matters more than single events. Query Store and the system_health session show which queries.

Adapting monitoring for Kubernetes

  • Alert on availability group health, not just pod restarts. A Running pod can have stopped synchronizing.
  • Alert on unexpected primary changes, which point to evictions, node pressure, or network problems.
  • Alert when a container nears its memory limit. On older SQL Server builds, an OOM kill is the first sign that the memory limit isn't set.
  • Track PVC capacity, especially for the log volume. A database in the full recovery model without working log backups fills it.
  • Add your own metrics as T-SQL queries in the exporter's configuration, for example tempdb usage or application-specific counters.

5. Backup strategies

SQL Server's own backups are the right tool on Kubernetes, too. Since SQL Server 2022, BACKUP TO URL writes directly to S3-compatible object storage, so backups need no extra tools, and they're ordinary .bak files that any SQL Server can restore.

Backup types and recovery models

BackupWhat it containsTypical schedule
FullThe whole database. The base of every restoreWeekly or daily
DifferentialEverything changed since the last full backupDaily, between full backups
Transaction logEvery transaction since the previous log backup. Enables point-in-time recoveryEvery 5 to 15 minutes

Log backups need the full recovery model, which availability groups require anyway. In the full recovery model, the log is truncated only after a log backup, so log backups are also what keeps the log volume from filling up.

In an availability group, you can offload backups to a secondary. Secondaries support copy-only full backups and regular log backups, but not differential backups. sys.fn_hadr_backup_is_preferred_replica tells a backup job whether it's running on the replica the group prefers.

Backing up to S3

SQL Server authenticates to S3 with a credential named after the bucket URL, and writes to an s3:// URL over HTTPS. For a self-signed endpoint, such as a MinIO server inside the cluster, add its CA to the container's trust store.

CREATE CREDENTIAL [s3://s3.example.com/sql-backups]
  WITH IDENTITY = 'S3 Access Key',
       SECRET = '<access key ID>:<secret key>';

BACKUP DATABASE [sales]
  TO URL = 's3://s3.example.com/sql-backups/sales/full-2026-10-19.bak'
  WITH COMPRESSION, MAXTRANSFERSIZE = 20971520;

BACKUP LOG [sales]
  TO URL = 's3://s3.example.com/sql-backups/sales/log-2026-10-21-1430.trn'
  WITH COMPRESSION, MAXTRANSFERSIZE = 20971520;

Each backup file is uploaded in parts, and S3 limits how many parts one object can have. For large databases, split the backup across several URLs. SQL Server 2019 can't write to S3, so on 2019 you back up to a volume and copy the files to object storage yourself. Express edition doesn't support S3 backups at all.

Point-in-time recovery

To undo a mistake, such as a DELETE without a WHERE clause, you restore the last full backup, then the latest differential, then every log backup after it, and stop just before the mistake:

RESTORE DATABASE [sales] FROM URL = '.../full-2026-10-19.bak'  WITH NORECOVERY;
RESTORE DATABASE [sales] FROM URL = '.../diff-2026-10-21.bak'  WITH NORECOVERY;
RESTORE LOG [sales] FROM URL = '.../log-2026-10-21-1415.trn'   WITH NORECOVERY;
RESTORE LOG [sales] FROM URL = '.../log-2026-10-21-1430.trn'
  WITH STOPAT = '2026-10-21T14:31:00', RECOVERY;
Full backupDifferentiallog backups replayedSun 01:00Wed 01:00STOPAT Wed 14:31DELETE without WHERE at 14:32
A point-in-time restore uses Sunday's full backup, Wednesday's differential, and the log backups after it, and stops one minute before the bad DELETE. Log backups before the differential aren't needed.

The chain has to be complete. A missing log backup breaks recovery for every point after it, so a retention policy that deletes a full backup also deletes the ability to restore any later point that depends on it. Restore drills catch both problems.

Snapshot backups for very large databases

For multi-terabyte databases, even a fast backup to S3 takes hours. SQL Server 2022 added T-SQL snapshot backups: ALTER DATABASE ... SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON freezes writes, you take a storage snapshot, for example a Kubernetes VolumeSnapshot, and BACKUP DATABASE ... WITH METADATA_ONLY records it and resumes writes. The snapshot works as a full backup that log backups can be applied to. Few operators automate this yet.

Backup architecture decisions

ApproachSetupWhy
RecommendedNative full, differential, and log backups to S3 in a separate account or region, with log backups every 5 to 15 minutesPoint-in-time recovery, standard .bak files, and backups that survive the loss of the cluster. Test restores regularly, because an untested backup is not a backup.
Acceptable for very large databasesSnapshot backups of the volumes, plus native log backups to S3Fast full backups and restores, but snapshots usually stay in the same storage system as the database.
Acceptable for dev and stagingS3-compatible storage such as MinIO or Ceph in the same Kubernetes clusterConvenient, but it shares the cluster's fate. If you lose the cluster, you lose the backups.
Not recommendedFull backups to a PVC from a cron job, without log backupsNo point-in-time recovery, the same blast radius as the database, and a log volume that grows until it fills.

6. SQL Server operators compared

There's no dominant open source SQL Server operator. Microsoft previewed an availability group operator with early SQL Server 2019 builds but didn't ship it. Today its documentation covers plain StatefulSets, and for availability groups on Kubernetes it points to a partner product, DH2i DxOperator. The rest of the field is commercial, apart from one new community project.

OperatorMaintainerLicenseHigh availabilityBackups
Solanica MSSQL OperatorSolanicaCommercialAvailability groups with EXTERNAL cluster type and Lease-based failoverNative backups to S3, schedules, and point-in-time recovery
DH2i DxOperatorDH2iCommercial, with a free developer licenseAvailability groups managed by DxEnterprise clusteringNot among its listed features
KubeDBAppsCodeProprietaryAvailability groups with Raft-based leader electionKubeStash, with continuous archiving through WAL-G
KubeBlocksApeCloudEnterprise edition onlyAvailability groups with automatic failoverBuilt into KubeBlocks
sql-on-k8s-operatorAnthony Nocentino (community)No license file yetAvailability groups with EXTERNAL cluster type, health checks with sp_server_diagnosticsNot built in
SQL Managed Instance enabled by Azure ArcMicrosoftCommercial, billed through AzureManaged by the Arc data controllerBuilt in, with point-in-time restore

Solanica MSSQL Operator

Our operator, available through OpenEverest as a commercial provider. It runs standalone instances and availability groups with the EXTERNAL cluster type. A sidecar in each pod renews a Kubernetes Lease while its replica is primary, and checks engine health with sp_server_diagnostics. The operator promotes only synchronized secondaries and never forces a data-losing failover unless you allow it. It also covers TLS, Prometheus metrics, rolling version and edition upgrades, and native backups to S3 with point-in-time recovery for standalone instances. Point-in-time recovery for availability groups and cross-cluster disaster recovery aren't available yet.

DH2i DxOperator

Part of DH2i's DxEnterprise high-availability software, and the operator Microsoft's documentation uses for availability groups on Kubernetes. DxEnterprise manages the availability group and can span Kubernetes, Linux, and Windows, which helps with hybrid setups and migrations. It focuses on availability. Plan backups separately.

KubeDB

Part of a multi-database operator suite with one management plane. SQL Server support covers standalone instances and availability groups, with Raft for leader election, an arbiter for even-sized clusters, backups with KubeStash, and continuous log archiving with WAL-G. It is not open source, and production use needs a license.

KubeBlocks

An open source multi-database platform from ApeCloud. Its SQL Server support, with availability groups and automatic failover, is part of the commercial enterprise edition rather than the open source add-ons.

sql-on-k8s-operator

A community operator released in April 2026 by Anthony Nocentino. It bootstraps availability groups with the EXTERNAL cluster type and fails over in the style of Microsoft's Pacemaker agent. It doesn't manage backups, and its repository has no license file yet, so check its status before you depend on it.

SQL Managed Instance enabled by Azure Arc

Microsoft's own way to run SQL Server on Kubernetes: Azure SQL Managed Instance, deployed on your cluster by the Azure Arc data controller. It's managed and billed through Azure, so it suits teams that are standardizing on Azure management rather than leaving it.

OpenEverest

OpenEverest is a CNCF Sandbox project for running databases on Kubernetes through one control plane. SQL Server is available as a commercial provider from Solanica, built on the Solanica MSSQL Operator. You get availability groups, backups to S3-compatible storage, and monitoring from the same UI and API you use for PostgreSQL, MySQL, and MongoDB.

Choosing between running SQL Server yourself and a managed service? See Solanica vs Azure SQL Managed Instance.

Related whitepapers

Run SQL Server on Kubernetes with Solanica

Solanica runs SQL Server on any Kubernetes cluster with availability groups, automatic failover, native backups with point-in-time recovery, and monitoring, on infrastructure you own. Talk to us about your setup, licensing, or a move off a managed service.