The Short Version
For most of my career, "installing SQL Server" meant an ISO, a setup wizard, a checklist, and an afternoon. Today a SQL Server instance can be a single command that finishes in seconds and can be thrown away just as fast. Linux containers, run with Docker on a workstation or orchestrated by Kubernetes in a data center, change what a SQL Server instance is: not a hand-built server with a history, but a versioned artifact you start, test, replace and reproduce.
This article walks through why I think containers are the best default for SQL Server in development, test, CI and a growing number of production scenarios, compared with the three alternatives most teams still use: downloading and installing binaries, running a first-generation virtual machine, or buying a cloud-managed database service. It also lists where containers are not the answer, because pretending otherwise would cost you credibility the first time someone asks about SSRS.
Versus Downloading and Installing Binaries
The classic installer is the baseline everything else gets compared to, and it has well-known costs.
- Setup is slow and manual. Feature selection, collation, service accounts, data directories, tempdb files, max memory, MAXDOP. Even with a configuration file, you are driving a 30 to 60 minute process and then post-configuring.
- Every instance is a snowflake. Two servers installed six months apart by two people are never identical. Drift between dev, test and production is the quiet reason "it worked on my machine" still exists in database projects.
- Versions fight each other. Running SQL Server 2022 and 2025 side by side on one host means named instances, shared components, and the occasional setup failure that leaves a half-installed instance behind.
- Cleanup is never clean. Uninstalling leaves registry keys, folders, and orphaned logins in other tools. Resetting a test instance to a known state means a restore or a reinstall.
A container image bundles the engine, its libraries and its defaults into one immutable unit. Starting an instance becomes:
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<a strong password>" \
-e "MSSQL_PID=Developer" -p 1433:1433 \
-v sqldata:/var/opt/mssql --name sql2025 \
-d mcr.microsoft.com/mssql/server:2025-latest
Everything the instance needs is declared in that command or in a compose file next to your code. Want a different version? Change the tag. Want a clean slate? Remove the container and the volume, start again. Want two versions at once? Start two containers on two ports. There is no installer state to corrupt because there is no installer state.
Versus a First-Generation Virtual Machine
VMware, Hyper-V and similar platforms solved a real problem: many servers on fewer boxes. But a VM virtualizes hardware, so every SQL Server instance still drags a complete guest operating system along with it.
- Weight. A VM carries its own kernel, patch cycle, antivirus, backup agent and monitoring agent. A SQL Server container image is a few gigabytes, shares the host kernel, and starts in seconds rather than minutes.
- Density. On the same hardware you fit noticeably more small and medium instances as containers, because you are not paying the guest OS memory and CPU tax per instance.
- VM sprawl. Cloning a VM is easy, so teams clone. Months later nobody knows which of the 40 "SQLTEST" machines still matters, what patch level it has, or who owns it. An image in a registry has a tag, a digest and a build history.
- Provisioning is a ticket. Even in automated shops, a new VM usually passes through a request, a template, a domain join and a hardening step. A container starts from a file in source control.
- OS licensing and patching. Linux containers remove the Windows Server license and the monthly OS patch ritual for each guest. Patching becomes: pull a newer image, replace the container, keep the volume.
Fair note: A VM still gives stronger isolation than a container, and many production estates will keep running SQL Server in VMs for good reasons: existing tooling, Windows-only features, change-control processes built around VMs. On Windows and macOS desktops, Docker itself runs the Linux containers inside a lightweight VM (WSL2 or similar). That is fine for the workflow benefits described here, but it is not "bare metal" performance.
Versus a Cloud-Managed Database Service
Managed services remove real operational toil, and for many workloads they are the right choice. For the DBA who needs to understand, reproduce and test the engine, they come with limits.
- You do not get the same engine everywhere. Platform-as-a-service offerings differ from the box product in features, behavior and supported settings. Code that passes in a managed service can fail on-premises and the other way around. A container gives you the exact same SQL Server engine on a laptop, in a pipeline and in your own cluster.
- Cost shape. A throwaway test database in a managed service is billed while it exists, and spinning it up can take a long time. A container on hardware you already own costs nothing extra to start and nothing to stop.
- Version and patch control. Managed services decide when you patch. If you must rehearse an upgrade against a specific cumulative update, or reproduce a bug on the build your production server runs, you want to choose the image tag yourself.
- Lock-in and data residency. A container runs anywhere a container runtime runs: on-premises, in any cloud, in an air-gapped network. For regulated environments with data-residency or exit-strategy requirements, that portability matters.
- Access. No restrictions on instance-level configuration, trace flags, startup parameters, or the file system, within the limits of the container.
What Kubernetes Adds
Docker on a single host is excellent for development and test. Kubernetes turns the same containers into a platform you can operate.
- Declarative desired state. You describe the instance (image, memory limit, volume, secret, service) in YAML. The cluster makes it so, and keeps it so.
- Self-healing. If a node dies or the process crashes, the pod is rescheduled and attaches to its persistent volume. For many workloads this is a simpler recovery story than a hand-built failover cluster.
- Controlled upgrades. Changing the image tag in the manifest and applying it gives you a repeatable, reviewable, revertible patch process. The change lives in Git, not in a runbook that someone follows from memory.
- Resource governance. CPU and memory requests and limits are first-class settings. A noisy test instance cannot starve its neighbors.
- High availability options. Availability groups can be deployed on Kubernetes with an operator-based approach, so replicas are rebuilt from the same definition instead of by hand.
- One operating model. If your application tier already runs on Kubernetes, the database follows the same deployment pipeline, secrets handling, monitoring and policy controls.
The key requirement is storage. SQL Server is a stateful workload, and its behavior under Kubernetes is only as good as the persistent volumes behind it: latency, durability, and how volumes move between nodes. Treat the storage class as part of the database design, not as an afterthought.
Where Containers Pay Off Most for a DBA
Side-by-side versions
In my own lab I keep SQL Server 2022 and SQL Server 2025 running at the same time on the same machine, on different ports, each with its own volume. Testing a script or a tool against both versions is a matter of choosing a connection string. A compose file is all it takes:
services:
mssql-2022:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
ACCEPT_EULA: "Y"
MSSQL_PID: "Developer"
MSSQL_MEMORY_LIMIT_MB: "2048"
ports: [ "1433:1433" ]
volumes: [ "mssql-2022-data:/var/opt/mssql" ]
mssql-2025:
image: mcr.microsoft.com/mssql/server:2025-latest
environment:
ACCEPT_EULA: "Y"
MSSQL_PID: "Developer"
MSSQL_MEMORY_LIMIT_MB: "2048"
ports: [ "1434:1433" ]
volumes: [ "mssql-2025-data:/var/opt/mssql" ]
volumes:
mssql-2022-data:
mssql-2025-data:
Add a health check that runs SELECT 1 through sqlcmd, and your scripts can wait until the instance is genuinely ready instead of sleeping for a guessed number of seconds. The SA password in a lab compose file is acceptable. In anything shared, use secrets.
Availability groups without a domain or a cluster
A two-node availability group normally needs a Windows failover cluster and, in most setups, Active Directory. On Linux containers you can create an availability group with CLUSTER_TYPE = NONE between two containers on one bridge network. There is no WSFC and no domain. That is not a production high availability design (nothing orchestrates failover), but it is perfect for learning replica behavior, testing monitoring queries for send and redo queues, and rehearsing seeding and failover commands in minutes instead of days.
Upgrade rehearsals and regression testing
Restore last night's backup into a container running the target version, run your test suite, and delete the container. Do it in a CI pipeline and every change to a stored procedure or migration script is verified against a real engine before it goes anywhere near production. Do it for a new cumulative update and you know the result before the maintenance window.
Restore verification
A backup you have never restored is a hope, not a backup. A container makes restore testing cheap enough to automate: start, restore, run DBCC CHECKDB, report, remove.
Training and demos
A broken lab takes one command to rebuild. Participants in a workshop get identical environments, and a demo that goes wrong is reset in seconds.
Side-by-Side Summary
| Criterion | Installer | First-gen VM | Managed service | Linux container |
|---|---|---|---|---|
| Time to a new instance | Tens of minutes to hours | Minutes to days (provisioning) | Minutes to hours | Seconds |
| Reproducibility | Low, drifts | Medium, clones drift | Vendor-defined | High, image plus manifest |
| Several versions side by side | Awkward | One VM each | Separate resources | Trivial |
| Resource overhead | Host OS only | Full guest OS per instance | Vendor-managed | Shared kernel, small |
| Reset to clean state | Restore or reinstall | Snapshot or rebuild | Recreate resource | Remove and start |
| Same engine as on-premises | Yes | Yes | Not always | Yes |
| Patch and version control | Yours | Yours | Vendor's schedule | Yours, by image tag |
| Portability | Low | Medium | Low, vendor-bound | High |
| Fits CI pipelines | Poorly | Poorly | Costly | Natively |
Where Containers Are Not the Answer
Being honest about the limits is what makes the case credible.
- Windows-only components. The SQL Server database engine runs on Linux, but not every product in the family does. SSRS, Power BI Report Server, SSAS multidimensional and the full SSIS runtime are Windows products. Plan for them separately.
- Windows authentication needs setup. Active Directory authentication on Linux works through Kerberos, keytabs and SPNs. It is doable, but it is a project, not a checkbox. Without it, a lab runs on SQL authentication.
- Clustering is different. Failover cluster instances rely on Windows Server Failover Clustering. On Linux the equivalent is a Pacemaker-based stack, and on Kubernetes you rely on pod rescheduling and storage. Know which model you are choosing.
- Persistent storage is your responsibility. A container without a volume loses its data when it is removed. In production, storage performance, snapshots and backups must be designed deliberately.
- Licensing and support still apply. The Developer edition is for non-production use. Production containers need proper SQL Server licensing, the same as any other deployment. Check your agreement and Microsoft's support policy for your target platform before you commit.
- Pin your tags. The
-latesttag is convenient in a lab and a trap in production. Use a tag that names a specific cumulative update, and upgrade on purpose. - Existing skills and tooling. A team with mature VM operations and no container experience will pay a learning cost. Start with development and test, where mistakes are cheap, and expand from there.
A Practical Starting Path
- Install Docker Desktop (or a Linux host with Docker) and run a single Developer-edition container with a named volume.
- Move your database project's test environment into a compose file in the same repository as the code.
- Add a health check and a restore step so that a fresh test database can be built from a backup with one command.
- Run the same compose file in your CI pipeline so every change is tested on a real engine.
- Rehearse your next cumulative update or major version upgrade against a container before you schedule the change.
- Only then evaluate Kubernetes for the workloads where self-healing, declarative operations and a shared platform justify the added complexity.
The Bottom Line
Installers give you a server. Virtual machines give you a server inside a server. Managed services give you someone else's server. Containers give you an instance as code: small, fast, identical wherever it runs, and disposable by design. For development, testing, upgrade rehearsal, training and CI, that is a clear win, and with Kubernetes and sound storage design it is a credible production option as well. Keep the VM or the managed service where the workload needs Windows-only features or where the operational model fits, and use containers everywhere else, starting with the test environment you currently build by hand.