Publishing container image with a Dockerfile is the mainline scenario for container image publishing an what many people are most familiar.
We have a set of useful samples at dotnet/dotnet-docker. They will be used in the remainder of this document, assuming that the repo has been cloned locally.
dotnetapp demonstrates how to build a console app.
It uses a standard multi-stage build pattern with SDK and runtime images.
$ pwd
/home/rich/git/dotnet-docker/samples/dotnetapp
$ grep FROM Dockerfile.chiseled
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
FROM mcr.microsoft.com/dotnet/runtime:10.0-noble-chiseledBuild the app.
$ docker build -t dotnetapp -f Dockerfile.chiseled .
$ docker images dotnetapp
REPOSITORY TAG IMAGE ID CREATED SIZE
dotnetapp latest c3884c20fffe 2 minutes ago 97MBRun the app
$ docker run --rm dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP!!!!!!! 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
OSArchitecture: X64
OSDescription: Ubuntu 24.04.3 LTS
FrameworkDescription: .NET 10.0.0
UserName: app
HostName : c1b81d931f83
ProcessorCount: 24
TotalAvailableMemoryBytes: 64907018240 (60.45 GiB)The app respects container limits
$ docker run --rm -m 50mb --cpus 2 dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP!!!!!!! 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
OSArchitecture: X64
OSDescription: Ubuntu 24.04.3 LTS
FrameworkDescription: .NET 10.0.0
UserName: app
HostName : 47d7a2c4048a
ProcessorCount: 2
TotalAvailableMemoryBytes: 39321600 (37.50 MiB)
cgroup memory constraint: /sys/fs/cgroup/memory.max
cgroup memory limit: 52428800 (50.00 MiB)
cgroup memory usage: 6434816 (6.14 MiB)
GC Hard limit %: 75aspnetapp demonstrates how to build an ASP.NET Core app.
It uses a standard multi-stage build pattern with SDK and runtime images.
$ pwd
/home/rich/git/dotnet-docker/samples/aspnetapp
$ grep FROM Dockerfile.chiseled
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseledBuild the app.
$ docker build -t aspnetapp -f Dockerfile.chiseled .
$ docker images aspnetapp
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp latest bec338841eac 12 seconds ago 138MBRun the app.
$ docker run --rm -d -p 8000:8080 -m 50mb --cpus 0.5 aspnetapp
2d7ac1ad7863da2d5708b853f5b43ca843280285883eec5a64c472fe660d726d
$ curl http://localhost:8000/Environment
{"runtimeVersion":".NET 10.0.0","osVersion":"Ubuntu 24.04.3 LTS","osArchitecture":"X64","user":"app","processorCount":1,"totalAvailableMemoryBytes":39321600,"memoryLimit":52428800,"memoryUsage":35852288,"hostName":"cea8e3897792"}
$ docker kill 2d7ac1ad7863da2d5708b853f5b43ca843280285883eec5a64c472fe660d726d
2d7ac1ad7863da2d5708b853f5b43ca843280285883eec5a64c472fe660d726dChiseled images default to using a non-root user.
releasesapi is a service and is configured to use native AOT.
It uses a standard multi-stage build pattern with SDK and runtime AOT images.
$ pwd
/home/rich/git/dotnet-docker/samples/releasesapi
$ grep FROM Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble-aot AS build
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseledBuild the app
$ docker build --pull -t releasesapi .
$ docker images releasesapi
REPOSITORY TAG IMAGE ID CREATED SIZE
releasesapi latest 33d53062ff66 16 minutes ago 27.4MBUpdate the project file to optimize for size:
$ cat releasesapi.csproj | grep Optimization
<OptimizationPreference>Size</OptimizationPreference>
$ cat releasesapi.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<!--
Learn more about Native AOT deployment
https://learn.microsoft.com/dotnet/core/deploying/native-aot
-->
<PublishAot>true</PublishAot>
<OptimizationPreference>Size</OptimizationPreference>
</PropertyGroup>
</Project>Observe the size difference:
$ docker build --pull -t releasesapi .
$ docker images releasesapi
REPOSITORY TAG IMAGE ID CREATED SIZE
releasesapi latest dda01eec3bf9 3 hours ago 27.1MBSmall drop in size.
Run the app.
$ docker run --rm -d -p 8000:8080 releasesapi
16002fe9eedbbf7dc4d32b72334fc6ce99d37525047248507b1a07f4ef134f63
$ curl http://localhost:8000/healthz
Healthy
$ curl -s http://localhost:8000/releases | jq
{
"reportDate": "11/11/2025",
"versions": [
{
"version": "10.0",
"supported": true,
"eolDate": "2028-11-14",
"supportEndsInDays": 1098,
"releases": [
{
"releaseDate": "2025-11-11",
"releasedDaysAgo": 0,
"releaseVersion": "10.0.0",
"security": false,
"cveList": []
},
{
"releaseDate": "2025-10-14",
"releasedDaysAgo": 28,
"releaseVersion": "10.0.0-rc.2",
"security": true,globalapp relies on globalization data.
Build the app.
$ pwd
/home/rich/git/dotnet-docker/samples/globalapp
$ docker build --pull -t globalapp .
$ docker images globalapp
REPOSITORY TAG IMAGE ID CREATED SIZE
globalapp latest 47676a6edbcf 5 minutes ago 135MBRun the app.
$ docker run --rm globalapp
Hello, World!
****Print baseline timezones**
Utc: (UTC) Coordinated Universal Time; 11/11/2025 21:27:24
Local: (UTC) Coordinated Universal Time; 11/11/2025 21:27:24
****Print specific timezone**
Home timezone: America/Los_Angeles
DateTime at home: 11/11/2025 13:27:24
****Culture-specific dates**
Current: 11/11/2025
English (United States) -- en-US:
11/11/2025 9:27:24 PM
11/11/2025
9:27 PM
English (Canada) -- en-CA:
2025-11-11 9:27:24 p.m.
2025-11-11
9:27 p.m.
French (Canada) -- fr-CA:
2025-11-11 21 h 27 min 24 s
2025-11-11
21 h 27
Croatian (Croatia) -- hr-HR:
11. 11. 2025. 21:27:24
11. 11. 2025.
21:27
jp (Japan) -- jp-JP:
11/11/2025 21:27:24
11/11/2025
21:27
Korean (South Korea) -- ko-KR:
2025. 11. 11. 오후 9:27:24
2025. 11. 11.
오후 9:27
Portuguese (Brazil) -- pt-BR:
11/11/2025 21:27:24
11/11/2025
21:27
Chinese (China) -- zh-CN:
2025/11/11 21:27:24
2025/11/11
21:27
****Culture-specific currency:**
Current: ¤1,337.00
en-US: $1,337.00
en-CA: $1,337.00
fr-CA: 1 337,00 $
hr-HR: 1.337,00 €
jp-JP: ¥ 1337
ko-KR: ₩1,337
pt-BR: R$ 1.337,00
zh-CN: ¥1,337.00
****Japanese calendar**
08/18/2019
01/08/18
平成元年8月18日
平成元年8月18日
****String comparison**
Comparison results: `0` mean equal, `-1` is less than and `1` is greater
Test: compare i to (Turkish) İ; first test should be equal and second not
0
-1
Test: compare Å Å; should be equal
0We can build also build images for Alpine and Ubuntu Chiseled, with significantly smaller sizes.
$ grep FROM Dockerfile.alpine
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine-extra
$ docker build --pull -t globalapp -f Dockerfile.alpine .
$ docker images globalapp
REPOSITORY TAG IMAGE ID CREATED SIZE
globalapp latest 064a4e159d3d 3 seconds ago 63.4MB
$ grep FROM Dockerfile.ubuntu-chiseled
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled-extra
$ docker build --pull -t globalapp -f Dockerfile.ubuntu-chiseled .
$ docker images globalapp
REPOSITORY TAG IMAGE ID CREATED SIZE
globalapp latest 804b6fcbc689 4 seconds ago 67.2MB