Building a Linux Based Minimal and Efficient .NET 8 Application Docker Image

Published on Nov 24, 2024 | Reading time: 3 min


In this article, we will walk through a multi-stage Dockerfile optimized for building and running a .NET 8 application targeted for Linux. This setup emphasizes performance, size, and compatibility, particularly for applications running in Linux-based environments.


Dockerfile Breakdown

Stage 1: Build Stage

FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG BUILD_CONFIGURATION=Release
ARG RUNTIME=linux-musl-x64
WORKDIR /src

We start with the official .NET 8 SDK image (mcr.microsoft.com/dotnet/sdk:8.0-alpine) for the build stage. The alpine variant is chosen for its small size and compatibility with the lightweight Linux musl runtime.

COPY ["MyApplication.WebAPI/MyApplication.WebAPI.csproj", "MyApplication.WebAPI/"]
RUN dotnet restore "./MyApplication.WebAPI/MyApplication.WebAPI.csproj" -r "$RUNTIME"

We copy only the project file first to take advantage of Docker’s caching mechanism, which avoids re-downloading dependencies unless the csproj file changes. The runtime (linux-musl-x64) ensures the restored packages are compatible with the target Linux environment.

COPY . .

After restoring dependencies, the full source code is copied. This order reduces unnecessary rebuilds during development.

RUN dotnet publish "./MyApplication.WebAPI/MyApplication.WebAPI.csproj" \
    -c "$BUILD_CONFIGURATION" \
    -r "$RUNTIME" \
    --self-contained false \
    -o /app/publish \
    /p:UseAppHost=false \
    /p:PublishReadyToRun=true

Here’s a breakdown of the key options used in the dotnet publish command:


Stage 2: Final Stage

FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine

The runtime stage uses mcr.microsoft.com/dotnet/aspnet:8.0-alpine, which is purpose-built for hosting ASP.NET Core applications and is significantly smaller than the SDK image.

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
RUN apk add --no-cache icu-libs tzdata
WORKDIR /app
USER app
EXPOSE 8080
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApplication.WebAPI.dll"]

The published application is copied from the build stage, and the entry point specifies the main DLL file to start the application.


Complete Dockerfile

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG BUILD_CONFIGURATION=Release
ARG RUNTIME=linux-musl-x64
WORKDIR /src

# Copy only project files for caching
COPY ["MyApplication/MyApplication.csproj", "MyApplication/"]

# Restore dependencies
RUN dotnet restore \
    "./MyApplication/MyApplication.csproj" \
    -r "$RUNTIME"

# Copy the entire source after restore to prevent re-restoring
COPY . .

# Publish the application
RUN dotnet publish \
    "./MyApplication/MyApplication.csproj" \
    -c "$BUILD_CONFIGURATION" \
    -r "$RUNTIME" \
    --self-contained false \
    -o /app/publish \
    /p:UseAppHost=false \
    /p:PublishReadyToRun=true

# Final Stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
RUN apk add --no-cache icu-libs tzdata

WORKDIR /app
USER app
EXPOSE 8080

COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApplication.dll"]

Why Use the linux-musl Runtime?

Targeting Linux Images

The linux-musl-x64 runtime is specifically designed for environments that use musl as the standard C library (such as Alpine Linux). By targeting this runtime:

Why Alpine Linux?

Alpine Linux is widely used in containerized environments due to its:


Conclusion

This Dockerfile is optimized for creating lightweight and efficient containers for .NET 8 applications targeted for Linux. By using the linux-musl-x64 runtime and Alpine Linux, you ensure a small, compatible, and high-performing application that’s ready for modern cloud-native deployments.