Topic page · SQL Server on Linux and Docker

Linux, containers or Windows? Where SQL Server belongs

The question usually comes from outside. IT is moving to Linux, the development team ships everything as containers, or the next database server is supposed to do without Windows. SQL Server has supported this since 2017. But whether it fits your application is rarely decided by the database engine. It comes down to three questions: Which features does the application use? Who runs the server? And how will it be made highly available?

Updated Nov 12, 2026 · approx. 10 min read

Part 1 · ArchitectureSQL query 1Lab SQL Server 2017–2025
Linux VMContainerT-SQL jobs, SQL loginsworksworksWindows logins (AD)with effortuntestedPowerShell and SSIS jobsmissingmissingFILESTREAMmissingmissingSeveral instances per hostmissingworksAutomatic failoverwith effortuntestedTest and CI instancescumbersomeworks

This page walks through the three questions, summarizes them in a decision table and ends with a check query for your existing instance.

In 60 seconds

  • The license is the same. Microsoft licenses SQL Server the same way for Windows and Linux. Costs change at most for the operating system, and for production Microsoft recommends distributions with a support contract.
  • For development and CI, the container is usually the better choice. It starts in seconds, has a fixed version and can be thrown away.
  • For production, the feature list decides first. Agent jobs with PowerShell or SSIS, FILESTREAM, merge replication or linked servers to Oracle force a rebuild or argue against Linux.
  • High availability works, but differently. Pacemaker takes over from the Windows failover cluster. It is a different tool with different operational knowledge.
  • Containers in production are supported, under conditions. Microsoft ties support to its images and to the same distribution in container and host. The pitfalls from part 3 come on top.

Three usage scenarios

Development and CI in a container

This is where the benefit is greatest and the risk smallest. In our lab, a container with the Developer edition started in 7 to 14 seconds, every version and every update is its own image tag, and after the test run the container is gone again. The Developer edition is free, but licensed only for development and test. What to watch for so that tests run reproducibly is in part 3: a fixed CU tag instead of latest, waiting for a successful query instead of for the running container, and for tests against Standard on SQL Server 2025, setting MSSQL_PID=StandardDeveloper.

Production on a Linux server or Linux VM

The classic setup, just on a different platform. SQL Server is installed and updated as a package of the distribution, configured with mssql-conf and controlled with systemctl. There is exactly one instance per machine. This path fits if your operations team already patches, monitors and backs up Linux servers and the application needs none of the missing features.

Production in a container or in Kubernetes

Microsoft supports SQL Server in containers on Docker, Kubernetes and OpenShift, but according to the support policy under conditions:

  • Official images. Supported are Microsoft's images and your own images that build on them and change nothing under /opt/mssql; fully self-built images only on a supported distribution. In a support case, Microsoft can require you to reproduce the problem on the official image.
  • Same distribution in container and host. Distribution and major version of the image must match those of the host. An image based on Ubuntu 22.04 on a host with Ubuntu 24.04 is not supported under this rule.
  • Problems of the container runtime you clarify with its vendor, not with Microsoft.
  • Features that depend on the Azure Arc agent, such as sign-in through Microsoft Entra, do not exist in the container.

For Kubernetes, Microsoft recommends a StatefulSet with one SQL Server container per pod and, for production, the "Guaranteed" QoS class, that is, equal values for requested and limited resources.

What speaks for Linux

  • Operations knows Linux. If patching, monitoring, backup and automation already run on Linux tools, SQL Server fits in there instead of remaining a Windows island.
  • The platform is a container cluster anyway. Then SQL Server in a container is the path with the fewest special rules, provided the conditions above are met.
  • Many small instances. For test, training and development, containers are simpler than named instances on a Windows server.
  • Operating system costs. Windows Server is no longer needed, but for production a support contract for the distribution usually comes in its place. Whether that is cheaper depends on your contracts; the SQL Server license does not change.

What speaks against it

  • The application uses features that are missing. The complete list is in the overview. The check query below finds the most common cases on your instance.
  • High availability requires new knowledge. Availability groups with automatic failover need Pacemaker on Linux. The listener is an IP address with a DNS entry, and distributed transactions through MSDTC are not supported in availability groups on Linux. A failover cluster instance has no virtual network name; you create the DNS entry yourself.
  • Windows logins take setup. The host must join Active Directory, SQL Server needs a keytab with SPNs, and clients connect through the fully qualified host name. Domain local groups are not supported.
  • Familiar tools are missing. No Configuration Manager, no maintenance plans from SSMS, no Agent steps for PowerShell. Scripts that access the database server through PowerShell or WMI have to be rebuilt.
  • The vendor has a say. For off-the-shelf software, the vendor's approval counts. If it supports SQL Server only on Windows, technical feasibility helps little.

