PluginBench
Skill
Pass
Audit score 90

container-publish

codewithmukesh/dotnet-claude-kit

Containerize .NET 10 apps without writing a Dockerfile using SDK-native publishing.

What is container-publish?

The .NET 10 SDK can build OCI-compliant container images directly via `dotnet publish /t:PublishContainer`, eliminating the need for a Dockerfile. Use this skill when containerizing .NET applications, configuring chiseled images for production, building multi-architecture containers, or publishing to registries.

  • Build container images directly from dotnet publish without a Dockerfile
  • Use chiseled base images for minimal attack surface (~110 MB vs 220 MB)
  • Configure all container settings as MSBuild properties in .csproj
  • Publish to Docker Hub, GitHub Container Registry, Azure Container Registry, and other registries
  • Build multi-architecture images (x64, arm64) with a single command
  • Export images as tarballs for scanning without a container runtime

How to install container-publish

npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill container-publish
Prerequisites
  • .NET 10 SDK installed
  • Docker daemon running (or use ContainerArchiveOutputPath to skip it)
  • Registry authentication via `docker login` when pushing to remote registries
Claude Code
Cursor
Windsurf
Cline

How to use container-publish

  1. 1.Set ContainerRepository and ContainerFamily (e.g., noble-chiseled) in your .csproj PropertyGroup
  2. 2.Add ContainerPort and ContainerEnvironmentVariable items to configure runtime settings
  3. 3.Run `dotnet publish /t:PublishContainer --os linux --arch x64` to build locally
  4. 4.Authenticate with `docker login` for your target registry
  5. 5.Run `dotnet publish /t:PublishContainer -p ContainerRegistry=<registry>` to push to a registry

Use cases

Good for
  • Containerize an ASP.NET Core API with production-hardened chiseled images
  • Build and push multi-arch container images to GitHub Container Registry in CI/CD
  • Create minimal container images for .NET Worker Services or console apps
  • Generate container tarballs for vulnerability scanning before registry push
  • Configure container ports, environment variables, and labels via project file
Who it's for
  • Backend developers building containerized .NET applications
  • DevOps engineers automating container builds in CI/CD pipelines
  • Teams deploying to Kubernetes or cloud container platforms
  • Developers seeking minimal, secure container images without shell access

container-publish FAQ

Do I need Docker installed to use SDK container publishing?

No. You can export images as tarballs using ContainerArchiveOutputPath without a container runtime, though a Docker daemon is required to push to registries.

What is a chiseled image and why use it?

Chiseled images (noble-chiseled) are distroless base images with no shell or package manager, reducing size to ~110 MB and attack surface to 7 Linux components vs 100+. Recommended for production.

How do I build images for multiple architectures (x64 and arm64)?

Set RuntimeIdentifiers and ContainerRuntimeIdentifiers to 'linux-x64;linux-arm64' in your .csproj, then run `dotnet publish /t:PublishContainer` once. The SDK creates an OCI Image Index.

Can I use SDK container publishing if my app needs OS packages?

No. SDK publishing cannot run apt-get or install native packages. Create a custom base image with a Dockerfile first, then reference it via ContainerBaseImage.

What registries are supported?

Any OCI-compliant registry: Docker Hub, GitHub Container Registry (ghcr.io), Azure Container Registry, and others. Authenticate with `docker login` first.

Full instructions (SKILL.md)

Source of truth, from codewithmukesh/dotnet-claude-kit.


name: container-publish description: > Dockerfile-less containerization using the .NET 10 SDK container publishing feature. Covers MSBuild properties, chiseled images, multi-arch builds, and registry publishing — all without writing a Dockerfile. Load this skill when the user wants to containerize without a Dockerfile, or mentions "dotnet publish container", "PublishContainer", "ContainerRepository", "ContainerFamily", "chiseled", "distroless", "container publish", "SDK container", "no Dockerfile", or "containerize without Docker".

Container Publishing (No Dockerfile)

Core Principles

  1. No Dockerfile needed — The .NET 10 SDK builds OCI-compliant container images directly from dotnet publish /t:PublishContainer. No Dockerfile to write or maintain.
  2. Chiseled images for production — Use noble-chiseled base images: no shell, no package manager, 7 Linux components vs 100+. Smallest attack surface.
  3. Non-root by default — .NET 10 container images run as the app user automatically. Never override to root in production.
  4. Configuration in the .csproj — All container settings are MSBuild properties, versioned with your project. No separate files to drift.

Patterns

Minimal Container Publish

No project file changes needed. Just publish:

dotnet publish /t:PublishContainer --os linux --arch x64

This creates a container image in your local Docker daemon using the default aspnet:10.0 base image.

