ASP.NET Core Dockerfile for .NET 10 (Multi-Stage, Non-Root)

This ASP.NET Core Dockerfile restores and publishes your app on the .NET 10 SDK image, then runs the output on the much smaller aspnet:10.0 runtime image. It uses the built-in non-root app user and Kestrel's default port 8080. It suits Minimal APIs, MVC and Razor Pages apps.

CI status
Build passing
Image size (uncompressed)
230.1 MB
Build time
6 s
Runtime image
mcr.microsoft.com/dotnet/aspnet:10.0
Port
8080
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY *.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "App.dll"]
.dockerignore
.git
bin/
obj/
.vs/
.env
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

    The SDK image contains the compiler and NuGet tooling. It's large, but it's only used to produce the published output.

  2. COPY *.csproj ./ RUN dotnet restore

    Restoring NuGet packages from the project file alone creates a cached layer, so code changes don't trigger a full package restore.

  3. RUN dotnet publish -c Release -o /app/publish --no-restore

    Compiles a Release build into a single output folder. --no-restore reuses the packages restored in the earlier cached layer.

  4. FROM mcr.microsoft.com/dotnet/aspnet:10.0

    The ASP.NET Core runtime image has no SDK or compiler, only what's needed to run web apps, which keeps the deployed image much smaller.

  5. COPY --from=build /app/publish .

    Copies only the published assemblies and content files into the runtime image.

  6. USER $APP_UID

    Official .NET images define a non-root app user and expose its ID as APP_UID. Switching to it means the app never runs as root.

  7. ENTRYPOINT ["dotnet", "App.dll"]

    Starts your assembly with the dotnet host as the main process, so stop signals trigger ASP.NET Core's graceful shutdown.

Build and run

terminal
docker build -t aspnet-core-app .
docker run --rm -p 8080:8080 aspnet-core-app

Build from the folder that contains App.csproj, then open http://localhost:8080. Set ASPNETCORE_ENVIRONMENT and connection strings with -e flags at runtime.

Other ASP.NET Core Dockerfile variants

The standard aspnet runtime image suits most apps. Chiseled Ubuntu images remove the shell and package manager for a smaller attack surface, and native AOT variants trade build complexity for faster startup. Verified variants appear in the table below.

VariantRuntime imageImage sizeCI
.NET 10 SDK build + ASP.NET runtimemcr.microsoft.com/dotnet/aspnet:10.0230.1 MBPassing

Troubleshooting

Could not execute because the specified command or file was not found: App.dll
Your assembly has a different name. Use the name of your .csproj file, or set AssemblyName in the project, so the ENTRYPOINT matches.
The app starts but nothing answers on port 8080
Something overrides the listening URL, such as launchSettings or a UseUrls call. In containers, configure ports with ASPNETCORE_HTTP_PORTS instead.
Permission denied writing to the app directory
The non-root user can't write where root-owned files were copied. Write temporary data to /tmp, or use a mounted volume for persistent files.

FAQ

Why is the port 8080 and not 80?
Since .NET 8, the official images default to port 8080 so apps can run as non-root users, which can't bind to privileged ports such as 80.
Do I need the SDK image in production?
No. The SDK is only needed to build. The aspnet runtime image is far smaller and has fewer packages that could need security updates.
How do I handle multiple projects in a solution?
Copy every project file the web project references before running restore, then publish the web project by its path. Large solutions often copy the solution file too.