Optimal Dockerfile for .NET Worker Service
Below is an example Dockerfile that we recommend at Depot for building images for .NET Worker Service applications.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY src/WorkerService/WorkerService.csproj src/WorkerService/
COPY *.sln ./
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restore
COPY src/ src/
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet publish "src/WorkerService/WorkerService.csproj" \
--no-restore \
--configuration Release \
--self-contained true \
--output /app/publish \
/p:PublishSingleFile=true
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS runtime
WORKDIR /app
RUN groupadd -g 1001 appgroup \
&& useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser
COPY --from=build --chown=appuser:appgroup /app/publish/WorkerService .
USER appuser
ENV DOTNET_RUNNING_IN_CONTAINER=true \
DOTNET_EnableDiagnostics=0
ENTRYPOINT ["./WorkerService"]Explanation of the Dockerfile
At a high level, here are the things we're optimizing in our Docker build for a .NET Worker Service:
- Self-contained deployment for standalone executables
- Minimal runtime dependencies
- Single-file publishing for simplified deployment
- Security optimizations with non-root users
Stage 1: FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY src/WorkerService/WorkerService.csproj src/WorkerService/
COPY *.sln ./We use the .NET 8 SDK image and set up the build environment for building the Worker Service application.
Dependency restoration
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restoreWe restore NuGet packages with cache mounts to persist dependencies between builds, improving build performance on subsequent runs.
Self-contained publishing
COPY src/ src/
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet publish "src/WorkerService/WorkerService.csproj" \
--no-restore \
--configuration Release \
--self-contained true \
--output /app/publish \
/p:PublishSingleFile=trueThe publish command includes several important options:
--no-restoreskips package restoration since we already restored dependencies--self-contained trueincludes the .NET runtime in the output/p:PublishSingleFile=truecreates a single executable file--configuration Releasebuilds in release mode for production
Stage 2: FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS runtime
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS runtime
WORKDIR /app
RUN groupadd -g 1001 appgroup \
&& useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuserThe runtime stage uses the runtime-deps image, which contains only the native dependencies needed by self-contained .NET applications. We create a non-root user for security.
Runtime configuration
COPY --from=build --chown=appuser:appgroup /app/publish/WorkerService .
USER appuser
ENV DOTNET_RUNNING_IN_CONTAINER=true \
DOTNET_EnableDiagnostics=0
ENTRYPOINT ["./WorkerService"]We copy only the single executable file from the build stage, switch to a non-root user, and configure the .NET runtime for container environments.
Benefits of self-contained deployment
Self-contained deployment offers several advantages for Worker Services:
- No runtime dependencies: The image doesn't need the .NET runtime installed
- Smaller attack surface: Fewer components in the final image
- Version consistency: The exact .NET version is bundled with the application
- Simplified deployment: Single executable file is easier to manage
Runtime dependencies explained
The runtime-deps image provides only the native dependencies required by .NET:
- Minimal base: Essential system libraries for .NET execution
- No .NET runtime: The runtime is included in the self-contained executable
Understanding BuildKit Cache Mounts
Cache mounts in this Dockerfile speed up builds by persisting the package manager cache. This means that even when a layer needs to be rebuilt, your package manager only fetches what's new or updated. This Dockerfile uses the following cache mount syntax:
RUN --mount=type=cache,target=/root/.nuget/packages \
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache \
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache \
--mount=type=cache,target=/tmp/NuGetScratchroot \
dotnet restoreCache Mount Parameters Explained
-
type=cache: Specifies this is a cache mount that persists across builds. -
Multiple NuGet cache targets:
/root/.nuget/packages: Global NuGet package cache/root/.local/share/NuGet/v3-cache: NuGet v3 API metadata cache/root/.local/share/NuGet/plugins-cache: NuGet plugins cache/tmp/NuGetScratchroot: Temporary extraction directory
For more information regarding NuGet cache mounts, please visit the official Microsoft documentation.