Understanding a Multi-Stage Dockerfile for .NET 9 Application

Dockerfiles are essential for containerizing applications, allowing developers to package code, dependencies, and runtime environments into lightweight, portable containers. In this article, we’ll explore a multi-stage Dockerfile designed for a .NET Web API project named SampleWebAPI. This Dockerfile employs best practices to create a lightweight and secure container image while maintaining an efficient build process.


Dockerfile Overview

This Dockerfile is structured into multiple stages, each focusing on a specific phase of the containerization process:

  1. Debug Stage: Prepares the runtime environment for debugging.
  2. Build Stage: Compiles the application code.
  3. Publish Stage: Prepares the application for deployment.
  4. Runtime Stage: Creates the final container image for production.

Debug Stage

dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base USER $APP_UID WORKDIR /app EXPOSE 8080 EXPOSE 8081
  • Base Image:
    • Uses the official ASP.NET Core runtime image (aspnet:9.0) to provide a lightweight environment for running .NET applications.
  • User Setup:
    • Runs the container as a non-root user ($APP_UID) for improved security.
  • Working Directory:
    • Sets /app as the working directory for subsequent commands.
  • Port Exposure:
    • Declares ports 8080 and 8081 for the application. These ports are used for communication but must be explicitly mapped when deploying.

Build Stage

dockerfile
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["SampleWebAPI/SampleWebAPI.csproj", "SampleWebAPI/"] RUN dotnet restore "./SampleWebAPI/SampleWebAPI.csproj" COPY . . WORKDIR "/src/SampleWebAPI" RUN dotnet build "./SampleWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
  • Base Image:
    • Uses the .NET SDK image (sdk:9.0), which includes tools needed to restore, build, and publish the application.
  • Build Argument:
    • Accepts a configurable build mode (Release by default) for flexibility during the build process.
  • Dependency Restoration:
    • Copies the project file (.csproj) into the container and runs dotnet restore to download NuGet dependencies. This step is cached for efficiency.
  • Source Code:
    • Copies the entire source code into the container.
  • Build Command:
    • Compiles the project into binaries, outputting them to /app/build.

Publish Stage

dockerfile
FROM build AS publish ARG BUILD_CONFIGURATION=Release RUN dotnet publish "./SampleWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
  • Purpose:
    • Prepares the application for deployment by publishing it into a self-contained directory (/app/publish).
  • UseAppHost=false:
    • Disables the creation of a platform-specific executable wrapper, reducing unnecessary files in the container.

Runtime Stage

dockerfile
FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "SampleWebAPI.dll"]
  • Base Image:
    • Reuses the base stage, which contains only the ASP.NET Core runtime environment.
  • Working Directory:
    • Ensures the application runs from the /app directory.
  • File Copy:
    • Copies the published application files from the publish stage to the runtime container.
  • Entry Point:
    • Specifies the application’s entry point (SampleWebAPI.dll) to start the web server using dotnet.

Key Advantages of This Multi-Stage Dockerfile

1. Smaller Final Image

  • The final image only includes the runtime environment and published application, excluding SDK tools and source code. This reduces the container size and attack surface.

2. Efficient Build Process

  • By separating the build and runtime stages, caching can optimize dependency restoration and build times.

3. Enhanced Security

  • Runs the application as a non-root user ($APP_UID), adhering to best practices for container security.

4. Flexibility

  • Supports configurable build configurations (Release or Debug) through the BUILD_CONFIGURATION argument.

5. Portability

  • The published application can run on any host with Docker, as all dependencies are packaged within the container.

Here is the complete file

dockerfile
#######################################################
# DEBUG STAGE 
#######################################################
# Purpose: Specifies the base image for running the application in debug mode (Default for Debug configuration)
# mcr.microsoft.com/dotnet/aspnet:9.0:
# An official .NET runtime image that includes ASP.NET Core 9.0.
# Used to run .NET applications without the SDK (lighter than SDK images).
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
# Purpose: Sets the user for the container.
# $APP_UID: A build argument or environment variable defining the non-root user ID to improve security.
USER $APP_UID
# Purpose: Sets the working directory for the container to /app.
# Any subsequent commands will be executed relative to this directory.
WORKDIR /app
# Purpose: Declares the container will listen on ports 8080 and 8081.
# These ports are not bound automatically but act as metadata for tools like Docker Compose.
EXPOSE 8080
EXPOSE 8081

#######################################################
# BUILD STAGE
#######################################################
# Purpose: Specifies the base image for the build stage.
# mcr.microsoft.com/dotnet/sdk:9.0:
# A .NET SDK image that includes tools for building and restoring .NET projects.
# Larger than runtime-only images, but necessary for compiling and publishing .NET applications.
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
# Purpose: Declares a build argument (BUILD_CONFIGURATION) with a default value of Release.
# Allows flexibility to pass a different configuration during build (e.g., Debug).
ARG BUILD_CONFIGURATION=Release
# Purpose: Sets the working directory for this stage to /src.
WORKDIR /src
# Purpose: Copies the .csproj file of the project to the container.
# Allows restoring dependencies without copying the entire project (optimizes build caching).
COPY ["SampleWebAPI/SampleWebAPI.csproj", "SampleWebAPI/"]
# Purpose: Restores the dependencies for the project using dotnet restore.
# Ensures all required NuGet packages are downloaded.
RUN dotnet restore "./SampleWebAPI/SampleWebAPI.csproj"
# Purpose: Copies the entire source code to the container.
# Necessary for building the application after restoring dependencies.
COPY . .
# Purpose: Changes the working directory to the root of the project within the container.
WORKDIR "/src/SampleWebAPI"
# Purpose: Builds the project with the specified configuration (default is Release).
# Outputs the build artifacts to /app/build.
RUN dotnet build "./SampleWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build

#######################################################
# PUBLISH STAGE
#######################################################
# Purpose: Uses the build stage as the base for the publish stage.
FROM build AS publish
# Purpose: Allows specifying a build configuration (Release by default).
ARG BUILD_CONFIGURATION=Release
# Purpose: Publishes the application to /app/publish.
# /p:UseAppHost=false:
# Disables the generation of a platform-specific executable wrapper, which is unnecessary in containers.
RUN dotnet publish "./SampleWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

#######################################################
# RUNTIME STAGE
#######################################################
FROM base AS final
# Purpose: Specifies the final image for running the application.
# Uses the base stage, which includes only the runtime environment (aspnet:9.0).
WORKDIR /app
# Purpose: Sets the working directory for the final stage to /app.
COPY --from=publish /app/publish .
# Purpose: Sets the entry point for the container to execute the application.
# Runs the SampleWebAPI.dll using the dotnet runtime.
ENTRYPOINT ["dotnet", "SampleWebAPI.dll"]

How to Use This Dockerfile

  1. Build the Container:

    bash
    docker build -t samplewebapi .
  2. Run the Container:

    bash
    docker run -p 8080:8080 samplewebapi
  3. Access the Application:

    • Open your browser and navigate to http://localhost:8080.


Conclusion

This multi-stage Dockerfile demonstrates best practices for containerizing a .NET 9 Web API project. It separates the build, publish, and runtime stages, ensuring an optimized, secure, and production-ready container. With this approach, you can efficiently develop and deploy scalable .NET applications.

Feel free to customize this Dockerfile to suit your project’s specific requirements!

Comments

Popular posts from this blog

Configuring Any .NET 9.0 Program to Run in Docker: A Step-by-Step Guide

Understand .NET 9.0 Blazor Hosting Models