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-Controlonly 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:
.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.
web/Dockerfile: ARG APP_VERSION → ENV APP_VERSION=${APP_VERSION} so it is present at runtime.
application/identity.rbdetermine_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).
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 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Follow-up to PR #868 (issue #870). #868 added the app-level
AssetCacheControlmiddleware, but — per the independent review of #868 — it stamps the fullimmutable(year-long, never-revalidated)Cache-Controlonly when the running version is unique per build, i.e. git-derived (APP_VERSION != Config.version_fallback). A Docker image is built without.git(.dockerignoreexcludes it;web/Dockerfilecopies onlyweb/libetc.), sodetermine_app_versionfalls back to the constantConfig.version_fallback("0.7.4"). The?v=buster is therefore identical across every commit built between version bumps, so pinningimmutablethere could serve stale JS for a year, unrecoverable without a hard refresh on a rolling-maindeploy.To stay safe, #868 gives constant-fallback deployments a bounded
public, max-age=300instead ofimmutable. 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, makingimmutableuniversally safe:.github/workflows/docker.yml: computegit describe --tags --long --abbrev=7in the build job (the CI checkout has.git) and pass it as a Docker build arg.web/Dockerfile:ARG APP_VERSION→ENV APP_VERSION=${APP_VERSION}so it is present at runtime.application/identity.rbdetermine_app_version: prefer an explicitENV["APP_VERSION"](the baked value) over thegit describe/version_fallbackpath — so the baked version wins inside the image, and non-Docker runs keep today's git/fallback behavior.Result: inside a Docker image
APP_VERSIONis the baked git-derived string →APP_VERSION != version_fallback→AssetCacheControlusesimmutable→ the full year-long caching win for Docker, with no stale-JS risk (the buster is now genuinely unique per build).Acceptance / tests
determine_app_versionprefers a setENV["APP_VERSION"]; falls back to git/version_fallbackwhen unset (100% coverage per repo standard).AssetCacheControlgit-gate (CA-A1) then resolves toimmutablefor a baked-version deployment./assets/js/app/main.js?v=<baked>withimmutable.Related
/assets/immutable off disk on the managed instances (l5y-web-deploy-docs).