.NET SDK Container Publishing: No Dockerfile Required
Starting with .NET 7, the SDK can publish your application directly as a container image — no Dockerfile needed. This feature is built into dotnet publish and produces optimised container images using the same base images you'd choose manually.
The Basics
Add the container publishing package to your project:
<ItemGroup>
<PackageReference Include="Microsoft.NET.Build.Containers" Version="9.0.0" />
</ItemGroup>
For .NET 8 and later with the Microsoft.NET.Sdk.Web SDK, this package is included automatically. Then publish:
dotnet publish --os linux --arch x64 /t:PublishContainer
That's it. The SDK builds your application, selects an appropriate base image, and creates a container image in your local Docker daemon.
Configuring the Image
Control the image name, tag, and registry through MSBuild properties in your .csproj:
<PropertyGroup>
<ContainerImageName>mycompany/myapi</ContainerImageName>
<ContainerImageTag>1.0.0</ContainerImageTag>
<ContainerRegistry>ghcr.io</ContainerRegistry>
</PropertyGroup>
You can also set multiple tags:
<ItemGroup>
<ContainerImageTags Include="1.0.0;latest" />
</ItemGroup>
Choosing the Base Image
By default, the SDK picks the appropriate mcr.microsoft.com/dotnet/aspnet or mcr.microsoft.com/dotnet/runtime image based on your project type. Override it for smaller images:
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:9.0-alpine</ContainerBaseImage>
</PropertyGroup>
Or use chiselled images:
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled</ContainerBaseImage>
</PropertyGroup>
For self-contained AOT applications, use runtime-deps:
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/runtime-deps:9.0-noble-chiseled</ContainerBaseImage>
<PublishAot>true</PublishAot>
</PropertyGroup>
Environment Variables and Ports
Configure runtime settings declaratively:
<ItemGroup>
<ContainerEnvironmentVariable Include="ASPNETCORE_URLS" Value="http://+:8080" />
<ContainerEnvironmentVariable Include="DOTNET_EnableDiagnostics" Value="0" />
</ItemGroup>
<ItemGroup>
<ContainerPort Include="8080" Type="tcp" />
</ItemGroup>
Running as Non-Root
Set the container user:
<PropertyGroup>
<ContainerUser>app</ContainerUser>
</PropertyGroup>
The official .NET base images include an app user. This is the recommended configuration for production.
Publishing to a Registry
Push directly to a container registry without Docker:
dotnet publish --os linux --arch x64 /t:PublishContainer \
/p:ContainerRegistry=ghcr.io \
/p:ContainerImageName=mycompany/myapi \
/p:ContainerImageTag=1.0.0
The SDK pushes the image directly using the registry's HTTP API. You don't even need Docker installed — only the .NET SDK.
For authentication, the SDK reads credentials from Docker's config file (~/.docker/config.json) or environment variables:
export DOTNET_CONTAINER_REGISTRY_UNAME=myuser
export DOTNET_CONTAINER_REGISTRY_PWORD=mytoken
CI Pipeline Example
Here's a GitHub Actions workflow that builds and pushes a container image:
name: Build and Push Container
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Publish container
run: |
dotnet publish src/MyApi/MyApi.csproj \
--os linux --arch x64 \
/t:PublishContainer \
/p:ContainerRegistry=ghcr.io \
/p:ContainerImageName=${{ github.repository }} \
/p:ContainerImageTag=${{ steps.version.outputs.VERSION }}
When to Use SDK Publishing vs Dockerfile
SDK container publishing is excellent when:
- Your application is straightforward and doesn't need custom OS packages.
- You want to avoid maintaining a Dockerfile.
- You want Docker-free builds (e.g., in environments where Docker isn't available).
Stick with a Dockerfile when:
- You need to install system-level packages.
- You have complex multi-stage build logic.
- You need to run build-time scripts or copy non-.NET artefacts.
Comparison
| Feature | Dockerfile | SDK Publishing |
|---|---|---|
| Custom OS packages | Yes | No |
| Docker required | Yes | No |
| Layer caching control | Full | Limited |
| Maintenance burden | Higher | Lower |
| Multi-stage builds | Yes | Not needed |
Summary
SDK container publishing removes the Dockerfile from simple .NET applications. For APIs and console apps that don't need custom system packages, it's the fastest path from code to container. Configure everything in MSBuild properties, publish directly to a registry, and let the SDK handle the rest.