Production-Ready .csproj Configuration

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

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ContainerRepository>mycompany/myapp-api</ContainerRepository>
    <ContainerFamily>noble-chiseled</ContainerFamily>
  </PropertyGroup>

  <ItemGroup>
    <ContainerPort Include="8080" Type="tcp" />
    <ContainerEnvironmentVariable Include="ASPNETCORE_HTTP_PORTS" Value="8080" />
    <ContainerEnvironmentVariable Include="DOTNET_EnableDiagnostics" Value="0" />
    <ContainerLabel Include="org.opencontainers.image.vendor" Value="MyCompany" />
  </ItemGroup>

</Project>

Publishing to a Registry

Authenticate with docker login first, then specify the registry:

# GitHub Container Registry
docker login ghcr.io
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerRegistry=ghcr.io \
    -p ContainerImageTag=1.0.0

# Azure Container Registry
az acr login --name myregistry
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerRegistry=myregistry.azurecr.io

# Docker Hub (requires username prefix in repository)
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerRegistry=docker.io \
    -p ContainerRepository=myuser/myapp

Multi-Architecture Images

Build images for multiple platforms with a single publish:

<PropertyGroup>
    <RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
    <ContainerRuntimeIdentifiers>linux-x64;linux-arm64</ContainerRuntimeIdentifiers>
</PropertyGroup>
dotnet publish /t:PublishContainer

This produces an OCI Image Index — registries serve the correct architecture automatically.

Multiple Tags

# Bash — note the quoting for semicolons
dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerImageTags='"1.0.0;latest"'

Or in the project file:

<ContainerImageTags>1.0.0;latest</ContainerImageTags>

Save as Tarball (No Docker Required)

No container runtime needed on the build machine. Useful for CI scanning:

dotnet publish /t:PublishContainer --os linux --arch x64 \
    -p ContainerArchiveOutputPath=./images/myapp.tar.gz

# Scan with Trivy before pushing
trivy image --input ./images/myapp.tar.gz

Chiseled Image Variants

ContainerFamilyUse CaseShellSize
(default)General purpose (Debian)Yes~220 MB
noble-chiseledProduction (no shell)No~110 MB
noble-chiseled-extraProduction with localization (ICU)No~120 MB
alpineSmall size, has shellYes~112 MB
<!-- Standard chiseled (InvariantGlobalization=true) -->
<ContainerFamily>noble-chiseled</ContainerFamily>

<!-- Chiseled with ICU for localization -->
<ContainerFamily>noble-chiseled-extra</ContainerFamily>

For Native AOT, the SDK auto-selects chiseled-aot:

<PublishAot>true</PublishAot>
<!-- SDK picks runtime-deps:10.0-noble-chiseled-aot automatically -->

CI/CD with GitHub Actions

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-dotnet@v5
        with:
          dotnet-version: '10.0.x'
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - run: |
          dotnet publish src/MyApp.Api/MyApp.Api.csproj \
            /t:PublishContainer --os linux --arch x64 \
            -p ContainerRegistry=ghcr.io \
            -p ContainerRepository=${{ github.repository_owner }}/myapp \
            -p ContainerImageTag=${{ github.sha }}

Anti-patterns

Don't Use the Deprecated Property Names

<!-- BAD — ContainerImageName is deprecated -->
<ContainerImageName>myapp</ContainerImageName>

<!-- GOOD — use ContainerRepository -->
<ContainerRepository>myapp</ContainerRepository>

Don't Use PublishProfile=DefaultContainer

# BAD — old approach, inconsistent across project types
dotnet publish -p:PublishProfile=DefaultContainer

# GOOD — use the MSBuild target directly
dotnet publish /t:PublishContainer

Don't Forget to Target Linux

# BAD on Windows — may produce a Windows container
dotnet publish /t:PublishContainer

# GOOD — explicitly target Linux
dotnet publish /t:PublishContainer --os linux --arch x64

Don't Skip Authentication Before Push

# BAD — fails with CONTAINER1013 error
dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io

# GOOD — authenticate first
docker login ghcr.io
dotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io

Don't Use SDK Publishing When You Need OS Packages

<!-- BAD — SDK container publish cannot run apt-get or install native packages -->
<!-- There is no RUN equivalent -->

<!-- GOOD — create a custom base image with a Dockerfile first, then reference it -->
<ContainerBaseImage>myregistry/custom-base:1.0</ContainerBaseImage>

Decision Guide

ScenarioRecommendation
Standard ASP.NET Core APISDK container publishing with noble-chiseled
Worker service / console appSDK container publishing (native .NET 10 support)
Needs native OS packagesDockerfile (or custom base image + SDK publishing)
Azure FunctionsDockerfile (not supported by SDK publishing)
CI without Docker daemonTarball output with ContainerArchiveOutputPath
Multi-arch deployment (x64 + arm64)ContainerRuntimeIdentifiers property
Production image sizenoble-chiseled (~110 MB) or Native AOT (~10 MB)
Local developmentdotnet publish /t:PublishContainer --os linux --arch x64
Registry pushContainerRegistry + docker login