web: bake git version into the Docker image so /assets/ JS/CSS can be immutable-cached (follow-up to #868/#870) #222

Closed
opened 2026-10-04 23:04:38 +02:00 by adam · 0 comments
Owner

Summary

Follow-up to PR #868 (issue #870). #868 added the app-level AssetCacheControl middleware, but — per the independent review of #868 — it stamps the full immutable (year-long, never-revalidated) Cache-Control only when the running version is unique per build, i.e. git-derived (APP_VERSION != Config.version_fallback). A Docker image is built without .git (.dockerignore excludes it; web/Dockerfile copies only web/lib etc.), so determine_app_version falls back to the constant Config.version_fallback ("0.7.4"). The ?v= buster is therefore identical across every commit built between version bumps, so pinning immutable there could serve stale JS for a year, unrecoverable without a hard refresh on a rolling-main deploy.

To stay safe, #868 gives constant-fallback deployments a bounded public, max-age=300 instead of immutable. That is correct but means Docker — the primary deployment mode #870 targeted — does not get the full caching win (it revalidates every 5 min rather than caching a year).

Proposed change

Bake the real git version into the image so ?v= is unique per build, making immutable universally safe:

  1. .github/workflows/docker.yml: compute git describe --tags --long --abbrev=7 in the build job (the CI checkout has .git) and pass it as a Docker build arg.
  2. web/Dockerfile: ARG APP_VERSION → ENV APP_VERSION=${APP_VERSION} so it is present at runtime.
  3. application/identity.rb determine_app_version: prefer an explicit ENV["APP_VERSION"] (the baked value) over the git describe / version_fallback path — so the baked version wins inside the image, and non-Docker runs keep today's git/fallback behavior.

Result: inside a Docker image APP_VERSION is the baked git-derived string → APP_VERSION != version_fallback → AssetCacheControl uses immutable → the full year-long caching win for Docker, with no stale-JS risk (the buster is now genuinely unique per build).

Acceptance / tests

  • Unit: determine_app_version prefers a set ENV["APP_VERSION"]; falls back to git/version_fallback when unset (100% coverage per repo standard).
  • The existing AssetCacheControl git-gate (CA-A1) then resolves to immutable for a baked-version deployment.
  • Consider a smoke check that a built image serves /assets/js/app/main.js?v=<baked> with immutable.

Related

  • PR #868 — the git-gated middleware (this issue restores full immutable for Docker).
  • #870 — app-level versioned-asset caching.
  • The bare-metal nginx path already serves /assets/ immutable off disk on the managed instances (l5y-web-deploy-docs).
  • SPEC.md §"Feature: Asset cache-busting" (AV1–AV4). AV1's documented limitation ("a build with no git metadata yields a constant fallback version") is exactly what this closes for Docker.
## Summary Follow-up to **PR #868** (issue **#870**). #868 added the app-level `AssetCacheControl` middleware, but — per the independent review of #868 — it stamps the full `immutable` (year-long, never-revalidated) `Cache-Control` **only when the running version is unique per build**, i.e. git-derived (`APP_VERSION != Config.version_fallback`). A **Docker image is built without `.git`** (`.dockerignore` excludes it; `web/Dockerfile` copies only `web/lib` etc.), so `determine_app_version` falls back to the constant `Config.version_fallback` (`"0.7.4"`). The `?v=` buster is therefore identical across every commit built between version bumps, so pinning `immutable` there could serve **stale JS for a year, unrecoverable without a hard refresh** on a rolling-`main` deploy. To stay safe, #868 gives constant-fallback deployments a bounded `public, max-age=300` instead of `immutable`. That is correct but means **Docker — the primary deployment mode #870 targeted — does not get the full caching win** (it revalidates every 5 min rather than caching a year). ## Proposed change Bake the real git version into the image so `?v=` is unique per build, making `immutable` universally safe: 1. `.github/workflows/docker.yml`: compute `git describe --tags --long --abbrev=7` in the build job (the CI checkout has `.git`) and pass it as a Docker build arg. 2. `web/Dockerfile`: `ARG APP_VERSION` → `ENV APP_VERSION=${APP_VERSION}` so it is present at runtime. 3. `application/identity.rb` `determine_app_version`: prefer an explicit `ENV["APP_VERSION"]` (the baked value) over the `git describe` / `version_fallback` path — so the baked version wins inside the image, and non-Docker runs keep today's git/fallback behavior. Result: inside a Docker image `APP_VERSION` is the baked git-derived string → `APP_VERSION != version_fallback` → `AssetCacheControl` uses `immutable` → the full year-long caching win for Docker, with no stale-JS risk (the buster is now genuinely unique per build). ## Acceptance / tests - Unit: `determine_app_version` prefers a set `ENV["APP_VERSION"]`; falls back to git/`version_fallback` when unset (100% coverage per repo standard). - The existing `AssetCacheControl` git-gate (CA-A1) then resolves to `immutable` for a baked-version deployment. - Consider a smoke check that a built image serves `/assets/js/app/main.js?v=<baked>` with `immutable`. ## Related - **PR #868** — the git-gated middleware (this issue restores full immutable for Docker). - **#870** — app-level versioned-asset caching. - The bare-metal nginx path already serves `/assets/` immutable off disk on the managed instances (`l5y-web-deploy-docs`). - SPEC.md §"Feature: Asset cache-busting" (AV1–AV4). AV1's documented limitation ("a build with no git metadata yields a constant fallback version") is exactly what this closes for Docker. <!-- github-sync-id:5002429404 -->
adam closed this issue 2026-10-04 23:04:38 +02:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/potato-mesh#222