Decision table

RequirementLinux server or VMContainerNote
Application with SQL logins, Agent jobs only in T-SQLworksworks–
Windows logins from Active Directorywith effortnot testedKeytab, SPNs, connection through the fully qualified host name
Agent jobs with PowerShell, CmdExec or SSISmissingmissingConvert jobs to T-SQL or schedule them outside SQL Server
AlertsmissingmissingMonitoring outside SQL Server
FILESTREAM or FileTablesmissingmissingRedesign file storage
Merge replicationmissingmissing–
Linked server to Oracle, ODBC or ExcelmissingmissingMicrosoft points to PolyBase
Reporting or Analysis Services on the same servermissingmissingReporting Services on Windows, the catalog may reside on Linux
Several instances on one machinemissingworksone container per instance, own port
Automatic failoverwith effortnot testedPacemaker; a pod restart by Kubernetes is not a failover to a second data copy
Distributed transactions (MSDTC)checkchecknot supported in availability groups on Linux
Many short-lived test and CI instancescumbersomeworksDeveloper edition, fixed CU tag

"Not tested" means: The sources we read do not answer it unambiguously for containers, and we did not set it up in the lab.

Containers in production: what goes with it

Anyone who runs SQL Server in a container in production needs a few fixed rules in addition to the support conditions. The measurements are in part 3.

  • Shut down cleanly. On SQL Server 2022 and 2025, docker stop does not end the instance in an orderly way in today's images. Until Microsoft fixes the startup script, a step is needed that sends the stop signal directly to the SQL Server process, in Kubernetes for example as preStop.
  • Set limits yourself. Memory through MSSQL_MEMORY_LIMIT_MB with headroom below the container limit, CPUs through pinned CPUs or MAXDOP. SQL Server does not react to a CPU quota alone.
  • Data on persistent volumes with XFS or ext4, not in the container's file system.
  • Updates as a new image with a fixed CU tag, rolled out by a new container on the same volumes.

Check query: What does my instance use that is missing on Linux?

The query runs on your existing instance, usually under Windows, and checks 20 items from Microsoft's list of features missing on Linux: instance settings, logins and endpoints, Agent jobs, alerts and maintenance plans in msdb and, per database, FILESTREAM, FileTables, CLR assemblies and secure enclaves. The result has one row per check, hits first. The classification column says what a hit means:

  • missing on Linux: must be rebuilt before a move.
  • with effort: works on Linux, but needs setup, for example certificates instead of Windows authentication for endpoints.
  • check: depends on what the setting is used for.

Query: What is missing on Linux?

