.NET SDK and project files

What the SDK actually installs, how an SDK-style project file describes a build, and how to pin a repository to one SDK version.

SDK versus runtime

The runtime is what executes a compiled .NET application. The SDK is a superset: it bundles a runtime plus the C# compiler, MSBuild and the dotnet CLI. A build machine needs the SDK; a production server usually needs only the runtime.

CommandAnswers the question
dotnet --infoEverything at once: SDK version, runtimes, base path, OS and RID
dotnet --list-sdksWhich SDKs are installed and where
dotnet --list-runtimesWhich runtimes are installed, per component (Microsoft.NETCore.App, Microsoft.AspNetCore.App)
dotnet --versionThe SDK that this directory resolves to, honouring global.json
dotnet --list-sdks
# 8.0.404 [C:/Program Files/dotnet/sdk]
# 9.0.100 [C:/Program Files/dotnet/sdk]

dotnet --list-runtimes
# Microsoft.AspNetCore.App 8.0.11 [C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App]
# Microsoft.NETCore.App 8.0.11 [C:/Program Files/dotnet/shared/Microsoft.NETCore.App]

Runtimes are installed side by side and selected at launch: a framework-dependent app built for net8.0 runs on the newest compatible 8.x runtime on the machine, so patching the runtime fixes the app without rebuilding it.

The project file

An SDK-style project is a single XML file that declares intent, not file lists. Source files are globbed automatically, so adding a .cs file needs no edit. The Sdk attribute decides which default props and targets are imported — Microsoft.NET.Sdk for console and library code, Microsoft.NET.Sdk.Web for ASP.NET Core.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
    <ProjectReference Include="../Core/Core.csproj" />
  </ItemGroup>

</Project>
  • TargetFramework selects the API surface. Use TargetFrameworks (plural, semicolon-separated) to multi-target.
  • PackageReference restores NuGet packages per project; versions belong in a central file once you have more than a handful of projects.
  • Nullable turns on the nullable reference type analysis — treat it as mandatory on new code.
  • Directory.Build.props in the repository root applies shared properties to every project beneath it, which is where common settings belong.

Keep the project file declarative. Logic in MSBuild targets is hard to debug and invisible to a reader opening the app for the first time.

Pinning the SDK version

By default the CLI picks the highest installed SDK. On a team that means one developer silently builds with a different compiler than CI. A global.json at the repository root removes that ambiguity.

{
  "sdk": {
    "version": "8.0.404",
    "rollForward": "latestFeature"
  }
}
rollForward valueBehaviour when the exact version is missing
disableFail immediately — strictest, good for CI
latestPatchAccept a newer patch of the same feature band
latestFeatureAccept a newer feature band within the same major version
latestMajorAccept the newest SDK available
💡
Commit global.json and let CI fail loudly if the SDK is absent. A build that quietly uses a newer SDK produces different behaviour than the one you tested, and that difference surfaces in production rather than in the pipeline.

FAQ

Do I need both the SDK and a separate runtime?
No. The SDK includes runtimes, so a developer machine needs only the SDK. Install the runtime on servers to keep images and disk usage small.
What is a RID?
A Runtime Identifier such as win-x64, linux-arm64 or osx-x64. It names the OS and CPU architecture a build targets, and only matters once you publish self-contained or native.

The dotnet CLI workflow Publishing and deployment

Last refreshed 2026-09-18.