# How to Upgrade an ASP.NET Core Service From .NET 10 to .NET 11

> Move an ASP.NET Core service from .NET 10 to .NET 11. Select the SDK, retarget the app and tests, and check that the published image starts on the oldest CPU in your fleet.

Published: 2026-11-13. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/upgrade-dotnet-10-to-dotnet-11

To upgrade a framework-dependent ASP.NET Core service from .NET 10 to .NET 11, select the .NET 11 SDK in CI, retarget the service and its tests to `net11.0`, and update the runtime on the deployment host.
A passing test run tells you little about the runtime in the final Docker image or the CPU features your oldest VM exposes.

## Is It Worth Moving From .NET 10 LTS?

.NET 10 LTS and .NET 11 STS both receive support through **November 14, 2028**, provided you keep up with servicing patches under [**Microsoft's lifecycle policy**](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core).
A service on .NET 10 can stay there while both releases are supported.
Move sooner if you need one of the [**new features in .NET 11**](https://milanjovanovic.tech/blog/whats-new-dotnet-11), such as built-in Zstandard for compressed exports.

## Select the SDK and Retarget the Tests

Start at the repository root and run `dotnet --version` locally and in CI.
If the runner reports 10.x even though an 11.x SDK is installed, check which `global.json` it found.

Install the .NET 11 SDK on the build agent, then update the `sdk` section of the repository's `global.json`.
This configuration starts at 11.0.100 and accepts later installed 11.0 feature bands and patches.

```json
{
  "sdk": {
    "version": "11.0.100",
    "rollForward": "latestFeature",
    "allowPrerelease": false
  }
}
```

Run `dotnet --version` again in CI.
If the resolver can't find a matching SDK, check `dotnet --list-sdks` on that agent.
The [**SDK selection policy**](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json) only chooses among installed versions.

Change the target framework in the service and test project files, or in `Directory.Build.props` if they share the setting.

```diff
-    <TargetFramework>net10.0</TargetFramework>
+    <TargetFramework>net11.0</TargetFramework>
```

Retarget the tests that reference the application in the same change.
A `net10.0` test project can't reference a `net11.0` application project.
Shared libraries can keep an older compatible target if they still serve .NET 10 consumers.

Review explicit framework package references, including versions in `Directory.Packages.props` if you use central package management.
Microsoft lists the package families to update in its [**ASP.NET Core migration guide**](https://learn.microsoft.com/en-us/aspnet/core/migration/100-to-110?view=aspnetcore-11.0).
Check third-party packages against their own compatibility guidance.
Services that use chained configuration, disposable scopes, or `BackgroundService` also have [**worker behavior changes to test**](https://milanjovanovic.tech/blog/dotnet-11-breaking-changes).

Run the same test suite that passed on .NET 10.
For a solution named `Orders.slnx`, the test run should now report `net11.0` for the retargeted project.

```bash
dotnet restore Orders.slnx
dotnet test Orders.slnx -c Release --no-restore
```

## Why Can the Tests Pass and the Container Still Crash?

The tests run where the SDK is installed.
A framework-dependent production image gets its runtime from the **final** Docker stage.
Change only the SDK image and the build can finish successfully with a service that exits before it starts listening.

Here's a minimal [**multi-stage Docker build**](https://milanjovanovic.tech/blog/docker-dotnet-developers) for a repository with `src/Orders.Api/Orders.Api.csproj`.
Both base images move to 11.0.
Exclude local `bin` and `obj` directories from the build context with `.dockerignore`.

```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:11.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish src/Orders.Api/Orders.Api.csproj -c Release \
    --no-self-contained -p:UseAppHost=false -o /out

FROM mcr.microsoft.com/dotnet/aspnet:11.0
WORKDIR /app
COPY --from=build /out/ .
USER $APP_UID
ENTRYPOINT ["dotnet", "Orders.Api.dll"]
```

Using the generic Linux tags also upgrades the distribution in this example.
The 10.0 runtime image uses Ubuntu 24.04 and the 11.0 image uses Ubuntu 26.04.
If your Dockerfile installs a system package or your service loads a native library, run the part of the app that uses it in the new image.
Microsoft maintains a [**list of runtime image tags and distributions**](https://github.com/dotnet/dotnet-docker/blob/main/README.aspnet.md).

Build the image and ask its own `dotnet` executable which shared frameworks it has.

```bash
docker build --pull -t orders-api:net11 .
docker run --rm --entrypoint dotnet orders-api:net11 --list-runtimes
```

The GA image listed both `Microsoft.NETCore.App 11.0.0` and `Microsoft.AspNetCore.App 11.0.0`.
A fresh pull of the 11.0 tag can show a later patch.
Starting the same DLL in the 10.0.15 image failed on Linux Arm64 with this diagnostic.

```text
Framework: 'Microsoft.NETCore.App', version '11.0.0' (arm64)
```

The requested framework and version come from `Orders.Api.runtimeconfig.json` alongside the published DLL.
Compare that file with `--list-runtimes` on the machine where startup failed.
On a VM running a framework-dependent web service, install the ASP.NET Core 11 runtime there.
A self-contained publish carries its own runtime and needs a new build for the target OS and architecture.

## Test the Published Image on the Oldest Node

.NET 11 raises the x86/x64 minimum to **x86-64-v2**.
A VM can report `x86_64` and still expose too few CPU features to start .NET 11.
Starting the service on a representative VM from each node pool catches an old virtual CPU model that a successful build on a new laptop would miss.

There's a separate change to the **ReadyToRun** target, the instruction set used when precompiling assemblies.
On Linux and Windows x64, that target rises to x86-64-v3.
A supported v2-only processor can still run the application, but some precompiled methods may need JIT compilation, so measure cold start on that node.
Arm64 has its own [**hardware requirements by operating system**](https://learn.microsoft.com/en-us/dotnet/core/compatibility/jit/11/minimum-hardware-requirements).

Run the image on a test host using the same architecture and CPU configuration as the deployment.
These commands assume the service maps an ASP.NET Core [**health check**](https://milanjovanovic.tech/blog/health-checks-in-asp-net-core) at `/healthz` and listens on the image's default port of 8080.

```bash
docker run --rm -d --name orders-net11 -p 127.0.0.1:8080:8080 orders-api:net11
curl --fail --retry 10 --retry-all-errors --retry-delay 1 http://127.0.0.1:8080/healthz
docker stop orders-net11
```

Before rolling out to the fleet, point one new instance at the staging database.
Fetch a seeded order and check that the response contains its ID.
That exercises the database driver and application code that a process-only health check can skip.
Keep the previous image digest available until the new instance passes those checks.

## Summary

If CI can't select an SDK, compare `global.json` with `dotnet --list-sdks` on the runner.
A successful publish followed by a missing-framework message points to the runtime in the final image.
If the same image starts on one VM and fails on another, check the CPU features exposed to the guest before changing the project file again.

## Frequently asked questions

### Do I have to upgrade from .NET 10 to .NET 11 to stay supported?

No. .NET 10 LTS and .NET 11 STS both reach end of support on November 14, 2028. Either version must stay current on servicing patches.

### Why does a .NET 11 app build successfully but fail to start in Docker?

The build stage and the final stage can use different versions of .NET. A framework-dependent ASP.NET Core service targeting net11.0 needs compatible Microsoft.NETCore.App and Microsoft.AspNetCore.App shared frameworks in the final image. Check them with dotnet --list-runtimes inside that image.

### Does .NET 11 require AVX2 on Linux x64?

No. The minimum runtime baseline is x86-64-v2. Linux x64 ReadyToRun images target x86-64-v3, which includes AVX2. A supported v2-only CPU can still run the app, with possible extra JIT compilation at startup.
