Skill v1.0.1
currentAutomated scan100/100+3 new
version: "1.0.1" 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 Registrydocker login ghcr.iodotnet publish /t:PublishContainer --os linux --arch x64 \-p ContainerRegistry=ghcr.io \-p ContainerImageTag=1.0.0# Azure Container Registryaz acr login --name myregistrydotnet 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 semicolonsdotnet 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 pushingtrivy 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-latestpermissions:packages: writesteps:- uses: actions/checkout@v5- uses: actions/setup-dotnet@v5with:dotnet-version: '10.0.x'- uses: docker/login-action@v3with:registry: ghcr.iousername: ${{ 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 typesdotnet publish -p:PublishProfile=DefaultContainer# GOOD — use the MSBuild target directlydotnet publish /t:PublishContainer
Don't Forget to Target Linux
# BAD on Windows — may produce a Windows containerdotnet publish /t:PublishContainer# GOOD — explicitly target Linuxdotnet publish /t:PublishContainer --os linux --arch x64
Don't Skip Authentication Before Push
# BAD — fails with CONTAINER1013 errordotnet publish /t:PublishContainer -p ContainerRegistry=ghcr.io# GOOD — authenticate firstdocker login ghcr.iodotnet 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 |