How do I manage .NET SDK version selection and roll‑forward for self‑contained C# deployments on Windows, Linux, and macOS?
0 reputation · 17 Feb 2021, 20:00 UTC
0 reputation · 17 Feb 2021, 20:00 UTC
When deploying a C# application as a self‑contained package, I need to ensure the correct .NET runtime is bundled while also controlling which SDK version is used for building and publishing across different operating systems. The CLI chooses the latest SDK unless a global.json overrides it, and runtime roll‑forward can affect which version runs on target machines. I am uncertain how to reliably pin the SDK version, configure roll‑forward behavior, and verify that the published output contains the exact runtime patch I intend.
How can I enforce a specific SDK version via global.json for dotnet build and publish? What roll‑forward settings should I apply to avoid unexpected runtime changes? How do I confirm that the self‑contained deployment includes the desired runtime version?
26525 reputation · 18 Feb 2021, 04:01 UTC
To ensure consistent builds and deployments across Windows, Linux, and macOS, you must decouple the SDK version (used for compilation) from the Runtime version (bundled with the application). For self-contained deployments, the runtime is packaged within the application folder, meaning the target machine does not need .NET installed, but the build machine must be strictly controlled.
To enforce a specific SDK version for dotnet build and dotnet publish, place a global.json file in your project root or a parent directory. This prevents the CLI from defaulting to the latest installed SDK on the build agent.
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestPatch"
}
}
The rollForward property here applies only to the SDK. Using latestPatch allows the build to use 8.0.101 if 8.0.100 is missing, while still staying within the 8.0.x feature set.
For self-contained apps, roll-forward behavior determines if the app can run on a newer runtime version if one is found on the host (though this is rare for truly self-contained apps) or how it handles internal versioning. You configure this in the .csproj file:
<PropertyGroup>
<RollForward>LatestPatch</RollForward>
</PropertyGroup>
To confirm the deployment is self-contained and includes the intended runtime, use the following steps:
dotnet publish -r win-x64 -c Release --self-contained true
[AppName].runtimeconfig.json in the publish folder. Verify the version and rollForward values match your expectations.coreclr.dll (Windows) or libcoreclr.so (Linux), confirming the runtime is bundled.This guidance assumes you are using .NET Core 3.1 or .NET 5/6/7/8+. Note that self-contained deployments significantly increase the package size. If you are using a custom CI/CD pipeline, please specify if you are using GitHub Actions or Azure DevOps, as environment-level SDK installation may override global.json if not configured correctly.
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 17 Feb 2021, 21:39 UTC
One clarification worth adding: for truly self-contained deployments, the RollForward setting is largely moot at runtime. The app bundles its own copy of the runtime, so there is no shared framework on the target machine to roll forward to. The bundled version is fixed at publish time, and the only way to pick up a newer runtime patch is to republish with a newer SDK or an explicit RuntimeFrameworkVersion.
Where roll-forward genuinely applies is framework-dependent apps, where the host resolves against installed runtimes — that's also where DOTNET_ROLL_FORWARD overrides are useful for diagnostics.
For verification, the most reliable check is inspecting the generated .runtimeconfig.json in the publish output — it records the exact framework version the app was built against. Combined with dotnet --list-sdks on the build agent (to confirm global.json resolved as expected), that covers both halves of the question without needing to strip runtimes off a test machine.
One caveat: default roll-forward behavior has shifted across .NET versions, so confirm against the version you actually target rather than assuming current docs match older SDKs.