build.md
PicTv.Api — Dockerfile Build Optimization Changes
Summary
The api/PicTv.Api/Dockerfile was updated to fix Docker layer caching issues that caused slow builds. Most rebuilds were re-downloading NuGet packages and re-copying the entire repository even when only source code changed.
What Changed
Before
COPY ["api/PicTv.Api/PicTv.Api.csproj", "api/PicTv.Api/"]
RUN dotnet restore "api/PicTv.Api/PicTv.Api.csproj"
COPY . .
- Only
PicTv.Api.csprojwas copied before restore, but the project depends on 3 other projects (PicTv.Application,PicTv.Infrastructure,PicTv.Domain). The restore could not resolve ProjectReferences from the.csprojfiles alone, so the layer cache was effectively useless. COPY . .copied the entire repository (workers, tests, migrations, etc.) into the build stage. Any file change anywhere in the repo invalidated the build cache.
After
# 1. Copy all .csproj files in the dependency chain
COPY ["api/PicTv.Api/PicTv.Api.csproj", "api/PicTv.Api/"]
COPY ["core/PicTv.Application/PicTv.Application.csproj", "core/PicTv.Application/"]
COPY ["core/PicTv.Infrastructure/PicTv.Infrastructure.csproj", "core/PicTv.Infrastructure/"]
COPY ["core/PicTv.Domain/PicTv.Domain.csproj", "core/PicTv.Domain/"]
# 2. Restore with NuGet cache mount
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore "api/PicTv.Api/PicTv.Api.csproj"
# 3. Copy only the source directories needed for this build
COPY ["api/PicTv.Api/", "api/PicTv.Api/"]
COPY ["core/", "core/"]
Changes Explained
| # | Change | Why |
|---|---|---|
| 1 | Copy all 4 .csproj files before restore |
dotnet restore needs the full ProjectReference chain to resolve dependencies. Without them the restore layer is invalidated on every build. |
| 2 | --mount=type=cache,target=/root/.nuget/packages on restore, build, and publish steps |
Persists the NuGet package cache across builds. Even when the restore layer is invalidated (e.g., a new package is added), already-downloaded packages are reused from the cache mount instead of re-downloaded. |
| 3 | Replace COPY . . with selective COPY of api/PicTv.Api/ and core/ |
Changes to workers/, tests/, or other unrelated directories no longer invalidate the build cache. |
| 4 | --no-install-recommends on apt-get install in the final stage |
Skips optional apt packages, reducing final image size. |
Requirements
- BuildKit must be enabled. The
--mount=type=cachesyntax requires BuildKit. Docker Desktop has it enabled by default. For CI/CD pipelines or older Docker daemons, ensureDOCKER_BUILDKIT=1is set or the daemon config has"features": {"buildkit": true}. - Build context must be the repository root. The Dockerfile uses paths relative to the repo root (e.g.,
core/PicTv.Application/), so the build command must run from the root:docker build -f api/PicTv.Api/Dockerfile .
Project Dependency Chain
PicTv.Api
├── PicTv.Application
│ └── PicTv.Domain
└── PicTv.Infrastructure
If a new ProjectReference is added to any of these projects, the corresponding .csproj must also be copied in the Dockerfile before the restore step.
Risk
- No functional changes. The build output is identical.
- If BuildKit is not available in the CI/CD environment, the
--mount=type=cachelines will cause a build error. In that case, remove the--mountflags — the other optimizations (granular .csproj copy and selective source copy) still work without BuildKit.
Please review these changes and confirm they are compatible with our CI/CD environment. Specifically:
- Is BuildKit enabled in our pipeline?
- Are there any concerns with the cache mount approach?
- Any other feedback before we merge?