mcpField
C
container-publish

container-publish

github.com/codewithmukeshCloud & DevOps7912.9k installsUpdated 12w ago
Repository

About container-publish

A skill for publishing .NET applications as OCI container images through the .NET 10 SDK. It covers image configuration, registry publishing, architecture targets, and Docker-free archive output.

Key features

  1. 01Build OCI images with dotnet publish
  2. 02Configure images through MSBuild properties
  3. 03Choose chiseled and other image variants
  4. 04Publish multi-architecture images
  5. 05Export images as tarballs

Use cases

  1. 01Package ASP.NET Core APIs for production
  2. 02Publish x64 and arm64 images
  3. 03Create container archives for CI scanning

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill container-publish

After installing, type/container-publishin Claude Code to confirm it loaded.

Details

From the project README

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

Comments

Markdown supported