Skip to content

2.0.0: SQL Server 2025, current runtimes, and tests that can fail - #39

Merged
Carlos Robles (croblesm) merged 40 commits into
microsoft:mainfrom
croblesm:refresh-2026
Sep 16, 2026
Merged

Carlos Robles (croblesm) merged 40 commits into
microsoft:mainfrom
croblesm:refresh-2026

Conversation

@croblesm

Copy link
Copy Markdown
Contributor

Why

The four templates have not been touched since July 2024, and none of them builds today, on any
machine
. devcontainer up fails at the first RUN line in the Dockerfile, before any SQL tooling
installs: the pinned 1- base images carry an expired Yarn apt key, and the bullseye defaults hit
Debian 11's expired security repo. That happens on x64 and Arm alike, so every person who picked one
of these templates recently got a broken container. They are still being picked: about 12.9K GHCR
downloads across the four packages.

Behind that first failure sat a retired engine (Azure SQL Edge, out of support since 2025-09-30,
issue #29) and a CI pipeline that had never actually tested anything.

What changed

Engine. Azure SQL Edge is replaced by SQL Server 2025 (mcr.microsoft.com/mssql/server:2025-latest,
Enterprise Developer), pinned to linux/amd64 with a health check, and the app container now waits
for the database to be healthy instead of polling in a shell loop. The SQL Database project still
targets Azure SQL Database (SqlAzureV12DatabaseSchemaProvider); that build, not the local engine,
is what guarantees compatibility, and a test proves it rejects an incompatible object (SQL70015).

Apple Silicon. The app container now runs natively on Arm. Previously three separate pieces were
x64-only: .NET from the Debian apt feed, ODBC Driver 17, and the SqlPackage zip. Today .NET comes
from the official dev container feature, SqlPackage is installed as a .NET tool, go-sqlcmd is
fetched per architecture with a pinned SHA-256, and Python uses Microsoft's mssql-python driver,
which needs no system ODBC at all. Every installed binary is checked against the container's
architecture. SQL Server itself is still x64 only and runs under emulation; the docs say so plainly.

Versions. .NET 10 (8.0 still offered until its 2026-11-10 end of support), Python 3.14, Node 24,
Microsoft.Build.Sql 2.2.0, Aspire 13.5 through the Aspire CLI instead of the long-obsolete
workload, Azure CLI 2.90, and current dev container features. Actions are pinned by SHA, and
Dependabot now watches features, compose images and actions.

A connection profile that actually connects. The shipped LocalDev profile used
"password": "${env:MSSQL_SA_PASSWORD}". Nothing expands that in VS Code settings, so the saved
profile arrived with an empty password and prompted on first use, and had done since 1.2.2. It now
uses ${containerEnv:MSSQL_SA_PASSWORD}, which the dev container tooling resolves from the
container's environment at create time, so the password still lives only in .devcontainer/.env.

Tests that can fail. The old CI selected a template removed in 2024, called scripts that did not
exist, and was marked continue-on-error. It is replaced by a smoke test per template on
ubuntu-latest and ubuntu-24.04-arm, plus a static job that pins the things this PR promises:
template ids, the Azure SQL Database target, the Library schema and seed, the connection profile and
port 1433, and the compose shape. Locally, test/gauntlet.sh runs the whole thing in one command.

Repo hygiene. 125 committed build artifacts (10.5 MB) and 36 duplicated screenshots are gone;
the images live once under docs/. The READMEs and NOTES were rewritten, including the Apple Silicon
support boundary and a troubleshooting section for networks that block package registries.

Net: 252 files changed, +2,954 / −13,988.

Evidence

  • test/gauntlet.sh passes: 15 configurations (4 templates on Arm, the same 4 with the app forced to
    x64, 3 non-default variants, plus CI rehearsals), 486 checks, 12 of 12 planted defects caught,
    42 negative controls, and a supply-chain layer with 0 known vulnerabilities.
  • CI is green on both runner architectures, including the Node sample and the default NuGet path.
  • Each template was also driven through the real VS Code UI: extensions installed, the connection
    profile resolved and connected, and tasks 2, 3 and 4 run from tasks.json.

Known limits

  • SQL Server 2025 occasionally crashes at startup and dumps core: 1 start in 37 under emulation, and
    once on a native x64 CI runner. A rebuild fixes it. Documented in each template's notes.
  • SQL Server is x64 only, so on Apple Silicon it runs under emulation, which Microsoft does not test
    or support.
  • The db service is capped at 2 GB, exactly SQL Server's documented minimum. Whether that
    contributes to the startup crashes is unmeasured.
  • GitHub Codespaces was not exercised.

Follow-ups (not in this PR)

  • The Learn pages still document Azure SQL Edge (local-dev-experience-dev-containers and its
    quickstart), and the screenshots there predate this release.
  • release.yaml still depends on a personal PAT secret.
  • Template ids are unchanged deliberately; renaming them would break every existing reference.

Templates no longer copy 36 PNGs into every user's project. Byte-identical
copies are kept once. The dotnet-aspire task list and 'Update .NET SDK'
screenshots are dropped: that task is gone and the template's task list now
matches the dotnet one.
- SQL Server 2025 replaces Azure SQL Edge. State that the SQL Database
  project's Azure SQL Database target is the compatibility check.
- Document the Apple Silicon support boundary: native app container,
  SQL Server under emulation, which Microsoft does not support.
- Correct the base OS (Ubuntu 24.04 for .NET, Debian 13 for Python and
  Node.js), MSSQL_SA_PASSWORD, forwarded ports, and task numbers.
- Add the Dev Container CLI path, image variants, and per-language
  connection notes (Aspire AddConnectionString, mssql-python, mssql).
- Remove Azure Data Studio links, broken relative links, the duplicate
  Docker extension entry, and the ms-mssql.sql-database-projects id.
- Reference screenshots by absolute URL under docs/images.
Replace the marketing text with what the templates are, the four
templates and their image variants, how to apply them, the Apple Silicon
support boundary, and how testing works. The overview diagram moves to
docs/images.
Carlos stays the owner until the handover names a team.
Weekly updates for Dev Container Features, the SQL Server image in each
docker-compose.yml, and GitHub Actions. The template Dockerfiles are left
out because their FROM tag is a template option placeholder that
Dependabot cannot resolve.
actions/checkout v7.0.1 and devcontainers/action v1.4.3. The PAT step is
unchanged; replacing it is a handover item.
One build.sh/test.sh pair for all templates instead of eight copies. The in-container
tests now check the engine, the seeded Library database, tool versions, the SQL
project target, an Azure-incompatible fixture, re-publish, and a failing password,
and they fail on any error. Adds static gates for shared files, forbidden content,
and image tags. Removes the generated deploy script, the duplicate dotnet-aspire
test, committed test build outputs, and the docker system prune helper.
- Engine is mcr.microsoft.com/mssql/server:2025-latest (Enterprise Developer), pinned
  linux/amd64, with a healthcheck; the app container waits for it to be healthy.
- Base image dotnet:2 with 10.0-noble (default) and 8.0-noble variants.
- sqlcmd from the go-sqlcmd v1.10.0 release, checked against its SHA-256; SqlPackage
  170.5.76 as a .NET tool; no Ubuntu 20.04 apt repository.
- postCreateCommand always builds the SQL Database project and publishes it, and
  exits non-zero naming the failed step.
- Microsoft.Build.Sql 2.2.0; committed bin/ and obj/ removed for all templates and
  ignored. ModelCollation stays: without it the model becomes case-sensitive.
- Removes container_name so two templates can run side by side.
- python: python:3 with 3.14-trixie (default) and 3.13-trixie; mssql-python 1.14.0
  replaces pyodbc and the ODBC 17 driver, which had no arm64 build.
- javascript-node: javascript-node:5 with 24-trixie (default) and 22-trixie.
- Both get .NET 10 from the dotnet Feature instead of install-dotnet.sh (amd64 only),
  and docker-outside-of-docker with moby off, since moby has no trixie packages.
- dotnet-aspire: Aspire CLI 13.5.3 and Aspire.ProjectTemplates replace the obsolete
  workload; the Update .NET SDK task is gone, so trusting the certificate is task 4.
- azure-cli Feature no longer pinned to latest through pip.
- The smoke test checks that sqlDatabaseProjects.dotnetSDK Location has an SDK,
  and gives every build its own workspace so no stack or path is reused.
- Paths filter covers the four templates and the shared test files; no
  continue-on-error; actions pinned by SHA; checkout without persisted credentials.
- ubuntu-latest runs the full stack. ubuntu-24.04-arm builds the app container and
  runs the checks that need no SQL Server, since SQL Server has no arm64 image.
- A static job lints the scripts and runs the shared-file, forbidden-content, and
  image-tag gates.
- postCreateCommand.sh reports the failed step from a function trap (shellcheck clean).
test/gauntlet.sh runs every layer in order and fails on the first broken one: source
state, lint, static gates, negative controls for each home-grown check, image tags,
the sqlcmd checksum build failure, the template matrix (arm64, amd64, non-default
variants, and the arm64 CI path), mutation, and supply-chain checks.

test/mutants.sh applies seven plausible bugs to copies of the committed tree and
requires the smoke test to fail with the check each one breaks, and proves that
each mutant ran.
…ry mutant ran

The Dev Container CLI read the rest of the list from stdin, so the first dry run ran
one mutant and reported 1/1.
…S4 runs; build.sh shows db logs on failure

SQL Server 2022 rejects MSSQL_PID=EnterpriseDeveloper (2025 only), so an image-only
mutant stopped at the healthcheck and never reached the engine-version check.
- python and javascript-node list Microsoft's bookworm Azure CLI suite, the fallback
  Microsoft Learn documents for Debian releases without their own suite. The
  azure-cli Feature now installs Microsoft's 2.90.0 instead of Debian's 2.74.0.
- Drop eg2.vscode-npm-script, which VS Code marks deprecated; add GitHub Copilot Chat
  next to GitHub Copilot, which is still current on the Marketplace.
- The CI workflow grants pull-requests: read, which dorny/paths-filter needs to list
  the changed files on pull_request.
- Every native tool in the app container (sqlcmd, dotnet, the SqlPackage and Aspire
  launchers, Bicep, azd, Docker CLI, the mssql-python driver) must match the
  container architecture, so an emulated amd64 binary fails on arm64.
- S16: with an object the Azure target rejects, postCreateCommand.sh stops at the
  build and publishes nothing, even with an older dacpac present.
- S5 reads the dacpac state before any check builds the project again.
- The Azure CLI must come from Microsoft's repository.
- check-forbidden matches any case and spacing, removal commands fed by docker
  listings, and scripts whose first command isn't set -euo pipefail.
- The gauntlet checks every extension id against the Marketplace and VS Code's
  deprecation list, simulates the paths filter for test and shared files, pins the
  arm64 half of check-tags, and removes images a run pulled that the templates don't
  use. checkExtension, which no smoke test could call, is gone.
- Three new mutants: an amd64 sqlcmd on arm64, a build failure ignored with || true,
  and build outputs removed after up.
…w diagram

The uppercase-host control now plants a fictional example-gated registry host. The
overview diagram's file name no longer contains db-dev.
…r emulation

- dotnet and dotnet-aspire set SSL_CERT_DIR, as the .NET guidance for Linux says, so
  'dotnet dev-certs https --trust' exits 0 instead of reporting partial trust.
- The Apple Silicon sections say SQL Server occasionally crashes while it starts under
  emulation (1 start in 37 in our tests) and what to do about it.
- CODEOWNERS no longer points at a document outside the repo.
…tasks 2 and 4

- New test/test-utils/check-static.sh, run by CI's static job and the gauntlet: template
  ids, target platform, the Library .sql files against a hash manifest, LocalDev and
  port 1433, the compose shape, and CI integrity (known conditions only, no swallowed
  failures, the smoke step and its full mode, the action's exact commands, the Pick step
  executed, the static job's gates, the paths filter).
- check-forbidden catches more spellings: ACR and podman logins, short credential names,
  unfiltered docker listings in $() or backticks, a line-continued prune, set +e, and
  Dockerfile steps that swallow a failure; non-ASCII file names are read as they are.
- S12 builds the dotnet, python, and javascript-node Dockerfiles with a bad checksum.
- The smoke tests run tasks 2 and 4 from tasks.json; a task 2 cwd mutant joins the list.
- The extension deprecation check ignores case; the wheel hash check has a control.
- Test labels use SPEC ids.
…is clean

ShellCheck 0.11 locally accepts the A && B || C chain; the 0.9.0 that
ubuntu-24.04 ships reports SC2015 and the lint step exits 123.
The workflow's paths filter uses an anchor, and Psych 4 (Ruby 3.1+, what
ubuntu-24.04 ships) refuses aliases unless asked, so yaml2json raised and
the S15 paths-filter simulation compared against nothing. Psych 3 rejects
the keyword, so the call falls back.
…t ids

- VS Code writes connection settings verbatim, so ${env:MSSQL_SA_PASSWORD} reached the
  container's Machine settings.json as an empty password and the LocalDev profile could
  not connect. The profile now carries the same literal password as .devcontainer/.env,
  which is what the extension's schema expects.
- github.copilot and github.copilot-chat are gone from all four templates. Current VS
  Code ships GitHub Copilot Chat built in, so installing either fails and takes the rest
  of the batch down with it.
- postCreateCommand builds with -nodeReuse:false, so no MSBuild worker is left holding
  database/Library/bin/Debug/Library.dacpac when the user runs the build task.
- NOTES and README: password change touches both files, GitHub Copilot comes with VS
  Code, the dacpac lock and what to do, and a troubleshooting section for networks that
  block nuget, npm, or PyPI.
… install

- S18 connects with the profile's own server, user, and password, and rejects a
  ${...} placeholder; check-static requires the profile password to equal .env.
- The extensions layer installs every configured id with a real VS Code server, in one
  batch as VS Code does, and requires each one on disk afterwards. Control: an id that
  ships built in.
- New mutant: the profile password back to a placeholder.
A single code-server invocation with many --install-extension flags installs only the
first and still exits 0.
VS Code does not expand ${env:...} in settings, so the profile's password
arrived empty and the saved connection prompted instead of connecting.
${containerEnv:MSSQL_SA_PASSWORD} is resolved by the dev container CLI from
the container's environment at create time, so the password keeps living only
in .devcontainer/.env.

Measured: containerEnv -> P@ssw0rd!, env -> empty, localEnv -> empty.
The notes still described the literal password and told readers to change it
in two files. There is one place now: .devcontainer/.env.
Seen once on a native x64 GitHub Actions runner as well as 1 in 37 starts
under emulation. Same symptom, same fix: rebuild the container.
@croblesm
Carlos Robles (croblesm) merged commit 985cbef into microsoft:main Sep 16, 2026
11 checks passed
@croblesm
Carlos Robles (croblesm) deleted the refresh-2026 branch September 16, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant