Course contentsShow
← C#
Lesson 1830 of 1,90817. Deployment and Cloud ArchitectureFree lesson

Optimizing Multi-Stage Dockerfiles for .NET Runtimes

Write efficient, security-hardened Dockerfiles that minimize image sizes using build-time assets.

In this lesson, you will learn how to leverage multi-stage builds to produce minimal, security-hardened Docker images that contain only the necessary runtime assets for your .NET applications.

The Problem with Single-Stage Builds

When you build a standard .NET image, you often include the entire .NET SDK, build tools, source code, and intermediate artifacts. This results in bloated images (often over 800MB) that increase your attack surface by including unnecessary compilers and source files, and significantly slow down your CI/CD deployment pipelines.

The Multi-Stage Solution

Multi-stage builds allow you to use a "build" container to compile your code and a separate, minimal "runtime" container to execute it. By separating these concerns, you discard the heavy SDK and source code at the end of the process.

Consider this optimized Dockerfile structure:

# Stage 1: Build the application
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY . .
RUN dotnet publish -c Release -o /app/out --no-self-contained

# Stage 2: Serve the application
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# Only copy the published output
COPY --from=build /app/out .
# Run as a non-privileged user for security hardening
USER app
ENTRYPOINT ["dotnet", "YourApp.dll"]

Security and Optimization Principles

  1. Use Distroless or Alpine: For the runtime stage, use the aspnet base image, which is optimized for hosting. To further harden the image, consider using chiseled Ubuntu images, which remove the shell and package manager entirely, leaving zero room for attackers to execute unauthorized commands.
  2. Layer Caching: Always copy your .csproj files and run dotnet restore before copying the rest of your source code. This ensures that Docker caches your dependencies, preventing a full re-restore unless the project file itself changes.
  3. Non-Root Execution: Always explicitly set the USER directive to a non-privileged user. This prevents potential vulnerabilities in your application from escalating into host-level compromises.

By discarding the build environment and shipping only the compiled binaries, you drastically reduce the image footprint and tighten your application’s security perimeter.

Key Takeaway: Use multi-stage builds to strictly separate your build-time dependencies from the runtime environment, ensuring your final image is small, non-privileged, and contains only the binaries required to execute your code.