Building a Linux Based Minimal and Efficient .NET 8 Application Docker Image
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.
BUILD_CONFIGURATION
: Set toRelease
for optimized production builds.RUNTIME=linux-musl-x64
: Targets thelinux-musl
runtime, specifically tailored for Alpine Linux. This ensures compatibility with musl-based systems commonly used in containerized environments like Kubernetes.
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:
-r "$RUNTIME"
: Ensures the published app is optimized for the specified runtime (linux-musl-x64).--self-contained false
: Keeps the application lightweight by relying on the shared .NET runtime instead of bundling it./p:PublishReadyToRun=true
: Precompiles assemblies to improve startup performance, which is crucial for production workloads.
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
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
: Set tofalse
to enable full globalization support, such as culture-specific formatting.icu-libs
: Installed to provide ICU libraries required for globalization.tzdata
: Adds timezone data, ensuring the application handles time zones correctly.
WORKDIR /app
USER app
EXPOSE 8080
USER app
: Enhances security by running the application as a non-root user.EXPOSE 8080
: Exposes port8080
, commonly used for ASP.NET Core applications.
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:
- Compatibility: Ensures the application runs seamlessly in musl-based environments like Kubernetes or Docker containers using Alpine Linux.
- Size Efficiency: Produces smaller binaries compared to glibc-based Linux distributions.
- Performance: Musl is known for its lightweight and efficient design, making it ideal for containerized workloads.
Why Alpine Linux?
Alpine Linux is widely used in containerized environments due to its:
- Small Size: A base image is typically less than 5 MB.
- Security: A minimal surface area reduces vulnerabilities.
- Performance: Optimized for resource-constrained environments.
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.