Query 1: What is missing on Linux? · read-only · tested in the PSG lab, versions in the header
/* PSG Stack · SQL Server on Linux and Docker · Query 1   What does this instance use that is missing on Linux or needs rework?   Meant for your existing instance, before you decide on a move to Linux or into a   container. Result: one row per check, hits first.    !!! WARNING — please read before running !!!   - Read-only. The query changes no data and no setting; it only creates three     temporary tables in tempdb.   - Permissions: simplest as sysadmin. Otherwise VIEW SERVER STATE, VIEW ANY DEFINITION,     read access in msdb (db_datareader) and access to the databases. Without VIEW ANY     DEFINITION, SQL Server hides other logins, endpoints and linked servers; the note     column reports this, as well as anything that could not be read.   - MAXDOP 1: every part of the query runs serially.   - It goes through every database that is online and that you can access. With a very     large number of databases, run it outside peak hours.   - Basis: Microsoft's list of features not available on Linux for SQL Server 2022 and     2025 (read on Nov 11 and 12, 2026). No substitute for testing the application; it does     not check MSDTC, managed backup, Analysis Services or Reporting Services.   - SQL Server 2017 and later. Tested in the PSG lab on 2017, 2019, 2022 and 2025 in Linux     containers, with test objects as far as Linux allows creating them (Nov 12, 2026). Not     tested on Windows, where it will run before a move; it only reads catalog views that     exist there as well.*/SET NOCOUNT ON;SET QUOTED_IDENTIFIER ON;   -- sqlcmd starts without it; the XML methods below need it IF OBJECT_ID(N'tempdb..#checks') IS NOT NULL DROP TABLE #checks;IF OBJECT_ID(N'tempdb..#hits')   IS NOT NULL DROP TABLE #hits;IF OBJECT_ID(N'tempdb..#db')     IS NOT NULL DROP TABLE #db;CREATE TABLE #checks (nr int PRIMARY KEY, area nvarchar(30), check_name nvarchar(120), classification nvarchar(30), note nvarchar(200) NULL);CREATE TABLE #hits   (nr int, object_name nvarchar(300));CREATE TABLE #db     (name sysname, checked bit); INSERT #checks (nr, area, check_name, classification) VALUES ( 1, N'Instance',    N'Named instance (Linux: one default instance per host)',           N'with effort'), ( 2, N'Instance',    N'xp_cmdshell enabled',                                             N'missing on Linux'), ( 3, N'Instance',    N'Custom extended stored procedures',                               N'missing on Linux'), ( 4, N'Instance',    N'Buffer pool extension active',                                    N'missing on Linux'), ( 5, N'Instance',    N'Linked servers to sources other than SQL Server',                 N'missing on Linux'), ( 6, N'Instance',    N'Ad Hoc Distributed Queries enabled',                              N'check'), ( 7, N'Security',    N'Windows logins and groups',                                       N'with effort'), ( 8, N'Security',    N'Endpoints with Windows authentication',                           N'with effort'), ( 9, N'Security',    N'EKM providers (Linux: Azure Key Vault only, 2022 CU12 and later)', N'check'), (10, N'HA',          N'Database mirroring',                                              N'missing on Linux'), (11, N'Agent',       N'Job steps CmdExec, PowerShell, SSIS, SSAS, Queue Reader',         N'missing on Linux'), (12, N'Agent',       N'Alerts',                                                          N'missing on Linux'), (13, N'Agent',       N'Maintenance plans',                                               N'missing on Linux'), (14, N'Services',    N'SSIS catalog (SSISDB) present',                                   N'check'), (15, N'Databases',   N'Merge replication (published)',                                   N'missing on Linux'), (16, N'Databases',   N'Stretch Database',                                                N'missing on Linux'), (17, N'Databases',   N'FILESTREAM filegroups',                                           N'missing on Linux'), (18, N'Databases',   N'FileTables',                                                      N'missing on Linux'), (19, N'Databases',   N'CLR assemblies with EXTERNAL_ACCESS or UNSAFE',                   N'missing on Linux'), (20, N'Databases',   N'Always Encrypted with secure enclaves',                           N'missing on Linux'); /* Without VIEW ANY DEFINITION the catalog views show only part of the objects */IF HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW ANY DEFINITION') = 0  UPDATE #checks SET note = N'incomplete: only partly visible without VIEW ANY DEFINITION' WHERE nr IN (3, 5, 7, 8, 9, 19); /* 1 · Instance and security */INSERT #hits SELECT 1, CONVERT(nvarchar(300), SERVERPROPERTY('InstanceName')) WHERE SERVERPROPERTY('InstanceName') IS NOT NULL;INSERT #hits SELECT 2, N'xp_cmdshell = 1' FROM sys.configurations WHERE name = N'xp_cmdshell' AND CONVERT(int, value_in_use) = 1 OPTION (MAXDOP 1);INSERT #hits SELECT 3, name FROM master.sys.extended_procedures WHERE is_ms_shipped = 0 OPTION (MAXDOP 1);BEGIN TRY  INSERT #hits SELECT 4, path FROM sys.dm_os_buffer_pool_extension_configuration WHERE state <> 0 OPTION (MAXDOP 1);END TRY BEGIN CATCH UPDATE #checks SET note = N'not readable: ' + ERROR_MESSAGE() WHERE nr = 4; END CATCH;INSERT #hits SELECT 5, name + N' (' + provider + N')' FROM sys.servers WHERE is_linked = 1 AND NOT (product = N'SQL Server' OR provider LIKE N'SQLNCLI%' OR provider LIKE N'MSOLEDBSQL%' OR provider = N'SQLOLEDB') OPTION (MAXDOP 1);INSERT #hits SELECT 6, N'Ad Hoc Distributed Queries = 1' FROM sys.configurations WHERE name = N'Ad Hoc Distributed Queries' AND CONVERT(int, value_in_use) = 1 OPTION (MAXDOP 1);INSERT #hits SELECT 7, name FROM sys.server_principals WHERE type IN ('U', 'G') AND name NOT LIKE N'NT SERVICE\%' AND name NOT LIKE N'NT AUTHORITY\%' AND name NOT LIKE N'BUILTIN\%' OPTION (MAXDOP 1);INSERT #hits SELECT 8, name + N' (' + connection_auth_desc + N')' FROM sys.database_mirroring_endpoints WHERE connection_auth_desc LIKE N'%NTLM%' OR connection_auth_desc LIKE N'%KERBEROS%' OR connection_auth_desc LIKE N'%NEGOTIATE%' OPTION (MAXDOP 1);INSERT #hits SELECT 9, name FROM sys.cryptographic_providers OPTION (MAXDOP 1);INSERT #hits SELECT 10, DB_NAME(database_id) FROM sys.database_mirroring WHERE mirroring_guid IS NOT NULL OPTION (MAXDOP 1);INSERT #hits SELECT 14, name FROM sys.databases WHERE name = N'SSISDB' OPTION (MAXDOP 1);INSERT #hits SELECT 15, name FROM sys.databases WHERE is_merge_published = 1 OPTION (MAXDOP 1);IF EXISTS (SELECT 1 FROM sys.all_columns WHERE object_id = OBJECT_ID(N'sys.databases') AND name = N'is_remote_data_archive_enabled')  EXEC (N'INSERT #hits SELECT 16, name FROM sys.databases WHERE is_remote_data_archive_enabled = 1 OPTION (MAXDOP 1);'); /* 2 · SQL Server Agent and maintenance plans in msdb */BEGIN TRY  INSERT #hits SELECT 11, j.name + N' / ' + s.step_name + N' (' + s.subsystem + N')'  FROM msdb.dbo.sysjobsteps AS s JOIN msdb.dbo.sysjobs AS j ON j.job_id = s.job_id  WHERE s.subsystem IN (N'CmdExec', N'PowerShell', N'SSIS', N'ANALYSISQUERY', N'ANALYSISCOMMAND', N'QueueReader')  OPTION (MAXDOP 1);END TRY BEGIN CATCH UPDATE #checks SET note = N'not readable: ' + ERROR_MESSAGE() WHERE nr = 11; END CATCH;BEGIN TRY  INSERT #hits SELECT 12, name FROM msdb.dbo.sysalerts OPTION (MAXDOP 1);END TRY BEGIN CATCH UPDATE #checks SET note = N'not readable: ' + ERROR_MESSAGE() WHERE nr = 12; END CATCH;BEGIN TRY  INSERT #hits SELECT 13, name FROM msdb.dbo.sysmaintplan_plans OPTION (MAXDOP 1);END TRY BEGIN CATCH UPDATE #checks SET note = N'not readable: ' + ERROR_MESSAGE() WHERE nr = 13; END CATCH; /* 3 · Per database: FILESTREAM, FileTables, CLR, secure enclaves */DECLARE @db sysname, @sql nvarchar(max),        @enclave bit = CASE WHEN EXISTS (SELECT 1 FROM sys.all_columns WHERE object_id = OBJECT_ID(N'sys.column_master_keys')                                         AND name = N'allow_enclave_computations') THEN 1 ELSE 0 END;INSERT #db SELECT name, 0 FROM sys.databases WHERE database_id <> 2 OPTION (MAXDOP 1);DECLARE dbs CURSOR LOCAL FAST_FORWARD FOR  SELECT name FROM sys.databases WHERE database_id <> 2 AND state = 0 AND HAS_DBACCESS(name) = 1;OPEN dbs; FETCH NEXT FROM dbs INTO @db;WHILE @@FETCH_STATUS = 0BEGIN  SET @sql = N'USE ' + QUOTENAME(@db) + N';    INSERT #hits SELECT 17, DB_NAME() + N'': '' + name FROM sys.database_files WHERE type = 2 OPTION (MAXDOP 1);    INSERT #hits SELECT 18, DB_NAME() + N'': '' + name FROM sys.tables WHERE is_filetable = 1 OPTION (MAXDOP 1);    INSERT #hits SELECT 19, DB_NAME() + N'': '' + name + N'' ('' + permission_set_desc + N'')''      FROM sys.assemblies WHERE is_user_defined = 1 AND permission_set > 1 OPTION (MAXDOP 1);'    + CASE WHEN @enclave = 1 THEN N'    INSERT #hits SELECT 20, DB_NAME() + N'': '' + name FROM sys.column_master_keys WHERE allow_enclave_computations = 1 OPTION (MAXDOP 1);' ELSE N'' END;  BEGIN TRY    EXEC sys.sp_executesql @sql;    UPDATE #db SET checked = 1 WHERE name = @db;  END TRY BEGIN CATCH END CATCH;  FETCH NEXT FROM dbs INTO @db;END;CLOSE dbs; DEALLOCATE dbs;UPDATE #checks SET note = ISNULL(note + N'; ', N'') + CONVERT(nvarchar(10), (SELECT COUNT(*) FROM #db WHERE checked = 1)) + N' databases checked'  + ISNULL(N', not checked (offline or no access): ' + STUFF((SELECT N', ' + name FROM #db WHERE checked = 0 FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N''), N'')WHERE nr BETWEEN 17 AND 20; /* Result: hits first, at most five examples per check */SELECT c.nr, c.area, c.check_name,       COUNT(h.object_name) AS hits,       CASE WHEN COUNT(h.object_name) = 0 THEN N'-' ELSE c.classification END AS classification,       ISNULL(STUFF((SELECT TOP (5) N', ' + h2.object_name FROM #hits AS h2 WHERE h2.nr = c.nr ORDER BY h2.object_name              FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N''), N'') AS examples,       ISNULL(c.note, N'') AS noteFROM #checks AS c LEFT JOIN #hits AS h ON h.nr = c.nrGROUP BY c.nr, c.area, c.check_name, c.classification, c.noteORDER BY CASE WHEN COUNT(h.object_name) > 0 THEN 0 ELSE 1 END, c.nrOPTION (MAXDOP 1);

