Skip to content

ci(docker): the Dockerfile dependency-cache layer is a no-op, every image build is cold #265

Description

@beardthelion

The dependency-cache layer in Dockerfile has been a no-op since the workspace grew past four crates, so every image build compiles the full dependency graph from scratch.

What is wrong

Dockerfile:12-24 copies four crate manifests before priming the dependency cache:

COPY Cargo.toml Cargo.lock ./
COPY crates/gitlawb-core/Cargo.toml crates/gitlawb-core/
COPY crates/gitlawb-node/Cargo.toml crates/gitlawb-node/
COPY crates/gl/Cargo.toml crates/gl/
COPY crates/git-remote-gitlawb/Cargo.toml crates/git-remote-gitlawb/

The workspace has six members. Cargo.toml also lists crates/gitlawb-attest and crates/icaptcha-client, and crates/gl/Cargo.toml:15 takes a path dependency on the latter:

icaptcha-client = { path = "../icaptcha-client" }

So cargo fails while loading the workspace, before it ever resolves dependencies or reads Cargo.lock. The || true at the end of the layer swallows the error and the build proceeds as if the cache had been primed.

Reproduction

Built the first 24 lines of Dockerfile unchanged (docker 29.1.3), with a diagnostic step appended:

--- target/release/deps rlib count ---
0
--- registry cache size ---
no registry cache
--- rerun the layer command without || true ---
error: failed to load manifest for workspace member `/build/crates/gl`
referenced by workspace at `/build/Cargo.toml`

Caused by:
  failed to load manifest for dependency `icaptcha-client`

Caused by:
  failed to read `/build/crates/icaptcha-client/Cargo.toml`

Caused by:
  No such file or directory (os error 2)
exit=101

Zero compiled rlibs and no registry cache: the layer downloads nothing and builds nothing.

Impact

Every build of the main image is a cold build of the whole dependency graph, including the docker-build smoke test in pr-checks.yml (30-minute timeout) and the docker job that pushes to ghcr on release. The real build at Dockerfile:37 still produces a correct image, so this is wasted CI time rather than a correctness bug. It also silently defeats the layer-caching intent stated in the comment at line 18, so a reader would reasonably assume dependencies are cached when they are not.

Fix

Copy all six member manifests (and create the matching dummy src entries) so the layer can resolve, and consider dropping the || true that hid this. A trailing || true on a cache-priming step means the next crate added to the workspace breaks it silently again; if the layer is expected to succeed, letting it fail loudly is what would have surfaced this at the time.

Noticed while adding --locked to the shipping builds in #243. Filing separately rather than widening that PR.

The fix above is verified, not just proposed

Built the same layer with all six manifests copied, matching dummy src entries added, and the || true dropped:

--- rlib count after the fixed cache layer ---
619
--- registry cache ---
508M	/usr/local/cargo/registry

619 compiled rlibs and a 508 MB registry cache, against 0 and none today. The layer also succeeds without || true, so the guard is not load-bearing once the manifest list is complete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:bugDefect fix — wrong or unsafe behaviorkind:ciCI, release, or packaging pipelinesev:lowCosmetic, cleanup, or nice-to-have

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions