Runtime, SDK and Target Frameworks
By the end of this lesson
Decide which components a given machine needs and why.
This is the setup question that costs the most time across a team, and it is not because the concept is hard. It is because two downloads have similar names and the error message you get from choosing wrongly does not say which one you chose.
The SDK builds code. The runtime executes it. The SDK contains a runtime, so a machine with the SDK can do both. A machine with only the runtime can run a finished application and nothing else.
Same family, different jobs:
| SDK | Runtime | |
|---|---|---|
| Contains | Compiler, build engine, package client, project templates, the dotnet command — and a runtime | The execution engine and the base class library assemblies |
| dotnet new, build, test, publish | Work | Not available |
| Running a published application | Works | Works |
| Rough install size | Hundreds of megabytes | Tens of megabytes |
| Belongs on | Developer machines and build agents | Servers and production container images |
# Which SDKs are installed, and where
dotnet --list-sdks
# Which runtimes are installed — note there are usually several lines per version
dotnet --list-runtimes
# Everything: the SDK in use, the host, the platform identifier, and both lists
dotnet --info
# The SDK version selected in the current folder, which global.json can change
dotnet --version- dotnet --list-sdks printing nothing while --list-runtimes prints several lines is the signature of a runtime-only machine. That is the state where dotnet build fails and running a published application succeeds.
- The runtime list shows more than one entry per version because the shared framework is split. Microsoft.NETCore.App is the base platform; Microsoft.AspNetCore.App adds the web framework. A web application needs the ASP.NET Core runtime, which includes the base one.
- dotnet --info reports the platform identifier for the machine, such as linux-x64 or win-x64. You need that string when producing a self-contained build.
- dotnet --version can differ between folders. A global.json file pins which installed SDK is used for everything beneath it, which is how a team keeps build behaviour identical across machines.
The next distinction resolves most version confusion: a target framework is a promise you make at compile time, and the installed runtime is the reality at start-up.
The TargetFramework in your project file, net10.0 for example, states which version of the platform you are compiling against. It decides which library types and language features are available, and it is checked at compile time.
The runtime installed on the machine that runs your application is a separate fact, checked when the process starts. Nothing links the two except your deployment.
When the two do not match, the launch fails with a message naming the framework and version it wanted. What that message means is specific: the application was built expecting a major version of the platform that this machine does not have. The application is fine. The machine is missing a component.
$ dotnet Anvi.Employees.Api.dll
# You must install or update .NET to run this application.
# Framework: 'Microsoft.NETCore.App', version '10.0.0'
# The following frameworks were found:
# 9.0.14 at [/usr/share/dotnet/shared/Microsoft.NETCore.App]
# Confirm the reading: the app wants 10, the machine has 9
dotnet --list-runtimes- The wording varies between versions, but the shape holds: what the application asked for, followed by what was available.
- Within a major version the runtime rolls forward automatically. An application built against 10.0.0 starts happily on 10.0.7, which is why servers can take patch updates without anyone rebuilding.
- It does not roll forward across major versions by default. A net10.0 application will not start on a 9 runtime, and it will not start on an 11 runtime either unless you opt in with a roll-forward setting.
- Two fixes, and they are genuinely different decisions: install the matching runtime on the machine, or publish a self-contained build that carries its own.
The decision, machine by machine:
- Your development machine
- The SDK. Install the latest long-term support version unless a project pins another with global.json. Several SDK versions can sit side by side without conflict.
- A build agent or CI runner
- The SDK, at the version the build expects. If the agent has a newer SDK than your laptop, builds can differ — a global.json committed to the repository removes that variable.
- A server running a framework-dependent build
- The runtime, matching the major version your application targets. For a web application that means the ASP.NET Core runtime.
- A server running a self-contained build
- Nothing. The published output carries the runtime with it. You trade a larger deployment and your own patching responsibility for having no prerequisite.
- A container image
- Both, in separate stages. The build stage uses an SDK image; the final stage uses a runtime image and copies the published output into it. The shipped image never contains the compiler.
Summary
- The SDK builds and runs; the runtime only runs. The SDK contains a runtime
- Developer machines and build agents need the SDK; servers need the runtime or a self-contained build
- TargetFramework is checked when you compile; the installed runtime is checked when the process starts
- A mismatch message means the machine lacks the major version the application was built for — install it, or publish self-contained
- Self-contained output has no prerequisite but moves runtime patching onto your release process
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Read your own machine, then break it on purpose
Run dotnet --list-sdks and dotnet --list-runtimes and write down what you have, including which shared frameworks appear.
Now open a project file and change TargetFramework to a major version you do not have installed. Try to build, then read the error. Change it back.
Which of those two commands would have told you the answer fastest?
Show solution
The build fails with a message about the target framework or a missing reference pack, not about a runtime. That distinction matters: a compile-time failure means the SDK cannot find what it needs to build against, and a start-up failure means the machine cannot find what it needs to run.
dotnet --list-runtimes answers the start-up question. dotnet --list-sdks answers the build question. Getting into the habit of running the right one turns a confusing message into a two-second check.
Think about it
Why is self-contained not the default?
A self-contained build removes the need to install anything on the server. That sounds strictly better. Give two reasons it is not the default, and one situation where you would choose it anyway.
Show solution
Size is the obvious reason. Every deployment carries a full runtime, and several applications on one machine each carry their own copy instead of sharing the installed one.
Patching is the more important reason. With a framework-dependent build, a runtime security fix applied to the machine or the base image protects every application on it. With self-contained builds, each one needs rebuilding and redeploying. That is a process commitment, not a one-off choice.
It earns its place when you cannot control the target machine — a command-line tool you hand to other teams, an appliance, a build agent you do not administer. No prerequisite is worth real money there.
There is no single right answer. The point is that the choice moves patching responsibility, and a team should make it deliberately rather than by accident.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.