deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

.NET 8 and .NET 9 lose support on Nov 10; image tags won't show which pods are affected

.NET 8 and .NET 9 both stop receiving security patches on November 10, 2026. A dev.to walkthrough explains how to find which Kubernetes workloads still run them.

.NET 8 and .NET 9 lose support on Nov 10; image tags won't show which pods are affected

Two .NET releases go out of support on the same day

According to a post on dev.to, .NET 8 and .NET 9 both reach end of support on November 10, 2026. .NET 8 was the long-term support (LTS) release and .NET 9 the short-term support (STS) one, and their support windows close simultaneously. From that date onward, neither runtime receives security patches. The recommended destination is .NET 10, the current LTS release, which according to the post is supported until November 14, 2028.

The inventory problem

Before migrating anything, teams need to know which workloads actually run the old runtimes, and the dev.to author argues this is trickier than it first appears for Kubernetes users. The base image tag mcr.microsoft.com/dotnet/aspnet:8.0 states its version plainly, but almost nobody runs that image directly. Applications get built on top of it and pushed under an application-specific tag such as orders-api:1.42. At that point the .NET version is invisible to anything kubectl shows about running pods.

The usual fallback of exec-ing into a pod and running dotnet --list-runtimes has two drawbacks: chiseled container images ship without a shell, and the approach means entering production pods to answer what is essentially an inventory question.

Reading the version from the image config

Microsoft's official Dockerfiles, the post explains, embed the runtime version as environment variables in the image. The 8.0 runtime images — chiseled variants included — define DOTNET_VERSION (for example 8.0.33), and the ASP.NET images additionally set ASPNET_VERSION. Because environment variables are inherited through base image layers, those values survive into derived application images.

That makes it possible to audit a cluster without pulling or running anything. First collect every image reference across all pods, init containers included, using kubectl get pods -A -o json filtered with jq. Then query each image's config with crane from go-containerregistry and pull out the two version variables from the Env list. Any image reporting a version starting with 8. or 9. is on a runtime that no longer gets patches. The author notes both commands are read-only, and that docker image inspect shows the same Env data for images already present on a node.

Where the check falls short

Self-contained deployments are the blind spot. Applications published with --self-contained on top of the runtime-deps image carry the runtime inside the application folder, and runtime-deps sets no DOTNET_VERSION, so the environment-variable check comes back empty.

For those cases the post turns to the image's software bill of materials: the runtime shows up as a Microsoft.NETCore.App.Runtime.* component, and Trivy can emit a CycloneDX SBOM listing its name and version. Because Trivy has to fetch and unpack image layers, this path is slower than reading the config, so the author suggests running the environment-variable check across everything first and reserving the SBOM scan for images where it returns nothing but a .NET service is known to live.

Migrating and triaging

The mechanical migration is small for most services: bump the base image to 10.0, change TargetFramework to net10.0, rebuild, and run the test suite. The post cautions against assuming the jump is free, however, and directs readers to Microsoft's breaking-changes documentation for .NET 10, which is grouped by area so teams can skip what they don't use.

On prioritization, the author proposes starting with internet-facing services, then anything handling authentication or payments, then internal jobs and batch work. Services that genuinely can't move right away should be written down with an owner and a target date — an unpatched runtime that is documented is at least a deliberate choice, while one nobody has discovered becomes a crisis the day a CVE lands for it.

Why it matters

An unsupported runtime means every future .NET vulnerability in versions 8 and 9 goes unpatched, and the auditing gap described on dev.to makes that exposure easy to underestimate: application image tags deliberately hide the framework version, and whole categories of deployment — chiseled images, self-contained publishes — resist the quick checks teams habitually rely on. With .NET 10 supported into late 2028, migration is a bounded, one-time cost that removes an open-ended security liability, but only for the workloads teams actually manage to find before the deadline passes.

  • #dotnet
  • #kubernetes
  • #containers
  • #security
  • #devops

Related posts