Skip to content
Development
Skill

/sdk-container-publishing

Publish .NET services as container images with the built-in SDK tooling (dotnet publish /t:PublishContainer, Microsoft.NET.Build.Containers) - no Dockerfile required. Covers the MSBuild property surface, base-image inference, self-contained and AOT implications, and CI/CD

BOOST
From plugin
aaronontheweb-dotnet-skills
1.2k39 skills6 agents
Install
$ npx -y skills add aaronontheweb/dotnet-skills --skill sdk-container-publishing --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/sdk-container-publishing

Context preview

The summary Claude sees to decide when to auto-load this skill.

Publish .NET services as container images with the built-in SDK tooling (dotnet publish /t:PublishContainer, Microsoft.NET.Build.Containers) - no Dockerfile required. Covers the MSBuild property surface, base-image inference, self-contained and AOT implications, and CI/CD

SKILL.md

sdk-container-publishing.SKILL.md
name: sdk-container-publishing
description: Publish .NET services as container images with the built-in SDK tooling (dotnet publish /t:PublishContainer, Microsoft.NET.Build.Containers) - no Dockerfile required. Covers the MSBuild property surface, base-image inference, self-contained and AOT implications, and CI/CD publishing.
invocable: false

.NET SDK Container Publishing

When to Use This Skill

Use this skill when:

  • You want a container image for a .NET service and do not want to hand-write a Dockerfile
  • You want to know how to tag, name, or expose ports on an SDK-built image without a Dockerfile
  • You are wiring container publishing into CI / CD or a release pipeline
  • An SDK-built image pushes to the wrong registry, gets the wrong tag, or picks the wrong base image
  • You need a tarball of an image for scanning or air-gapped loading

Do not use it when:

  • You need a `RUN` step or arbitrary layer customization in the image. The SDK tooling cannot emulate `RUN`; write a Dockerfile instead.

What This Is

Since .NET 7, `dotnet publish` builds a container image natively. The `Microsoft.NET.Build.Containers` tooling ships with the SDK, so no Dockerfile is required and no Docker install is needed to produce an image. Docker or Podman is only required to run the image locally. This is documented in the [containerize with dotnet publish tutorial](https://learn.microsoft.com/en-us/dotnet/core/containers/sdk-publish).

The quickest path is one command:

dotnet publish /t:PublishContainer -c Release

That compiles the app and emits an image in one step. The image name defaults to the project's `AssemblyName`. Override it with `ContainerRepository` (renamed from `ContainerImageName` in earlier versions - see the [sdk-container-builds customization notes](https://github.com/dotnet/sdk-container-builds/blob/main/docs/ContainerCustomization.md)).

The Three Output Modes

The tooling writes the image in one of three ways. Pick based on where the image ends up.

| Mode | When to use | |------|-------------| | Local daemon (default) | Local dev. Pushes to the running Docker or Podman daemon with no extra config. | | Tarball | You want a file to scan, transfer, or load later. Set `ContainerArchiveOutputPath` to a `.tar.gz` path, then `docker load -i` or `podman load -i`. Useful in security scanning workflows. | | Registry push | Shipping. Set `ContainerRegistry` (for example `ghcr.io` or an internal registry) and the image is pushed directly. |

See the [containerize a .NET app reference](https://learn.microsoft.com/en-us/dotnet/core/containers/publish-configuration) for the full property surface.

Key Facts

  • **No Docker required to build.** The SDK creates the image itself; a runtime is only needed to run it. See the [SDK publish tutorial](https://learn.microsoft.com/en-us/dotnet/core/containers/sdk-publish).
  • **Image name defaults to `AssemblyName`.** Set `ContainerRepository` to override.
  • **Base image is inferred.** Self-contained projects get `mcr.microsoft.com/dotnet/runtime-deps`, ASP.NET Core gets `dotnet/aspnet`, other apps get `dotnet/runtime`, tagged for the TFM. Since SDK 8.0.200 the inference is size and security aware: musl RIDs pick Alpine variants, `PublishAot=true` picks the chiseled AOT runtime-deps variant. See the [base image inference notes](https://learn.microsoft.com/en-us/dotnet/core/containers/publish-configuration).
  • **Since .NET 8, the SDK tags images `latest` when you do not set a tag.** If your publish relies on the version tag you must set `ContainerImageTag` (single tag) or `ContainerImageTags` (list) explicitly. This is a real footgun in CI - see the [tag drift entry in references/troubleshooting.md](references/troubleshooting.md).
  • **Windows images need an explicit base image.** Microsoft no longer includes Windows variants in the manifest list. To target Windows, set `ContainerBaseImage` to a specific nanoserver tag (for example `mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022`). See the [Windows note](https://learn.microsoft.com/en-us/dotnet/core/containers/publish-configuration).
  • **Multi-architecture images** come from `ContainerRuntimeIdentifiers` (semicolon-delimited, a subset of `RuntimeIdentifiers`). The output is an OCI image index. Supported from SDK 8.0.405, 9.0.102, and 9.0.2xx onward.
  • **Insecure registries** are passed via the `DOTNET_CONTAINER_INSECURE_REGISTRIES` env var (comma-separated) starting in SDK 9.0.100. Since .NET 8.0.400 the SDK reads standard Docker / Podman config to decide HTTP vs HTTPS.

The Minimal Property Set (the pattern we use)

Most of our services only override what they need and let the SDK infer the rest. A minimal csproj looks like this:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ContainerRepository>my-service</ContainerRepository>
    <ContainerImageTags>$(VersionPrefix);latest</ContainerImageTags>
  </PropertyGroup>

  <ItemGroup>
    <ContainerPort Include="8080" Type="tcp" />
  </ItemGroup>
</Project>

Notes:

  • No `ContainerRegistry`, no `ContainerBaseImage`, no `ContainerUser`. The SDK defaults these correctly for most ASP.NET services.
  • `ContainerPort` items expose ports without a Dockerfile.
  • Tags follow semver: the package version plus a floating `latest` - but only when `$(VersionPrefix)` is set before this file is evaluated (on the command line, in `Directory.Build.props`, or earlier in the project file). The SDK applies its `1.0.0` default after the project body, so a plain local publish resolves `$(VersionPrefix)` to empty and tags the image `latest` only. See the [VersionPrefix ordering entry in references/troubleshooting.md](references/troubleshooting.md).

For the full property catalog, see [references/property-reference.md](references/property-reference.md).

CI / CD Publishing

In CI, publish against a registry and tag with the release name. The tag-driven pattern we use:

Read more
Ships withaaronontheweb-dotnet-skills

A comprehensive AI coding plugin with 30 skills and 5 specialized agents for professional .NET development. Battle-tested patterns from production systems covering C#, Akka.NET, Aspire, EF Core, testing, and performance optimization.

Get the whole plugin