Optimal Dockerfile for .NET ASP.NET Core
Below is an example Dockerfile that we recommend at Depot for building images for .NET ASP.NET Core applications.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY src/WebApp/WebApp.csproj src/WebApp/
COPY src/WebApp.Core/WebApp.Core.csproj src/WebApp.Core/
COPY Directory.Build.props ./
COPY *.sln ./
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restore
COPY src/ src/
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet publish "src/WebApp/WebApp.csproj" \
--no-restore \
--configuration Release \
--output /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
RUN groupadd -g 1001 appgroup && \
useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app/publish .
USER appuser
ENV DOTNET_RUNNING_IN_CONTAINER=true \
DOTNET_EnableDiagnostics=0 \
HTTP_PORT=8080 \
ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "WebApp.dll"]Explanation of the Dockerfile
At a high level, here are the things we're optimizing in our Docker build for a .NET ASP.NET Core application:
- Multi-stage builds for smaller final images
- NuGet cache mounts for dependency caching
- Security optimizations with non-root users
- Production-optimized ASP.NET Core configuration
Stage 1: FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /srcWe use the official .NET 8 SDK image for the build stage, providing all necessary tools for compilation and publishing.
Project file and dependency restoration
COPY src/WebApp/WebApp.csproj src/WebApp/
COPY src/WebApp.Core/WebApp.Core.csproj src/WebApp.Core/
COPY Directory.Build.props ./
COPY *.sln ./
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restoreWe copy only the project files and solution file first for optimal layer caching. This pattern ensures that package restoration only runs when dependencies change, not when source code changes. The cache mount persists NuGet packages between builds.
Source code and publishing
COPY src/ src/
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet publish "src/WebApp/WebApp.csproj" \
--no-restore \
--configuration Release \
--output /app/publishAfter copying the source code, we publish the application:
--no-restoreskips restoration since we've already restored packages--configuration Releasebuilds in release mode for production--output /app/publishspecifies the output directory for the published files
Stage 2: FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
RUN groupadd -g 1001 appgroup && \
useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app/publish .The runtime stage uses the ASP.NET Core runtime image, which is much smaller than the SDK. We create a non-root user for security and copy only the published application files.
Runtime configuration
USER appuser
ENV DOTNET_RUNNING_IN_CONTAINER=true \
DOTNET_EnableDiagnostics=0 \
HTTP_PORT=8080 \
ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "WebApp.dll"]We configure the runtime environment:
- Run as non-root user for security
DOTNET_RUNNING_IN_CONTAINER=trueenables container-optimized settingsDOTNET_EnableDiagnostics=0disables diagnostics for productionHTTP_PORT=8080sets the HTTP portASPNETCORE_ENVIRONMENT=Productionsets the environment
Understanding BuildKit Cache Mounts
Cache mounts in this Dockerfile speed up builds by persisting the package manager cache. This means that even when a layer needs to be rebuilt, your package manager only fetches what's new or updated. This Dockerfile uses the following cache mount syntax:
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restoreCache Mount Parameters Explained
-
type=cache: Specifies this is a cache mount that persists across builds. -
Multiple cache targets:
/root/.nuget/packages: Global NuGet package cache/root/.local/share/NuGet/v3-cache: NuGet v3 API cache/root/.local/share/NuGet/plugins-cache: NuGet plugins cache/tmp/NuGetScratchroot: Temporary extraction directory
For more information regarding NuGet cache mounts, please visit the official Microsoft documentation.