perf(dev): speed up dev-web image builds with target cache mount and higher job cap #458

Merged
longjacksonle merged 1 commit from perf/dev-web-build-speed into main 2026-06-27 04:01:39 +02:00

Why

just dev-web / dev-web-saas build the production multi-stage image, and local iteration was slow for two reasons.

  1. The Dockerfile capped cargo at CARGO_BUILD_JOBS=2. That cap exists so the desktop and OCI builds can share one CI runner, but a local dev build runs one image at a time on a many-core host, so it just throttled the slow release compile to 2 jobs.
  2. Every source edit recompiled the whole lets-chat-server crate in release mode, because nothing carried the release target dir across --build runs. cargo-chef caches dependencies but not the app crate.

What

  • Add a BuildKit cache mount over the builder stage's release target dir, keyed by BUILD_MODE so standalone and saas keep separate caches and never relink against each other. On a cold cache (every CI build; the first local build) it is seeded from the cargo-chef-cooked target with cp -a to preserve mtimes and keep dependency fingerprints valid; warm local rebuilds skip the copy and compile only changed crates. The cargo registry stays on the existing layer-cached COPY and is intentionally not cache-mounted (a registry cache mount would shadow that COPY and force a cold re-download).
  • Lift the job cap to 16 in both prod-shape dev compose files, overridable per-invocation with CARGO_BUILD_JOBS=N just dev-web. The Dockerfile default of 2 is unchanged, so CI behavior is untouched.

Tradeoff

On a cold cache the seed copies the cooked target once (cp -a). In CI, where the cache mount is not persisted, that is a small one-time copy per build instead of the prior layer COPY; the warm-rebuild win is local.

Test

  • docker compose --file compose.dev-web.yml config and the saas variant both resolve CARGO_BUILD_JOBS to 16 and honor an override.
  • docker build --check on ci-build/Dockerfile.web lints clean (cache-mount syntax and the BUILD_MODE-keyed id accepted).

🤖 Generated with Claude Code

## Why `just dev-web` / `dev-web-saas` build the production multi-stage image, and local iteration was slow for two reasons. 1. The Dockerfile capped cargo at `CARGO_BUILD_JOBS=2`. That cap exists so the desktop and OCI builds can share one CI runner, but a local dev build runs one image at a time on a many-core host, so it just throttled the slow release compile to 2 jobs. 2. Every source edit recompiled the whole `lets-chat-server` crate in release mode, because nothing carried the release `target` dir across `--build` runs. cargo-chef caches dependencies but not the app crate. ## What - Add a BuildKit cache mount over the builder stage's release `target` dir, keyed by `BUILD_MODE` so standalone and saas keep separate caches and never relink against each other. On a cold cache (every CI build; the first local build) it is seeded from the cargo-chef-cooked target with `cp -a` to preserve mtimes and keep dependency fingerprints valid; warm local rebuilds skip the copy and compile only changed crates. The cargo registry stays on the existing layer-cached `COPY` and is intentionally not cache-mounted (a registry cache mount would shadow that COPY and force a cold re-download). - Lift the job cap to 16 in both prod-shape dev compose files, overridable per-invocation with `CARGO_BUILD_JOBS=N just dev-web`. The Dockerfile default of 2 is unchanged, so CI behavior is untouched. ## Tradeoff On a cold cache the seed copies the cooked target once (`cp -a`). In CI, where the cache mount is not persisted, that is a small one-time copy per build instead of the prior layer COPY; the warm-rebuild win is local. ## Test - `docker compose --file compose.dev-web.yml config` and the saas variant both resolve `CARGO_BUILD_JOBS` to 16 and honor an override. - `docker build --check` on `ci-build/Dockerfile.web` lints clean (cache-mount syntax and the `BUILD_MODE`-keyed id accepted). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perf(dev): speed up dev-web image builds with target cache mount and higher job cap
Some checks failed
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Kingfisher (push) Failing after 8s
check-secrets / TruffleHog (push) Successful in 8s
check-secrets / TruffleHog (pull_request) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 8s
check-secrets / Kingfisher (pull_request) Failing after 10s
Check / clippy + fmt + tests (pull_request) Successful in 5m14s
Create release / Create release from merged PR (pull_request) Has been skipped
ac7952fc45
The production-shape `just dev-web` / `dev-web-saas` builds were slow for two reasons: the Dockerfile capped cargo at CARGO_BUILD_JOBS=2 (a CI runner-sharing cap, but local dev builds one image at a time on a many-core host), and every source edit recompiled the whole app crate in release mode because nothing carried the release target dir across `--build` runs. cargo-chef caches dependencies but not the app crate.

Add a BuildKit cache mount over the builder stage's release target dir, keyed by BUILD_MODE so standalone and saas keep separate caches. On a cold cache (every CI build; the first local build) it is seeded from the cargo-chef-cooked target via `cp -a` to preserve mtimes and keep dependency fingerprints valid; warm local rebuilds skip the copy and compile only changed crates. The cargo registry stays on the existing layer-cached COPY and is intentionally not cache-mounted, which would shadow that COPY and force a cold re-download.

Lift the job cap to 16 in both prod-shape dev compose files (overridable per-invocation with CARGO_BUILD_JOBS=N). The Dockerfile default of 2 is unchanged, so CI behavior is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-06-27 03:57:25 +02:00
longjacksonle deleted branch perf/dev-web-build-speed 2026-06-27 04:01:39 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!458
No description provided.