Sample output

From the lab: SQL Server 2025 CU10 in a container, with test objects, run with the recommended minimum permissions. The note column is omitted; of the rows without hits, only the first three are shown here:

Sample output
nr  area       check_name                                               hits  classification    examples
--  ---------  -------------------------------------------------------  ----  ----------------  ------------------------------------------------
6   Instance   Ad Hoc Distributed Queries enabled                       1     check             Ad Hoc Distributed Queries = 1
8   Security   Endpoints with Windows authentication                    1     with effort       lx_hadr (NEGOTIATE)
11  Agent      Job steps CmdExec, PowerShell, SSIS, SSAS, Queue Reader  2     missing on Linux  lx_job / cmd (CmdExec), lx_job / ps (PowerShell)
12  Agent      Alerts                                                   1     missing on Linux  lx_alert
20  Databases  Always Encrypted with secure enclaves                    1     missing on Linux  lx_app: lx_cmk
1   Instance   Named instance (Linux: one default instance per host)    0     -
2   Instance   xp_cmdshell enabled                                      0     -
3   Instance   Custom extended stored procedures                        0     -
…

How to read it: The two job steps and the alert must be replaced before a move, the endpoint needs certificates, and the column master key for secure enclaves shows that a database uses Always Encrypted with enclaves, which is missing on Linux.

