Skip to content

SQL Server

Run Microsoft SQL Server next to your code, so your app and its migrations talk to the same engine as in production. The workbench gets the free Developer edition, and the database your code connects to is already there.

Microsoft builds the SQL Server image for x86_64 (amd64) only. On an Apple Silicon Mac it runs through Rosetta, so your Docker runtime needs Rosetta turned on.

HostWhat you need
Linux or Windows (WSL2) on x86_64nothing, it runs natively
Apple Silicon Mac with Docker DesktopSettings → General: choose the Apple Virtualization framework and turn on Use Rosetta for x86_64/amd64 emulation (with a screenshot)
Apple Silicon Mac with OrbStacknothing, Rosetta is on by default
Apple Silicon Mac with Colimaa VM created with colima start --vm-type=vz --vz-rosetta
Docker Desktop with Docker VMMdoes not work: Docker VMM has no Rosetta
Linux on ARM, Windows on ARMdoes not work: there is no image for ARM

Without Rosetta the service stops right after it starts. You see qemu: uncaught target signal 11 (Segmentation fault) in its log:

Terminal window
monoceros logs acme mssql

Microsoft does not support SQL Server under emulation. For local development it works. Apple plans to limit Rosetta from macOS 28 (2027). It is not known yet whether that affects Docker.

At init:

Terminal window
monoceros init acme --with-services=mssql

Or to an existing workbench:

Terminal window
monoceros add-service acme mssql

Need a second database server? Add it again under a different name with --as. Each instance gets its own host, data volume and connection env:

Terminal window
monoceros add-service acme mssql --as=reporting

Then build the workbench:

Terminal window
monoceros apply acme
Terminal window
monoceros remove-service acme mssql

Its data volume monoceros-acme-data-mssql is removed with the workbench on monoceros remove, which backs it up first.

services:
- name: mssql
image: mcr.microsoft.com/mssql/server:2025-latest
user: '0:0'
port: 1433
env:
MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD}
MSSQL_DATABASE: ${MSSQL_DATABASE}
ACCEPT_EULA: ${ACCEPT_EULA}
MSSQL_PID: ${MSSQL_PID}
volumes:
- data:/var/opt/mssql
healthcheck:
test:
[
'CMD',
'/opt/mssql-tools18/bin/sqlcmd',
'-C',
'-S',
'127.0.0.1',
'-U',
'sa',
'-P${MSSQL_SA_PASSWORD}',
'-b',
'-Q',
"IF DB_ID(N'${MSSQL_DATABASE}') IS NULL CREATE DATABASE [${MSSQL_DATABASE}]",
]
interval: 10s
timeout: 5s
retries: 5
startPeriod: 30s
connectionEnv:
URL: 'sqlserver://sa:${MSSQL_SA_PASSWORD}@${host}:${port}?database=${MSSQL_DATABASE}&TrustServerCertificate=true'
HOST: '${host}'
PORT: '${port}'
USER: 'sa'
PASSWORD: '${MSSQL_SA_PASSWORD}'
DB: '${MSSQL_DATABASE}'

The values live in acme.env, so the yml stays shareable without credentials in it (see Workbench configuration).

Keep the user: '0:0' line. Without it, SQL Server cannot start on data that monoceros restore brought back.

Monoceros injects a connection env into the workspace container, prefixed with the service name:

VariableValue (defaults)
MSSQL_URLsqlserver://sa:Monoceros-dev1@mssql:1433?database=monoceros&TrustServerCertificate=true
MSSQL_HOSTmssql
MSSQL_PORT1433
MSSQL_USERsa
MSSQL_PASSWORDMonoceros-dev1
MSSQL_DBmonoceros

The connection uses the sa user. The prefix is the service name in uppercase, so a second instance added with --as=reporting gets REPORTING_URL and never collides.

Not every driver reads a sqlserver:// URL. For .NET and JDBC, build the connection string from the single values. A .NET connection string with the defaults:

Server=mssql,1433;Database=monoceros;User ID=sa;Password=Monoceros-dev1;TrustServerCertificate=True

The server uses a self-signed certificate. Drivers that encrypt by default (Microsoft.Data.SqlClient, JDBC) reject it, so set TrustServerCertificate=true.

The database comes a few seconds after the server

Section titled “The database comes a few seconds after the server”

The image cannot create a database on its own, so the healthcheck creates MSSQL_DATABASE. Until the first check has run, the server answers but the database is not there yet.

SQL Server needs at least 8 characters from three of these groups: uppercase, lowercase, digits, symbols. With a weaker password in acme.env the server stops on start.

The image does not start without ACCEPT_EULA=Y, so the workbench sets it for you. By adding the service you accept the SQL Server license terms. The Developer edition (MSSQL_PID=Developer) is for development and testing, not for production.

SQL Server does not start with less memory in your Docker runtime.

Query the database through your project’s own driver. Or open a tunnel and use a client on your host:

Terminal window
monoceros tunnel acme mssql

See Proxy and tunnels.

A .bacpac file holds the schema and the data of a database. You import it with sqlpackage, which Microsoft ships as a .NET tool. Your workbench needs the dotnet language for it:

Terminal window
monoceros add-language acme dotnet
Terminal window
monoceros apply acme

Copy the export into the workbench’s projects folder on your host, ~/.monoceros/container/acme/projects/. Inside the workbench the same folder is /workspaces/acme/projects/. Then open a shell in the workbench:

Terminal window
monoceros shell acme

Install sqlpackage and put the .NET tools on your path:

Terminal window
dotnet tool install --global microsoft.sqlpackage
Terminal window
export PATH="$PATH:$HOME/.dotnet/tools"

Import the file into the database. The target database must be empty, and monoceros is empty after the first apply:

Terminal window
sqlpackage /Action:Import /SourceFile:/workspaces/acme/projects/shop.bacpac /TargetConnectionString:"Server=$MSSQL_HOST,$MSSQL_PORT;Database=$MSSQL_DB;User ID=$MSSQL_USER;Password=$MSSQL_PASSWORD;TrustServerCertificate=True"

To export a database again, run the same command with /Action:Export, /SourceConnectionString: and /TargetFile:.