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- .NET 10 SDK installed
- Docker daemon running (or use ContainerArchiveOutputPath to skip it)
- Registry authentication via `docker login` when pushing to remote registries
How to use container-publish
- 1.Set ContainerRepository and ContainerFamily (e.g., noble-chiseled) in your .csproj PropertyGroup
- 2.Add ContainerPort and ContainerEnvironmentVariable items to configure runtime settings
- 3.Run `dotnet publish /t:PublishContainer --os linux --arch x64` to build locally
- 4.Authenticate with `docker login` for your target registry
- 5.Run `dotnet publish /t:PublishContainer -p ContainerRegistry=<registry>` to push to a registry
Use cases
- 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
- 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
No. You can export images as tarballs using ContainerArchiveOutputPath without a container runtime, though a Docker daemon is required to push to registries.
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.
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.
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.
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
- No Dockerfile needed — The .NET 10 SDK builds OCI-compliant container images directly from
dotnet publish /t:PublishContainer. No Dockerfile to write or maintain. - Chiseled images for production — Use
noble-chiseledbase images: no shell, no package manager, 7 Linux components vs 100+. Smallest attack surface. - Non-root by default — .NET 10 container images run as the
appuser automatically. Never override to root in production. - 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
| ContainerFamily | Use Case | Shell | Size |
|---|---|---|---|
| (default) | General purpose (Debian) | Yes | ~220 MB |
noble-chiseled | Production (no shell) | No | ~110 MB |
noble-chiseled-extra | Production with localization (ICU) | No | ~120 MB |
alpine | Small size, has shell | Yes | ~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
| Scenario | Recommendation |
|---|---|
| Standard ASP.NET Core API | SDK container publishing with noble-chiseled |
| Worker service / console app | SDK container publishing (native .NET 10 support) |
| Needs native OS packages | Dockerfile (or custom base image + SDK publishing) |
| Azure Functions | Dockerfile (not supported by SDK publishing) |
| CI without Docker daemon | Tarball output with ContainerArchiveOutputPath |
| Multi-arch deployment (x64 + arm64) | ContainerRuntimeIdentifiers property |
| Production image size | noble-chiseled (~110 MB) or Native AOT (~10 MB) |
| Local development | dotnet publish /t:PublishContainer --os linux --arch x64 |
| Registry push | ContainerRegistry + docker login |
Related skills
More from codewithmukesh/dotnet-claude-kit and the wider catalog.

convention-learner
Detect and enforce project-specific coding conventions by analyzing existing codebase patterns.

ddd
Domain-Driven Design tactical patterns for .NET: aggregates, value objects, domain events, and repositories.

de-sloppify
Systematic 7-step .NET code cleanup pipeline with verification between each phase.

dependency-injection
Dependency injection patterns for .NET 10: service lifetimes, keyed services, decorators, and common pitfalls.

docker
Multi-stage Docker builds and containerization patterns for .NET 10 applications.

dotnet-init
Interactively initialize .NET projects and generate customized CLAUDE.md files for Claude Code.