WarpBuild LogoWarpBuild Docs

C# with .NET

Best practices for Dockerfile for C# with .NET

🐳 Annotated Dockerfile for C# with .NET:

# Stage 1: Build the application
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
 
# Set working directory
WORKDIR /src
 
# Copy csproj file(s) and restore dependencies
COPY *.csproj ./
RUN --mount=type=cache,target=/root/.nuget/packages \
    dotnet restore
 
# Copy everything else and build the project
COPY . ./
RUN --mount=type=cache,target=/root/.nuget/packages \
    dotnet build -c Release --no-restore
 
# Stage 2: Publish the application
FROM build AS publish
 
# Publish the application
RUN --mount=type=cache,target=/root/.nuget/packages \
    dotnet publish -c Release --no-build -o /app/publish
 
# Stage 3: Create the runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
 
# Set working directory
WORKDIR /app
 
# Create a non-root user
RUN useradd -u 1000 -r -s /bin/false dotnetuser && \
    mkdir -p /home/dotnetuser && \
    chown -R dotnetuser:dotnetuser /home/dotnetuser
 
# Copy published files from the publish stage
COPY --from=publish --chown=dotnetuser:dotnetuser /app/publish .
 
# Use the non-root user to run the application
USER dotnetuser
 
# Set the entrypoint
ENTRYPOINT ["dotnet", "YourAppName.dll"]

🔍 Why these are best practices:

✅ Multi-stage builds

  • Separates build environment from runtime environment.
  • Dramatically reduces final image size.
  • Eliminates build tools and intermediate artifacts from the runtime image.

✅ NuGet package caching

  • Uses Docker's cache mount feature to avoid downloading packages repeatedly.
  • Significantly speeds up build times, especially for large projects.
  • Prevents redundant network requests during iterative builds.

✅ Build flow optimization

  • Restores dependencies separately from building for better layer caching.
  • Copies only the project file first to take advantage of Docker's cache.
  • Optimizes the build process by using the --no-restore flag.

✅ Minimal runtime image

  • Uses the ASP.NET runtime image instead of the full SDK.
  • Includes only what's needed to run the application, not build it.
  • Reduces attack surface and resource usage in production.

✅ Security best practices

  • Runs the application as a non-root user.
  • Follows the principle of least privilege.
  • Sets proper file ownership to enhance security.

🚀 Additional Dockerfile best practices you can adopt:

Use Alpine-based images for even smaller footprint

For applications that don't require the full ASP.NET runtime:

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS final
 
# ... other steps ...
 
# For self-contained applications with trimming
COPY --from=publish /app/publish .

Enable assembly trimming and ahead-of-time compilation

Reduce application size and improve startup time:

# In the publish stage
RUN dotnet publish -c Release -r linux-x64 --self-contained true \
    /p:PublishTrimmed=true /p:PublishAot=true -o /app/publish

Add health checks

Monitor application health for container orchestration:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:80/health || exit 1

Use .dockerignore

Exclude unnecessary files from your Docker build context:

bin/
obj/
TestResults/
.vscode/
.git/
.github/
.gitignore
Dockerfile
README.md
*.sln

Configuration for development vs. production

Use environment variables and build arguments to switch between environments:

# Set environment variables for different configurations
ENV ASPNETCORE_ENVIRONMENT=Production \
    DOTNET_EnableDiagnostics=0
 
# For development builds
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS dev
WORKDIR /src
COPY . .
ENV ASPNETCORE_ENVIRONMENT=Development
ENTRYPOINT ["dotnet", "watch", "run", "--urls", "http://0.0.0.0:5000"]

Optimize for container resource limits

Configure .NET to respect container resources:

# In the final stage
ENV DOTNET_EnableDiagnostics=0 \
    DOTNET_gcServer=1 \
    DOTNET_gcConcurrent=1 \
    DOTNET_ThreadPool_UnfairSemaphoreSpinLimit=0

Support for multiple architectures

Build for multiple platforms:

FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG TARGETARCH
 
# ... other steps ...
 
RUN dotnet publish -c Release -o /app/publish \
    -r linux-$TARGETARCH --self-contained true

By following these practices, you'll create Docker images for your .NET applications that are secure, efficient, and optimized for both development and production environments. These approaches help minimize build times, reduce image sizes, improve startup performance, and ensure consistent behavior across different deployment environments.

Last updated on