An image is the immutable package (application, runtime, OS layers).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | # ====================================================================== # Q: What makes a good Dockerfile for a .NET service? # # An image is the immutable package (application, runtime, OS layers). A # container is a running instance of the image. # - Multi-stage build: the SDK (~800 MB) stays in the build stage. The final # image contains only the runtime. # - Chiseled images: no shell and no package manager, so a much smaller attack # surface and fewer CVEs. # - A .dockerignore with bin/, obj/, .git, *.user, so unrelated files or secrets # don't end up in the build context. # - No secrets in the image: not in ENV, not in appsettings.json. The layer # history can be read by anyone who has the image. # - Tag = commit SHA, never :latest. Build once, and deploy the same image to # every environment (Q14). # - Without a Dockerfile: dotnet publish -t:PublishContainer (.NET 8+) builds an # image directly from the SDK. # ====================================================================== # syntax=docker/dockerfile:1 FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src # 1. Project files first: the restore layer stays cached as long as the packages don't change COPY ["Shop.sln", "Directory.Packages.props", "./"] COPY ["src/Shop.Api/Shop.Api.csproj", "src/Shop.Api/"] COPY ["src/Shop.Domain/Shop.Domain.csproj", "src/Shop.Domain/"] COPY ["src/Shop.Infrastructure/Shop.Infrastructure.csproj", "src/Shop.Infrastructure/"] RUN --mount=type=cache,target=/root/.nuget/packages \ dotnet restore src/Shop.Api/Shop.Api.csproj # 2. Then the code COPY . . RUN --mount=type=cache,target=/root/.nuget/packages \ dotnet publish src/Shop.Api/Shop.Api.csproj -c Release -o /app --no-restore # 3. Runtime image: small, no SDK, no shell (chiseled) FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled AS final WORKDIR /app COPY --from=build /app . USER $APP_UID # non-root EXPOSE 8080 # since .NET 8 the default port is 8080, not 80 ENTRYPOINT ["dotnet", "Shop.Api.dll"] |