The Linux/Unix code path is exercised in CI on ubuntu-latest, but you can
also run the suite locally against a Linux container — useful when changing
platform-specific code.
Optional helper. Delete this file and
scripts/test-linux.ps1if your project does not need Linux testing from Windows.
- Rancher Desktop (or Docker Desktop) with the
dockerd/ moby engine enabled sodockeris onPATH - PowerShell 7+
pwsh ./scripts/test-linux.ps1The script mounts the repo into mcr.microsoft.com/dotnet/sdk:10.0 and runs
dotnet build + dotnet test for the net10.0 target framework (that image
carries only the net10 runtime; CI exercises both net8.0 and net10.0). The host's bin/ and obj/ folders are
shadowed inside the container with anonymous volumes, so the Linux build
neither sees the Windows IDE artifacts nor writes back into the host tree.
A named volume (ProcessKit-nuget) caches NuGet packages between runs.
Useful switches:
pwsh ./scripts/test-linux.ps1 -Filter "FullyQualifiedName~greet"
pwsh ./scripts/test-linux.ps1 -Configuration Debug -RebuildAlpine (musl libc) is the de facto standard base for containerized .NET
deployments and differs from the default glibc image in libc behaviour and in
which utilities ship in the base image. Pass -Alpine to run the same suite
against mcr.microsoft.com/dotnet/sdk:10.0-alpine instead:
pwsh ./scripts/test-linux.ps1 -Alpine
pwsh ./scripts/test-linux.ps1 -Alpine -Filter "FullyQualifiedName~VerbTests"-Alpine changes two things under the hood, both transparent to normal use:
- The in-container script runs under
shinstead ofbash— Alpine's base image has no bash, only BusyBox'ssh(every line the script runs is plain POSIX shell, so this is a no-op for the default image). apk add --no-cache util-linuxruns before the build. BusyBox ships its ownsetprivapplet under the same name, but it does not implement the--reuid/--regid/--clear-groupsflags theCommand.Uid/Gidprivilege-drop path (Native.Posix.fs) rewrites onto — without the real util-linux package shadowing that applet, the privilege-drop tests fail with asetpriv: unrecognized optionspawn error rather than exercising the real drop.
No other accommodation was needed: the rest of the suite (build, streaming,
readiness probes, pipelines, signals, /proc-based introspection) runs
unmodified against musl — it was confirmed green end-to-end (Category!=Stress)
against this exact image before the CI leg below was added.
CI runs the same combination in the test-alpine job
(CI workflow), by the same raw
docker run pattern as the test-cgroup-limits job (rather than the OS matrix
in test, since it needs the util-linux install step first): full suite,
Category!=Stress, net10.0 only (the -alpine image, like the default
image, ships only the net10 runtime).