What stood out in the lab was what SQL Server on Linux allows you to create: All four versions accepted the CmdExec and the PowerShell job step, the alert and the endpoint with Windows authentication. In contrast, they immediately rejected xp_cmdshell, FILESTREAM and a linked server over ODBC. So an object that can be created does not necessarily work, and a move by script can silently carry such objects along.

A result without hits does not mean the application runs on Linux. The query does not see what the application itself does, such as Windows paths in code or Windows logins of the clients. It does not check MSDTC, managed backup, or Analysis and Reporting Services. If the note column says "incomplete" or "not readable", the executing login lacks permissions.

Method and limits

The statements on license, platforms, features, support policy, Kubernetes and high availability come from Microsoft Learn (read on Nov 11 and 12, 2026). We read the feature list for SQL Server 2022 and 2025, not separately for 2017 and 2019. We tested the check query on Nov 12, 2026 on SQL Server 2017 CU31, 2019 CU32, 2022 CU27 and 2025 CU10 in Linux containers: on an empty instance, with test objects and with three permission levels. Limits:

  • We did not run it under Windows, where the query will usually run before a move. It only reads catalog views that exist there as well.
  • Several checks could not be triggered with real objects on Linux, because FILESTREAM, merge replication, mirroring, named instances or Windows logins cannot be created there. These branches ran only without hits.
  • We did not set up Pacemaker, Active Directory or Kubernetes in the lab. We did not measure whether objects that can be created but are not supported fail at runtime.

Further reading

  • Microsoft Learn: "Editions and supported features of SQL Server 2025 on Linux", "SQL Server on Linux frequently asked questions", "Support policy for SQL Server" (section on containers), "Deploy SQL Server Linux containers on Kubernetes with StatefulSets".
  • Microsoft Learn: "Availability groups for SQL Server on Linux" and the tutorial on Active Directory authentication for SQL Server on Linux.
  • In this series: Overview, Part 3: What goes wrong in the container.
  • At PSG: FCI vs. availability groups.

Before you decide

A move to Linux is rarely a pure infrastructure project. Run query 1 on today's instance, clarify the approval with the application vendor, and decide on high availability and sign-in before you order servers. We are glad to do this check together with you before planning and migration begin.

More parts of the series