Repository navigation
Measure whether the backend image build still needs --no-cache #566
Description
Part of #554. Work on main. Rulings: #554 rulings 1, 4, 5, 7, 15, 16.
Summary
Every deploy builds the backend with docker compose … build --no-cache rest-api hocuspocus-server. The webapp call after it uses the layer cache. The workflow comment says the flag is not needed for correctness, but nobody measured its cost. This issue runs the one controlled A/B test that ruling 4 allows.
- Measured cost. The backend call took 68.5–101.1 s, median 79.4 s, over 14 baseline deploys. It printed
CACHEDon at most 1 step,WORKDIR /app. - Expected (read from code): the cached call saves 50–80 s on an unchanged backend. It saves 10–20 s when backend build inputs change, because only the
apt-getandgroupaddlayers stay cached. - The real-world gain is small. Backend build inputs changed in 11 of 13 gaps between baseline deploys. The A/B test measures only the best case.
| Run | Deploy job | Backend call | CACHED steps |
|---|---|---|---|
| 35723691814 | 106734098333 | 88.3 s | 0 |
| 36324777594 | 108636866187 | 87.3 s | 0 |
| 36437965916 | 108984386483 | 93.8 s | 0 |
| 36473904247 | 109105362036 | 77.2 s | 1 |
| 36550492709 | 109349460176 | 101.1 s | 1 |
| 37431581531 | 112167211789 | 89.0 s | 0 |
| 37440315600 | 112194577684 | 91.3 s | 1 |
| 37976785981 | 113979952915 | 78.8 s | 1 |
| 38068638114 | 114262648443 | 80.0 s | 0 |
| 38239392350 | 114780192678 | 71.5 s | 0 |
| 38392614215 | 115281929767 | 69.8 s | 0 |
| 39275929831 | 118142985559 | 74.2 s | 0 |
| 39297468969 | 118217497479 | 68.5 s | 1 |
| 39398067267 | 118549257854 | 76.4 s | 1 |
Later deploys agree: 39412528288 (job 118596011402) 73.9 s, 39414945889 (job 118603664970) 72.8 s.
The 11 of 13 count runs the Accountability filter on consecutive rows. Only 37440315600 and 38068638114 followed a deploy with no backend input change. Failed runs between rows are ignored. Counting nested .md files gives the same 11 of 13, and all 11 gaps changed non-Markdown inputs.
Depends on / Blocks
- Depends on Write a timing table to every CI/CD run summary #556 for M1, M2i, M3, M4 and M5 of both dispatches.
- Depends on Roll back only after the deploy has started rolling services #555. Without it, every rollback rolls the failed SHA again (Current behaviour item 8).
- Must finish before Try running the backend and webapp image builds at the same time #567 merges. Parallel compose calls add CPU contention to the backend seconds and interleave the log lines.
- Blocks: nothing.
Current behaviour
Links are pinned to d0a53756f8676556358b6adc6c2397d2ed7c64c8.
- The build call (
prod.docs.plus.yml:612-642). The comment at:628-632says "Whether the flag can be dropped is UNMEASURED". It also says "Docker keys a COPY on file content, so the cache cannot serve a stale docker-entrypoint.sh". The call at:633-634isbuild --no-cache rest-api hocuspocus-server. The webapp call at:639-640has no--no-cacheand starts after the backend call ends. - The three backend services share one build (
docker-compose.prod.yml:170-176,:288-294,:397-403). The worker reuses thedocsplus-hocuspocustag. - The backend Dockerfile (
apps/hocuspocus.server/docker/Dockerfile.bun).- It copies
package.json bun.lock* bunfig.tomlat:18, andapps/hocuspocus.server(withscripts/docker-entrypoint.sh) at:28. - The other copies at
:23-47aretsconfig.base.json,tsup.base.ts,packages/email-templates, bothpackages/floating-*, three extension sources, and ten workspacepackage.jsonfiles. - A change to any input misses the cache from that
COPYon, so every later install runs again. The installs are at:50,:56and:93. - The entrypoint enters the image at
:96, byCOPY --from=base. Theapt-getlayers are at:12-14and:71-73.
- It copies
- The base image resolves to a digest on each build. Job 118549257854 logs
load metadata for docker.io/oven/bun:1-slim, thenFROM docker.io/oven/bun:1-slim@sha256:14a223fa…. A new digest misses the cache fromDockerfile.bun:6on. - The dispatch inputs are
skip_quality_gates,force_deployanddry_run_subject(prod.docs.plus.yml:39-55). The header comment at:17names the first two. - No
workflow_dispatchrun exists in the last 400 runs (gh run list --workflow prod.docs.plus.yml --limit 400, oldest 2025-09-20). - What a dispatch runs. Triage reads
github.event.head_commit.message || inputs.dry_run_subject(:111). With an empty subject,app_deployandhas_backarefalse, soback-validationis skipped (:267).FORCE_ALLruns all 6 Ext suites (:157). The deploy job runs only frommainwithforce_deploy(:431-435). - The rollback tag. Every dispatch uses
DEPLOY_TAG: ${{ github.sha }}(:77).PREVIOUS_TAGis read fromLAST_GOOD_TAG_FILE(:561-570), and a success writesDEPLOY_TAGthere (:800-810).- After dispatch 1 succeeds, dispatch 2 has
PREVIOUS_TAGequal toDEPLOY_TAGand re-tags that name. So its rollback (:855-868) rolls the images it just built. - The rollback rewrites
DEPLOY_TAGonly in.env.production(:892). Compose reads the shell value from line 77 first. Roll back only after the deploy has started rolling services #555 addsexport DEPLOY_TAG="${PREVIOUS_TAG}".
- After dispatch 1 succeeds, dispatch 2 has
- The disk guard prunes build cache older than 24 h by last use (
:578-582;AGENTS.md:297). The comment at:578names "--no-cache builds" as the reason. - Free disk is tight. Job 118603664970 shows
📊 Disk after prune: 28 GB free. The hard guard aborts below 10 GB (:589).
Target behaviour
- During the test, a dispatch with
backend_no_cache=falsebuilds the backend with the cache on. Push deploys and default dispatches keep--no-cache. Each build writes the backend flags and seconds to its log and step summary. - After the test, the input, env value and timing line are gone. The call is
build rest-api hocuspocus-server(drop, cachedapt-getlayers accepted) orbuild --no-cache rest-api hocuspocus-server(keep), by the Step 6 rule. Its comment records the run ids and seconds.
Implementation steps
"Comment here" means a comment on this GitHub issue.
Step 0. Check the base
-
Compare the cited files with the pinned commit:
git fetch origin main git diff d0a53756f8676556358b6adc6c2397d2ed7c64c8 origin/main -- \ .github/workflows/prod.docs.plus.yml apps/hocuspocus.server/docker/Dockerfile.bun \ docker-compose.prod.yml .dockerignore .github/WORKFLOWS.md
If a hunk touches a cited line, re-derive it on
main. If the meaning changed, stop and comment here. -
Write a timing table to every CI/CD run summary #556 is on
main: the latest(build):run summary shows the timing table. Else stop and comment here. -
Try running the backend and webapp image builds at the same time #567 is not on
main: the twodocker compose … buildcalls still run in sequence, with no&and nowait. Else stop and comment here. -
If Build the deploy images while the quality gates run #565 has landed,
🏗️ Build Docker Imagesis in theimagesjob. Apply Steps 1–2 there. -
If Prune Docker build cache toward a free-disk target and keep rollback images #564 has landed, its prune can delete the cache dispatch 2 needs. The Step 5.5
installs=CCCcheck catches that. -
No
backend_no_cacheinput exists yet:grep -n "backend_no_cache" .github/workflows/prod.docs.plus.yml || echo "absent"
-
Roll back only after the deploy has started rolling services #555 is on
main. This must print one line, inside🚨 Rollback on Failure. Else stop and comment here.grep -n -F 'export DEPLOY_TAG="${PREVIOUS_TAG}"' .github/workflows/prod.docs.plus.yml
Step 1. Add the temporary input
In .github/workflows/prod.docs.plus.yml, add after dry_run_subject (pinned line 55):
backend_no_cache:
description: 'TEMPORARY (backend cache A/B, remove after): pass --no-cache to the backend image build'
required: false
default: true
type: booleanChange the header comment at pinned line 17 to:
# - workflow_dispatch: manual run with `force_deploy` / `skip_quality_gates`
# (and the temporary `backend_no_cache` A/B input).In .github/WORKFLOWS.md, after line 92, add:
`backend_no_cache` is a temporary input for one A/B test (issue #<this issue>). Push deploys always build the backend with `--no-cache`.Step 2. Wire the input into the backend call
In 🏗️ Build Docker Images, make two edits only.
-
Append these lines to the existing
env:map. Keep every other key, for exampleSKIP_QUALITY_GATESfrom Skip the duplicate type check inside the webapp Docker build #560.# Empty on push, so push deploys keep --no-cache. Remove with the backend_no_cache input. BACKEND_NO_CACHE: ${{ inputs.backend_no_cache }}
-
Replace only the backend call (pinned lines 633–634). Leave the comment above it and the webapp call as they are.
backend_flags=(--no-cache) if [ "${BACKEND_NO_CACHE}" = "false" ]; then backend_flags=(); fi backend_start=$(date +%s) docker compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ build "${backend_flags[@]}" rest-api hocuspocus-server backend_s=$(( $(date +%s) - backend_start )) echo "backend cache A/B: flags='${backend_flags[*]:-cache on}', ${backend_s} s" echo "backend cache A/B: \`${backend_flags[*]:-cache on}\`, ${backend_s} s" >> "$GITHUB_STEP_SUMMARY"
Only the exact value false drops the flag; empty (push) and true keep it. An empty array expands to nothing; the step shell is bash (pinned lines 65–67).
Checks:
actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.yml
bunx prettier --check .github/workflows/prod.docs.plus.yml .github/WORKFLOWS.md AGENTS.md
git diff -- .github/workflows/prod.docs.plus.yml .github/WORKFLOWS.mdIf Prettier reports a file, run bunx prettier --write <that file>. Never run bun run format:fix; it rewrites every file in the tree.
Step 3. Review checkpoint 1
Stop. Show the diff from Steps 1–2. Do not commit. The maintainer commits and pushes per SP2 (#554), its own commit, with a non-deploy subject such as ci(prod): add a temporary backend_no_cache dispatch input. The live commit (P) then differs from the new tip (S), so dispatch 1 rolls back to P. Resume at Step 4 when the push run of S is green.
Step 4. Dry-run the dispatch path
Run SP1 (#554). Then:
gh workflow run prod.docs.plus.yml --repo docs-plus/docs.plus --ref main \
-f force_deploy=false -f backend_no_cache=false
gh run list --repo docs-plus/docs.plus --workflow prod.docs.plus.yml \
--event workflow_dispatch --limit 1 --json databaseId,headSha,status
gh run watch <run id> --repo docs-plus/docs.plus --exit-statusExpected: 🏗️ Build Verification ends success; 🔧 Backend Validation and 🚀 Deploy Production are skipped. If a gate fails, stop and comment here with the run id.
Step 5. Run the A/B test
Run SP1 (#554) before every dispatch in this issue.
5.1 Preconditions. Check each, and write each into the Measured comment.
-
Pick a UTC hour with low live editing. Tell the team that nobody pushes to
mainfrom the start of dispatch 1 until the end of dispatch 2. A push cancels every job of an older run that has not started yet ([Tracking] Faster, change-aware CI/CD pipeline #554, SP1). -
No dispatch on Sunday 00:00–05:00 UTC, the weekly
schedulewindow (pinned line 38; 02:14 UTC on 2026-10-18, run 39089384453). -
Record P, the live commit (read-only):
for id in $(gh run list --repo docs-plus/docs.plus --workflow prod.docs.plus.yml --limit 30 --json databaseId -q '.[].databaseId'); do gh run view "$id" --repo docs-plus/docs.plus --json databaseId,headSha,jobs \ -q 'select(any(.jobs[]; .name == "🚀 Deploy Production" and .conclusion == "success")) | "\(.databaseId) \(.headSha)"' done | head -1
-
Record S:
git rev-parse origin/main. S is the Step 3 commit, or later with no(build):deploy since. P differs from S. -
A dispatch skips
back-validation, so S adds only.github/and Markdown on top of P. If this lists a file, stop and comment here:git diff --name-only <P> <S> | grep -v -E '^\.github/|\.md$' || echo "only .github and Markdown"
-
Read
📊 Disk after prunein the latest deploy log. Without Prune Docker build cache toward a free-disk target and keep rollback images #564, stop and comment here below 25 GB. With Prune Docker build cache toward a free-disk target and keep rollback images #564, its guard aborts below 20 GB and prunes least recently used cache first.
5.2 Dispatch 1, flag on.
gh workflow run prod.docs.plus.yml --repo docs-plus/docs.plus --ref main \
-f force_deploy=true -f backend_no_cache=trueIn both dispatches keep skip_quality_gates=false and dry_run_subject empty. The #560 webapp build-arg reads skip_quality_gates, so a change would move M3.
Healthy when:
headShais S;🔐 Prepare Environmentprintsℹ️ Previous good tag: <P>;- the build log prints
backend cache A/B: flags='--no-cache'; 🩺 Verify Deploymentand the deploy job endsuccess;- a pad on https://docs.plus loads and syncs an edit in two tabs.
If dispatch 1 fails, it rolls back to P. Comment here with the run id, and go to Step 6 with "keep".
5.3 Dispatch 2, cache on. Start it soon after a healthy dispatch 1, in the same hour. Then the prune keeps dispatch 1's layers. Dispatch 1's 📊 Disk after prune minus about 14 GB (one build) estimates dispatch 2's start. Without #564, do not start below 10 GB; comment here.
gh workflow run prod.docs.plus.yml --repo docs-plus/docs.plus --ref main \
-f force_deploy=true -f backend_no_cache=falseHealthy when headSha is S, the build log prints backend cache A/B: flags='cache on', and the last two dispatch 1 checks pass.
5.4a Dispatch 2 fails before 🔧 Ensure Infrastructure. Production still runs dispatch 1's images. Run no recovery. Comment here with the run id and failed step. A disk guard abort makes the run invalid (Step 5.5 extra dispatch). A failed backend build means "keep".
5.4 Dispatch 2 fails after the roll starts. Its rollback rolls the same cached images. Wait until dispatch 2 has fully completed. git rev-parse origin/main (after git fetch origin main) must print S; else comment here and do not dispatch. Then recover:
gh workflow run prod.docs.plus.yml --repo docs-plus/docs.plus --ref main \
-f force_deploy=true -f backend_no_cache=trueComment here with all three run ids. The decision is "keep".
5.5 Measure. Run the Verification script on both run ids, and record per Accountability. Dispatch 2 must print installs=CCC. If not, the cache did not survive. Run one extra backend_no_cache=false dispatch under the Step 5 rules; only one is allowed.
Step 6. Remove the input and apply the result
Drop the flag only when all three hold; otherwise keep it:
- both dispatches end
🩺 Verify Deploymentsuccess; - dispatch 2 prints
installs=CCC; - dispatch 2's backend call is at least 30 s shorter than dispatch 1's.
Then make one change:
-
Delete the Step 1 input and
.github/WORKFLOWS.mdsentence, and restore the pinned line 17 comment. -
Delete the
BACKEND_NO_CACHEenv line,backend_flags,backend_start,backend_sand bothbackend cache A/Becho lines. -
Replace pinned lines 628–632 and the call with one form. Fill every
<…>. Each comment stays at 4 lines or fewer.Drop:
# No --no-cache: A/B on <date>, runs <run 1> (flag) and <run 2> (cache), one commit. # The cached call took <n> s against <m> s (#<this issue>). A COPY is keyed on # file content, so the cache cannot serve a stale docker-entrypoint.sh. docker compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ build rest-api hocuspocus-server
On a drop, if the disk guard still says 'Without this, --no-cache builds can fill', delete '--no-cache' there. If Prune Docker build cache toward a free-disk target and keep rollback images #564 replaced the guard, leave it.
Keep:
# --no-cache stays: A/B on <date>, runs <run 1> and <run 2>, one commit (#<this issue>). # The cached call saved only <n> s, below the bar ruled there. docker compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ build --no-cache rest-api hocuspocus-server
-
In
AGENTS.md§Docker, add after pinned line 301 (the cache-ordering bullet):- **The backend image build was measured with and without `--no-cache` on <date> (#<this issue>).** Runs `<run 1>` and `<run 2>` built one commit. The backend call took <m> s with the flag and <n> s without it. The flag was <dropped|kept>, because <reason from the ruling>.
-
On a drop only, in the
AGENTS.md§Docker cache-mount bullet, replace the sentence that starts "A mount also survives a changingARG" with the text below. Keep the sentence Prune Docker build cache toward a free-disk target and keep rollback images #564 appended after it.A mount also survives a changing `ARG` with the layer cache on. Since #<this issue> the backend builds with the cache on too, so a mount there is no longer ruled out by `--no-cache`.
Checks:
grep -n "backend_no_cache\|BACKEND_NO_CACHE\|backend cache A/B" .github/workflows/prod.docs.plus.yml .github/WORKFLOWS.md || echo "clean"
actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.yml
bunx prettier --check .github/workflows/prod.docs.plus.yml .github/WORKFLOWS.md AGENTS.md
bun run check:agent-docsReview checkpoint
Stop. Show git diff for .github/workflows/prod.docs.plus.yml, .github/WORKFLOWS.md and AGENTS.md, with a short summary. Do not commit. The maintainer commits and pushes per SP2 (#554). It needs no deploy; the next (build): push uses the new call.
Acceptance criteria
- Step 0 found no changed cited line, or each was re-derived and noted here.
-
actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.ymlprints nothing after Step 2 and Step 6. - Every push deploy between Step 3 and Step 6 prints
backend cache A/B: flags='--no-cache'. - The Step 4 dispatch ended
🏗️ Build Verificationsuccesswith🚀 Deploy Productionskipped. - Both dispatches have the same
headSha; dispatch 1'sPREVIOUS_TAGis P, not S. - Both dispatches were healthy by Step 5, or a recorded recovery dispatch ran.
- A "Measured, " comment holds the Accountability record for both dispatches.
- Step 6 applies its rule to the measured values.
- After Step 6,
grep -n "backend_no_cache\|BACKEND_NO_CACHE\|backend cache A/B"finds nothing in.github/. - The backend call comment names both run ids, in 4 lines or fewer.
-
AGENTS.md§Docker has the new bullet;bun run check:agent-docspasses. - After a drop: a second "Measured, " comment covers the next two
(build):deploys.
Verification
Run actionlint and bunx prettier --check as in Step 2, after Step 2 and Step 6. Triage and gating do not change, so bash .github/scripts/parse-build-trigger.test.sh and a dry_run_subject dispatch are not needed.
The backend call script (read-only; keep it outside the repo). Run bun x1-backend-call.ts <run id> <run id> ….
// x1-backend-call.ts: one row per run for the backend `docker compose … build` call.
const repo = "docs-plus/docs.plus";
const gh = (...a: string[]) => Bun.spawnSync(["gh", ...a, "--repo", repo]).stdout.toString();
for (const run of Bun.argv.slice(2)) {
const meta = JSON.parse(gh("run", "view", run, "--json", "jobs,headSha"));
const job = meta.jobs.find((j: any) => (j.steps ?? []).some((s: any) => s.name === "🏗️ Build Docker Images"));
const lines = gh("run", "view", "--job", String(job.databaseId), "--log").split("\n");
const ts = (l: string) => Date.parse(l.split("\t")[2].replace(/^/, "").slice(0, 28));
const at = (re: RegExp) => lines.findIndex((l) => re.test(l));
const start = at(/ Image docsplus-hocuspocus:[0-9a-f]+ Building/);
const built = at(/ Image docsplus-(hocuspocus|rest-api):[0-9a-f]+ Built/);
const web = at(/ Image docsplus-webapp:[0-9a-f]+ Building/);
const win = lines.slice(start, web === -1 ? undefined : web);
const label = new Map<string, string>();
for (const l of win) { const m = l.match(/ #(\d+) \[([^\]]+)\] (.*)$/); if (m) label.set(m[1], `${m[2]}] ${m[3]}`); }
const cached = new Set(win.flatMap((l) => l.match(/ #(\d+) CACHED\s*$/)?.[1] ?? []));
const installs = [...label].filter(([, v]) => /RUN bun install/.test(v)).map(([id]) => (cached.has(id) ? "C" : "-")).join("");
console.log([run, job.databaseId, meta.headSha.slice(0, 9), ((ts(lines[built]) - ts(lines[start])) / 1000).toFixed(1) + " s",
`cached=${cached.size}/${label.size}`, `installs=${installs}`].join("\t"));
}On run 39398067267 it prints 118549257854 3784c2c65 76.4 s cached=1/46 installs=---. Its window runs from the backend Building line to the webapp Building line, so it needs the two calls in sequence.
Judge on the backend call only. #569 defines M1–M5, M2g and W; record them from the #556 summary. Push baselines: M3 median 546 s, M4 0–72 s, M1 median 1090 s.
- M1, M2g, M2i and W are not comparable with push runs: a dispatch runs all 6 Ext suites and skips
back-validation. - M3 differs between the dispatches.
GIT_HASHcomes fromDEPLOY_TAG(docker-compose.prod.yml:517,:602). Dispatch 1 changes it from P to S and rerunsnext build; dispatch 2 can reuse that layer.
Accountability
The builder records per SP3 (#554) within 24 h of dispatch 2, one line per dispatch (recovery or extra included): run, job, headSha, backend_no_cache, UTC start, backend call, cached=, installs=, M3, M1, Disk after prune and Verify.
| Metric | Run to read | Baseline | Expected, dispatch 1 (flag) | Expected, dispatch 2 (cache) |
|---|---|---|---|---|
| Backend call | script, both dispatches | 68.5–101.1 s, median 79.4 s (14 deploys) | inside the baseline range | about 10–30 s (50–80 s saved) |
CACHED steps, backend call |
script, both dispatches | 0–1, installs=--- |
0–1, installs=--- |
nearly all labelled steps, installs=CCC |
| M5 Roll-out | #556 summary, both dispatches | 57–70 s | 57–70 s | 57–70 s |
-
After a drop, run the script and the filter below on the next two
(build):deploys. Expect about 10–30 s with no backend input change, and 60–90 s with one. Post a second "Measured, " comment: run ids, backend seconds,installs=, and whether backend inputs changed. -
Stop condition, revert the drop: a deploy changed a backend build input, and that input's
COPYstep printedCACHED. Revert at once (Rollback), and comment here with the run id.-
A backend build input is a file under a Current behaviour item 3 path that the root
.dockerignoredoes not exclude. -
Patterns without
**/match only the context root (.dockerignore:91-93). So*.mdand!README.md(:42-43) affect root files only; nested.mdfiles such asapps/hocuspocus.server/CHANGELOG.mdare inputs. -
**/docs,**/__tests__,**/__mocks__,**/*.test.tsand**/*.spec.ts(:45,:57-60) exclude nested files. A change to only those correctly printsCACHED. -
The filter:
git diff --name-only <previous deploy headSha> <deploy headSha> -- package.json bun.lock bunfig.toml \ tsconfig.base.json tsup.base.ts packages/email-templates packages/floating-popover packages/floating-tooltip \ apps/hocuspocus.server extensions/extension-hyperlink extensions/extension-hypermultimedia \ extensions/extension-inline-code apps/admin-dashboard/package.json apps/webapp/package.json \ packages/eslint-config/package.json packages/release-tooling/package.json packages/playground/package.json \ packages/document-swarm/package.json packages/supabase/package.json extensions/extension-indent/package.json \ extensions/extension-mermaid/package.json extensions/extension-placeholder/package.json \ | grep -v -E '(^|/)(__tests__|__mocks__|docs)/|\.(test|spec)\.ts$'
-
-
Investigate first: after a drop, a
(build):deploy fails🩺 Verify Deploymentonrest-api,hocuspocus-serverorhocuspocus-worker. Read the backend call log. Revert only when the stop condition holds.
Rollback
- The temporary input. Revert per SP4 ([Tracking] Faster, change-aware CI/CD pipeline #554). Push deploys never read it, so no deploy or host step is needed.
- A failed dispatch. Follow Steps 5.2, 5.4 and 5.4a.
- The drop. Revert per SP4 ([Tracking] Faster, change-aware CI/CD pipeline #554) when the stop condition fires. Deploy the revert with the subject
(build): back front, which runs every gate. Use aforce_deploydispatch instead only whengit diff --name-only <live headSha> origin/main | grep -v -E '^\.github/|\.md$'prints nothing. No host step:--no-cacheignores every cached layer.
Docs to update
.github/workflows/prod.docs.plus.yml: header comment at pinned line 17 (Steps 1 and 6); comment at pinned lines 628–632 (Step 6); on a drop, the disk guard comment if it still names --no-cache builds..github/WORKFLOWS.md:92, which describesforce_deployandskip_quality_gates: one sentence, added in Step 1, removed in Step 6.AGENTS.md:286-307(§Docker): one bullet after pinned line 301, and line 297 on a drop (Step 6 items 4–5).- No CHANGELOG entry: a CI change with no product effect.
Out of scope
- Running both compose calls at once (Try running the backend and webapp image builds at the same time #567). Never
--parallel; it is not a Compose v2 flag (AGENTS.md §Docker). no_cache: trueindocker-compose.prod.yml; the stage workflow andMakefileread it too.- Any
Dockerfile.bunchange: no cache mount, layer reorder,chown -R, ornode_modulescopy between stages. - A build-once-then-
docker tagstep (AGENTS.md §Docker rejects it). - Laptop timings, and judging a cache-order fix under
--no-cache. - Skipping backend images on
(build): front(ruling 7); extra deploys for other questions (ruling 4). --pull, a pinned base tag, or a weekly fresh build. The base staysoven/bun:1-slim.- Disk guard values or the image prune (Prune Docker build cache toward a free-disk target and keep rollback images #564).
- Hosted runners or a registry; not part of v2.3.0.
- The
docker compose … build webapp admin-dashboardcall.
Read first
AGENTS.md§Docker (pinned lines 286–307), §Git And Commits and §Test Policy.apps/hocuspocus.server/CLAUDE.md§Production And Docker Compose..github/WORKFLOWS.md..github/workflows/prod.docs.plus.yml: thedeployjob (pinned lines 420–965), and thetriageandchangesjobs.apps/hocuspocus.server/docker/Dockerfile.bun, in full.docker-compose.prod.ymlpinned lines 160–176, 288–294 and 397–403..dockerignore. It excludes.github, so a workflow-only commit leaves the backend context unchanged.- Write a timing table to every CI/CD run summary #556 for the summary table; [Tracking] Faster, change-aware CI/CD pipeline #554 for the issue order and SP1–SP4.
Activity