|
| 1 | +--- |
| 2 | +title: Optimal Dockerfile for .NET ASP.NET Core |
| 3 | +ogTitle: Optimal Dockerfile for .NET ASP.NET Core |
| 4 | +description: A sample optimal Dockerfile for building images for .NET ASP.NET Core applications from us at Depot. |
| 5 | +--- |
| 6 | + |
| 7 | +Below is an example `Dockerfile` that we recommend at Depot for building images for .NET ASP.NET Core applications. |
| 8 | + |
| 9 | +```dockerfile |
| 10 | +# syntax=docker/dockerfile:1 |
| 11 | + |
| 12 | +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build |
| 13 | + |
| 14 | +WORKDIR /src |
| 15 | + |
| 16 | +COPY src/WebApp/WebApp.csproj src/WebApp/ |
| 17 | +COPY src/WebApp.Core/WebApp.Core.csproj src/WebApp.Core/ |
| 18 | +COPY Directory.Build.props ./ |
| 19 | +COPY *.sln ./ |
| 20 | + |
| 21 | +RUN --mount=type=cache,target=/root/.nuget/packages \ |
| 22 | + --mount=type=cache,target=/root/.local/share/NuGet/v3-cache \ |
| 23 | + --mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \ |
| 24 | + --mount=type=cache,target=/tmp/NuGetScratchroot \ |
| 25 | + dotnet restore |
| 26 | + |
| 27 | +COPY src/ src/ |
| 28 | + |
| 29 | +RUN --mount=type=cache,target=/root/.nuget/packages \ |
| 30 | + --mount=type=cache,target=/root/.local/share/NuGet/v3-cache \ |
| 31 | + --mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \ |
| 32 | + --mount=type=cache,target=/tmp/NuGetScratchroot \ |
| 33 | + dotnet publish "src/WebApp/WebApp.csproj" \ |
| 34 | + --no-restore \ |
| 35 | + --configuration Release \ |
| 36 | + --output /app/publish |
| 37 | + |
| 38 | +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime |
| 39 | + |
| 40 | +RUN groupadd -g 1001 appgroup && \ |
| 41 | + useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser |
| 42 | + |
| 43 | +WORKDIR /app |
| 44 | + |
| 45 | +COPY --from=build --chown=appuser:appgroup /app/publish . |
| 46 | + |
| 47 | +USER appuser |
| 48 | + |
| 49 | +ENV DOTNET_RUNNING_IN_CONTAINER=true \ |
| 50 | + DOTNET_EnableDiagnostics=0 \ |
| 51 | + HTTP_PORT=8080 \ |
| 52 | + ASPNETCORE_ENVIRONMENT=Production |
| 53 | + |
| 54 | +ENTRYPOINT ["dotnet", "WebApp.dll"] |
| 55 | +``` |
| 56 | + |
| 57 | +## Explanation of the Dockerfile |
| 58 | + |
| 59 | +At a high level, here are the things we're optimizing in our Docker build for a .NET ASP.NET Core application: |
| 60 | + |
| 61 | +- Multi-stage builds for smaller final images |
| 62 | +- NuGet cache mounts for dependency caching |
| 63 | +- Security optimizations with non-root users |
| 64 | +- Production-optimized ASP.NET Core configuration |
| 65 | + |
| 66 | +### Stage 1: `FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build` |
| 67 | + |
| 68 | +```dockerfile |
| 69 | +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build |
| 70 | + |
| 71 | +WORKDIR /src |
| 72 | +``` |
| 73 | + |
| 74 | +We use the official .NET 8 SDK image for the build stage, providing all necessary tools for compilation and publishing. |
| 75 | + |
| 76 | +#### Project file and dependency restoration |
| 77 | + |
| 78 | +```dockerfile |
| 79 | +COPY src/WebApp/WebApp.csproj src/WebApp/ |
| 80 | +COPY src/WebApp.Core/WebApp.Core.csproj src/WebApp.Core/ |
| 81 | +COPY Directory.Build.props ./ |
| 82 | +COPY *.sln ./ |
| 83 | + |
| 84 | +RUN --mount=type=cache,target=/root/.nuget/packages \ |
| 85 | + --mount=type=cache,target=/root/.local/share/NuGet/v3-cache \ |
| 86 | + --mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \ |
| 87 | + --mount=type=cache,target=/tmp/NuGetScratchroot \ |
| 88 | + dotnet restore |
| 89 | +``` |
| 90 | + |
| 91 | +We copy only the project files and solution file first for optimal layer caching. This pattern ensures that package restoration only runs when dependencies change, not when source code changes. The cache mount persists NuGet packages between builds. |
| 92 | + |
| 93 | +#### Source code and publishing |
| 94 | + |
| 95 | +```dockerfile |
| 96 | +COPY src/ src/ |
| 97 | + |
| 98 | +RUN --mount=type=cache,target=/root/.nuget/packages \ |
| 99 | + --mount=type=cache,target=/root/.local/share/NuGet/v3-cache \ |
| 100 | + --mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \ |
| 101 | + --mount=type=cache,target=/tmp/NuGetScratchroot \ |
| 102 | + dotnet publish "src/WebApp/WebApp.csproj" \ |
| 103 | + --no-restore \ |
| 104 | + --configuration Release \ |
| 105 | + --output /app/publish |
| 106 | +``` |
| 107 | + |
| 108 | +After copying the source code, we publish the application: |
| 109 | + |
| 110 | +- `--no-restore` skips restoration since we've already restored packages |
| 111 | +- `--configuration Release` builds in release mode for production |
| 112 | +- `--output /app/publish` specifies the output directory for the published files |
| 113 | + |
| 114 | +### Stage 2: `FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime` |
| 115 | + |
| 116 | +```dockerfile |
| 117 | +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime |
| 118 | + |
| 119 | +RUN groupadd -g 1001 appgroup && \ |
| 120 | + useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser |
| 121 | + |
| 122 | +WORKDIR /app |
| 123 | + |
| 124 | +COPY --from=build --chown=appuser:appgroup /app/publish . |
| 125 | +``` |
| 126 | + |
| 127 | +The runtime stage uses the ASP.NET Core runtime image, which is much smaller than the SDK. We create a non-root user for security and copy only the published application files. |
| 128 | + |
| 129 | +#### Runtime configuration |
| 130 | + |
| 131 | +```dockerfile |
| 132 | +USER appuser |
| 133 | + |
| 134 | +ENV DOTNET_RUNNING_IN_CONTAINER=true \ |
| 135 | + DOTNET_EnableDiagnostics=0 \ |
| 136 | + HTTP_PORT=8080 \ |
| 137 | + ASPNETCORE_ENVIRONMENT=Production |
| 138 | + |
| 139 | +ENTRYPOINT ["dotnet", "WebApp.dll"] |
| 140 | +``` |
| 141 | + |
| 142 | +We configure the runtime environment: |
| 143 | + |
| 144 | +- Run as non-root user for security |
| 145 | +- `DOTNET_RUNNING_IN_CONTAINER=true` enables container-optimized settings |
| 146 | +- `DOTNET_EnableDiagnostics=0` disables diagnostics for production |
| 147 | +- `HTTP_PORT=8080` sets the HTTP port |
| 148 | +- `ASPNETCORE_ENVIRONMENT=Production` sets the environment |
| 149 | + |
| 150 | +## Understanding BuildKit Cache Mounts |
| 151 | + |
| 152 | +Cache mounts are one of the most powerful features for optimizing Docker builds with Depot. This Dockerfile uses the following cache mount syntax: |
| 153 | + |
| 154 | +```dockerfile |
| 155 | +RUN --mount=type=cache,target=/root/.nuget/packages \ |
| 156 | + --mount=type=cache,target=/root/.local/share/NuGet/v3-cache \ |
| 157 | + --mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \ |
| 158 | + --mount=type=cache,target=/tmp/NuGetScratchroot \ |
| 159 | + dotnet restore |
| 160 | +``` |
| 161 | + |
| 162 | +### Cache Mount Parameters Explained |
| 163 | + |
| 164 | +- **`type=cache`**: Specifies this is a cache mount that persists across builds. |
| 165 | + |
| 166 | +- **Multiple cache targets**: |
| 167 | + - **`/root/.nuget/packages`**: Global NuGet package cache |
| 168 | + - **`/root/.local/share/NuGet/v3-cache`**: NuGet v3 API cache |
| 169 | + - **`/root/.local/share/NuGet/plugins-cache`**: NuGet plugins cache |
| 170 | + - **`/tmp/NuGetScratchroot`**: Temporary extraction directory |
| 171 | + |
| 172 | +For more information regarding NuGet cache mounts, please visit the official [Microsoft documentation](https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders). |
0 commit comments