Repository navigation
Make the webapp Docker installs fast and cacheable #559
Description
Part of #554. Change set B. Work on main. Rulings: #554 rulings 1, 4, 5, 6, 7, 15 and 16.
Summary
- Today. The two webapp
bun install --frozen-lockfilesteps run lifecycle scripts. They took 144–182 s per deploy, median 158 s, and never printedCACHEDon the 6 deploys below. Admin and backend install the same 1751 packages with--ignore-scriptsin 6–18 s, in the same jobs. - Part 1. Add
--ignore-scriptsto both webapp installs (Dockerfile.bun:107and:191). Expected saving: about 120 s of🏗️ Build Docker Imagesper deploy. - Part 2. In
build-extensions, copy the six extension manifests before the install and the sources after it. The saving is small. bun.lockand the rootpackage.jsonstay byte-identical.
Evidence: DONE seconds of the two webapp installs, from each deploy job's BuildKit log.
| Run | Job | build-extensions install |
builder install |
Sum |
|---|---|---|---|---|
| 39398067267 | 118549257854 | 96.9 | 61.1 | 158.0 |
| 39297468969 | 118217497479 | 102.5 | 75.4 | 177.9 |
| 39275929831 | 118142985559 | 86.1 | 58.2 | 144.3 |
| 38392614215 | 115281929767 | 91.3 | 56.4 | 147.7 |
| 38239392350 | 114780192678 | 94.4 | 63.5 | 157.9 |
| 38068638114 | 114262648443 | 93.4 | 88.8 | 182.2 |
In job 118549257854, Bun reports 6.45 s (backend base), 9.59 s (admin deps) and 11.05 s (admin builder) with --ignore-scripts. The two webapp installs report 96.32 s and 60.46 s.
Depends on / Blocks
- Depends on: Roll back only after the deploy has started rolling services #555. Land this after it, as its own commit in set B.
- Blocks: nothing.
- Same files, same set: Skip the duplicate type check inside the webapp Docker build #560 edits the
builderstage ofapps/webapp/docker/Dockerfile.bunbelow line 198. Measure and trim the Next.js build-trace step in the webapp build #568 replaces its line 219. Make CI failures reach Telegram, and fix stale CI facts #557 edits its lines 214–218 and AGENTS.md lines 290 and 297. Measure whether the backend image build still needs --no-cache #566 adds an AGENTS.md bullet after line 301. Any of them may land first. Step 0 re-reads the lines. - Other AGENTS.md edits: Prune Docker build cache toward a free-disk target and keep rollback images #564 (line 297), Try running the backend and webapp image builds at the same time #567 (after line 300), Measure and trim the Next.js build-trace step in the webapp build #568 (a §Docker bullet), Run only the CI suites a pull request or push can affect #563 and Stop extension README media from triggering extension test suites #562 (line 222). Step 6 finds its place by text, so these do not block it.
Current behaviour
Links are pinned to d0a53756f8676556358b6adc6c2397d2ed7c64c8.
-
The
build-extensionsinstall copies extension sources first (Dockerfile.bun:91-107):# Overwrite extension source with context (in case deps had stale copy) COPY extensions/extension-hyperlink ./extensions/extension-hyperlink ... COPY extensions/extension-mermaid ./extensions/extension-mermaid ENV CYPRESS_INSTALL_BINARY=0 RUN bun install --frozen-lockfile
So any extension source edit re-runs the install. The extension builds follow at lines 109-118.
-
The
builderinstall has scripts on too (Dockerfile.bun:190-191). Its cache key includes the built extensions from line 182, so it re-runs on every extension edit. This issue does not change that. -
The pattern already exists.
Dockerfile.bun:193-198copiesapps/webappbelow its install: "COPY merges into the existing directory, so apps/webapp/node_modules survives." It also says "Never add --link here". -
.dockerignoredrops everynode_modulesanddist(.dockerignore:6-8,:62-66). So a sourceCOPYafter the install cannot overwrite theextensions/*/node_modulesit wrote. -
Admin and backend already use the flag. Admin:
Dockerfile.bun:41and:77, thennext buildwith@sentry/nextjs. Backend:Dockerfile.bun:49-56, then five tsup (esbuild) builds. -
The two root scripts already fail in the webapp image. Root
package.json:58-59has"prepare": "husky"and"postinstall": "bun --filter @docs.plus/hocuspocus prisma:generate || true". Job 118549257854 prints, in both webapp installs:- "Could not load
--schemafrom provided pathprisma/schema.prisma: file or directory not found"; - ".git can't be found" (husky).
They take about 2 s. The rest of each install is silent for 94.66 s (
build-extensions) and 58.72 s (builder). So dependency scripts hold the time. - "Could not load
-
What each script does. Every package in the tree with an install-time script. It does not claim Bun runs each one today. Versions are from
bun.lock. "Log" means seen in job 118549257854; "code" means read from the package source.Package ( bun.lock)Script What it does Webapp image needs it? root package.jsonpostinstall,prepareprisma:generateand huskyNo. Both fail today (log). sharp@0.35.5none Binary from optional @img/sharp-linux-x64@0.35.5(bun.lock:3613,:1151) (code).Unaffected. @sentry/cli@2.58.6postinstallscripts/install.jsresolves@sentry/cli-linux-x64and downloads only when that fails (code).bun.lockpins@sentry/cli-linux-x64@2.58.6(bun.lock:1451).No. Step 4 proves it. esbuild@0.27.7,esbuild@0.28.2postinstallinstall.jschecks the binary and may swap the JS shim for it (code).No. @esbuild/linux-*serves the JS API. Lines 110-118 fail loudly if it breaks.re2@1.24.1installDownloads or compiles the addon with node-gyp(code).No. Nothing in apps/webapp,extensionsorpackages/floating-*imports it (Step 0.4). The backend image already ships without it.@prisma/engines,@prisma/client,prisma(6.19.3)postinstall,preinstallDownload engines, generate a client (code). No. The webapp does not use Prisma. cypress@15.21.1postinstallDownloads the binary. CYPRESS_INSTALL_BINARY=0skips it.No. @parcel/watcher@2.6.0installBuilds only when npm_config_build_from_source=true(code).No. msgpackr-extract@3.0.4installLoads or compiles a prebuilt addon (code). No. The webapp does not import it. unrs-resolver@1.12.2postinstallnapi-postinstallchecks the binding (code).No. Jest and ESLint only. @turbodocx/html-to-docx@1.23.1postinstallPrints a message (code). Backend dependency. No. The log has no per-script output, so this issue does not say which script costs most.
-
Two failures would escape every deploy gate. Only
/_next/imageuses sharp at runtime, and the health check calls/api/healthonly. The GlitchTip sourcemap upload runs withsilent: true(next.config.js:435-449). A broken esbuild,next buildor next-pwasw.jsstep fails🏗️ Build Docker Images. -
A failed image build is not harmless at the pinned commit.
🚨 Rollback on Failureis a bareif: failure()(prod.docs.plus.yml:855-857). It runsdeploy_serviceon all five services, and Compose uses the failed SHA, notPREVIOUS_TAG(Roll back only after the deploy has started rolling services #555, Current behaviour item 8). Roll back only after the deploy has started rolling services #555 lands first and fixes both. Step 5 still builds the full image locally. -
Every
(build):deploy builds the webapp image (prod.docs.plus.yml:612-640).
Target behaviour
- Both webapp installs run
bun install --frozen-lockfile --ignore-scripts. - On the prod host, each webapp install ends in 20 s or less, or prints
CACHED. - The
build-extensionsinstall's inputs are: rootpackage.jsonandbun.lock,tsconfig.base.jsonandtsup.base.ts,packages/andapps/fromdeps, and the six extension manifests. An extension-source-only edit keeps itCACHED. - By design, a
bun.lockchange, any manifest change, or a change underpackages/floating-*,packages/email-templatesorpackages/eslint-configstill re-runs it (line 94). - The line 191 install still re-runs on every extension edit (line 182).
- The shipped image works as today:
/_next/imagereturns200 image/webp,/sw.jsreturns200, sourcemaps reach GlitchTip, and a pad loads.
Implementation steps
"Comment here" means a comment on this issue. The local Docker steps run on any machine. Judge no timing on a laptop (AGENTS.md §Docker).
Step 0. Read and check the base
-
Run
git diff --stat d0a53756f8676556358b6adc6c2397d2ed7c64c8 HEAD -- apps/webapp/docker/Dockerfile.bun package.json bun.lock .dockerignore AGENTS.md. To name the commits, rungit log --oneline d0a53756f8676556358b6adc6c2397d2ed7c64c8..HEAD -- apps/webapp/docker/Dockerfile.bun AGENTS.md.- Empty: go on.
- Only Skip the duplicate type check inside the webapp Docker build #560, Measure and trim the Next.js build-trace step in the webapp build #568, Make CI failures reach Telegram, and fix stale CI facts #557 or Measure whether the backend image build still needs --no-cache #566 touched
Dockerfile.bunorAGENTS.md: use the new line numbers. - Only the issues under Other AGENTS.md edits touched
AGENTS.md: go on. - Commits that change only dependency versions, the release version or doc text are fine. Today that is chore(deps): bump the dev-dependencies group across 1 directory with 5 updates #536, chore(deps): bump tailwind-merge from 3.6.0 to 3.7.0 #538,
8f5b13f6e,4047046e8,1dd664802,cb7d74aab,9fd84c0bd3d1b5f700and the 2026-10-24 sweep7c3d7d26c. Re-run Step 0.3 after them. - Anything else: stop and comment here.
-
Run
sed -n '87,118p;175,198p' apps/webapp/docker/Dockerfile.bun. Confirm it matches items 1–3 of Current behaviour. If not, stop and comment here. -
Run
grep -oE '"(sharp|@sentry/cli|@sentry/cli-linux-x64|esbuild|re2)@[0-9][^"]*"' bun.lock | sort -u. Expectsharp@0.35.5,@sentry/cli@2.58.6,@sentry/cli-linux-x64@2.58.6,esbuild@0.27.7,esbuild@0.28.2andre2@1.24.1. Commit7c3d7d26c(Sentry 11.9.0) removes@sentry/cliand@sentry/cli-linux-x64frombun.lock. Sentry 11 addssentry@0.45.0andoxc-parser@0.152.0, and neither has an install script. Without@sentry/cli, skip the threesentry-clilines of the Step 4 probe and their pass rule. If any other version differs, re-check that row in item 7 and comment here. -
Run this. Expect no output. If it prints a match, stop and comment here: that import may need its install script.
git grep -n -E "['\"](re2|url-regex-safe|metascraper[^'\"]*|@prisma/client|msgpackr[^'\"]*)['\"]" -- apps/webapp extensions packages/floating-popover packages/floating-tooltip -
Record the production baseline for the hand checks (read on 2026-10-20):
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' -H 'Accept: image/webp,*/*' 'https://docs.plus/_next/image?url=%2Ficons%2Ficon-96x96.png&w=64&q=75' # 200 image/webp curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' 'https://docs.plus/sw.js' # 200 application/javascript; charset=utf-8
Step 1. Parts 1 and 2 in build-extensions
In apps/webapp/docker/Dockerfile.bun, replace lines 98–107 (from # Overwrite extension source with context to RUN bun install --frozen-lockfile) with this block. Keep lines 91–96 and 109–118 unchanged.
# Manifests only, so an extension source edit leaves the install below CACHED.
COPY extensions/extension-hyperlink/package.json ./extensions/extension-hyperlink/
COPY extensions/extension-hypermultimedia/package.json ./extensions/extension-hypermultimedia/
COPY extensions/extension-indent/package.json ./extensions/extension-indent/
COPY extensions/extension-inline-code/package.json ./extensions/extension-inline-code/
COPY extensions/extension-placeholder/package.json ./extensions/extension-placeholder/
COPY extensions/extension-mermaid/package.json ./extensions/extension-mermaid/
ENV CYPRESS_INSTALL_BINARY=0
# No build step here needs a dependency install script, and the scripts are
# most of this install's time. Native binaries come from optional packages.
# AGENTS.md §Docker records the check.
RUN bun install --frozen-lockfile --ignore-scripts
# Sources after the install. COPY merges into each directory, so the
# extensions/*/node_modules the install wrote survive. Never add --link here.
COPY extensions/extension-hyperlink ./extensions/extension-hyperlink
COPY extensions/extension-hypermultimedia ./extensions/extension-hypermultimedia
COPY extensions/extension-indent ./extensions/extension-indent
COPY extensions/extension-inline-code ./extensions/extension-inline-code
COPY extensions/extension-placeholder ./extensions/extension-placeholder
COPY extensions/extension-mermaid ./extensions/extension-mermaid-
Keep
ENV CYPRESS_INSTALL_BINARY=0. It guards a later revert of the flag. -
Do not touch the
depsstage (lines 53–58 and 69–74). -
Check:
sed -n '87,130p' apps/webapp/docker/Dockerfile.bun grep -c 'COPY extensions/extension-[a-z-]*/package.json ./extensions/' apps/webapp/docker/Dockerfile.bun # expect 12 (6 in deps, 6 here)
Step 2. Part 1 in builder
Replace ENV CYPRESS_INSTALL_BINARY=0 and RUN bun install --frozen-lockfile (lines 190–191) with:
ENV CYPRESS_INSTALL_BINARY=0
# Same flag and reason as the build-extensions install.
RUN bun install --frozen-lockfile --ignore-scriptsCheck:
grep -n 'RUN bun install' apps/webapp/docker/Dockerfile.bun
# expect exactly two lines, both ending in: --frozen-lockfile --ignore-scripts
git diff --exit-code -- bun.lock package.json .dockerignoreStep 3. Prove Part 2 with the cache-hit check
Build the build-extensions target twice with the cache on, changing one extension input between builds. Do the same with the base Dockerfile as the control. Never use --no-cache here. Run from the repo root; the target needs no build args.
T="$(mktemp -d)"; echo "$T"
git show d0a53756f8676556358b6adc6c2397d2ed7c64c8:apps/webapp/docker/Dockerfile.bun > "$T/Dockerfile.before"
cat > "$T/installs.awk" <<'EOF'
# Prints each webapp install step, and the extension-indent source COPY, with its DONE or CACHED line.
match($0, /#[0-9]+ \[(webapp )?(build-extensions|builder) +[0-9]+\/[0-9]+\] (RUN bun install|COPY extensions\/extension-indent )[^\r]*/) {
s = substr($0, RSTART, RLENGTH); split(s, a, " "); if (!(a[1] in id)) id[a[1]] = s; next
}
{
for (k in id) if ($0 ~ ("(^|[ \t])" k " (DONE|CACHED)")) {
r = $0; sub(/.*#[0-9]+ /, "", r); print id[k] " => " r; delete id[k]
}
}
EOF
ext() { docker build -f "$1" --target build-extensions --progress=plain -t "$2" . > "$3" 2>&1; echo "exit $?"; }
ext apps/webapp/docker/Dockerfile.bun webapp-ext:after "$T/after-1.log"
echo "probe $T $(date +%s)" > extensions/extension-indent/cache-probe.txt
ext apps/webapp/docker/Dockerfile.bun webapp-ext:after "$T/after-2.log"
rm extensions/extension-indent/cache-probe.txt
ext "$T/Dockerfile.before" webapp-ext:before "$T/before-1.log"
echo "probe $T $(date +%s)" > extensions/extension-indent/cache-probe.txt
ext "$T/Dockerfile.before" webapp-ext:before "$T/before-2.log"
rm extensions/extension-indent/cache-probe.txt
git status --porcelain extensions/ # expect no output
awk -f "$T/installs.awk" "$T/after-2.log"
awk -f "$T/installs.awk" "$T/before-2.log"Pass rule:
- Every
extcall printsexit 0. - In
after-2.log,RUN bun install --frozen-lockfile --ignore-scriptsprintsCACHED, andCOPY extensions/extension-indent ./extensions/extension-indentprintsDONE. - In
before-2.log,RUN bun install --frozen-lockfileprintsDONE(the control). - If the
extension-indentsourceCOPYprintsCACHEDin either log, the probe did not change. Run Step 3 again fromT=and do not judge. - If
after-2.logshows the install asDONE, stop and comment here with both awk outputs.
BuildKit pads a step number below 10 with a second space ([build-extensions 8/20]). The + in the awk pattern allows for it.
Step 4. Check the skipped scripts
probe() { docker run --rm --entrypoint sh "$1" -c '
cd /app/apps/webapp && bun -e "require(\"sharp\"); console.log(\"sharp loads\")"
c=$(dirname "$(find /app/node_modules -path "*/node_modules/@sentry/cli/package.json" | head -1)")
f=$(cd "$c" && node -e "console.log(require(\"./js/helper\").getPath())")
echo "sentry-cli: $f"; "$f" --version
echo "re2.node files: $(find /app/node_modules -name re2.node | wc -l)"'; }
probe webapp-ext:before
probe webapp-ext:after
grep -c 'prisma:generate' "$T/after-1.log" # expect 0
grep -c 'prisma:generate' "$T/before-2.log" # expect 1 or more; the Step 3 control proves this install ranPass rule for webapp-ext:after:
- It prints
sharp loads. sentry-cli --versionprintssentry-cli 2.58.6. Thesentry-cli:path points into@sentry/cli-linux-*, not to@sentry/cli/sentry-cli.re2.node filesis0. Record thewebapp-ext:beforevalue next to it.after-1.loghas noprisma:generateline. This counts only whenawk -f "$T/installs.awk" "$T/after-1.log"shows the install asDONE. IfCACHED, record "not evidence".
The runtime starts with bun (Dockerfile.bun:286), so the sharp check uses bun. On arm64 the optional packages are linux-arm64; the mechanism is the same.
Step 5. Build the full image and compare it with the base
The build args are the Build Verification placeholders for build:ci (prod.docs.plus.yml:390-396). next.config.js fails only when NEXT_PUBLIC_RESTAPI_URL is empty (next.config.js:221-226).
full() { docker build -f "$1" --target runner --progress=plain \
--build-arg NEXT_PUBLIC_RESTAPI_URL=http://localhost:4000 \
--build-arg NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 \
--build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=dummy-key \
-t "$2" . > "$3" 2>&1; echo "exit $?"; }
full apps/webapp/docker/Dockerfile.bun webapp:after "$T/runner-after.log"
full "$T/Dockerfile.before" webapp:before "$T/runner-before.log"
smoke() { docker run -d -P --name "smoke-$1" "webapp:$1" > /dev/null
H="http://$(docker port "smoke-$1" 3000 | head -1)"
for i in $(seq 1 30); do curl -fsS "$H/api/health" > /dev/null 2>&1 && break; sleep 2; done
echo "== $1"
curl -sS -o /dev/null -w 'health %{http_code}\n' "$H/api/health"
curl -sS -o /dev/null -w 'image %{http_code} %{content_type}\n' -H 'Accept: image/webp,*/*' "$H/_next/image?url=%2Ficons%2Ficon-96x96.png&w=64&q=75"
curl -sS -o /dev/null -w 'sw %{http_code} %{content_type}\n' "$H/sw.js"
docker rm -f "smoke-$1" > /dev/null; }
smoke before
smoke afterPass rule:
- Both
fullcalls printexit 0. - If
beforefails, this machine cannot build the image. Stop and comment here withtail -40 "$T/runner-before.log". - If only
afterfails, this change broke the build. Stop and comment here withtail -40 "$T/runner-after.log". - The
afterlines equal thebeforelines. Expected:health 200,image 200 image/webp,sw 200 application/javascript; charset=utf-8. - If
beforefails a line, record it and rely on the production hand check for it. - If
afterdiffers frombefore, stop and comment here with both outputs.
This step cannot check the GlitchTip upload (no token locally). Hand check 3 covers it.
Step 6. Record the rule in AGENTS.md
In AGENTS.md §Docker, add these two bullets right after the bullet that starts "A cache-ordering fix cannot be measured with --no-cache." (AGENTS.md:301). Find it by text. If a sibling bullet already follows it, add yours below that one. Replace <date> with the day you run Step 4.
- **Every `bun install` in the webapp, admin and backend Dockerfiles uses `--ignore-scripts`.** No image build step needs a dependency install script. sharp 0.35 has none, and esbuild gets its binary from an optional platform package. Without the flag, the webapp installs took 144–182 s per deploy, against 6–18 s for the same 1751 packages in admin and backend. The root `postinstall` and `prepare` scripts failed in the webapp image anyway. Checked on <date>. After a major bump of sharp or esbuild, check that the binary still comes from an optional package.
- **The webapp `build-extensions` stage copies the extension `package.json` files before its install, and the sources after it.** An extension-only edit then keeps that install `CACHED`. The `builder` install still re-runs, because it copies the built extensions.Do not edit the "both webapp installs should print CACHED" sentence at AGENTS.md:290. #557 owns it.
bun run check:agent-docs
bun run formatStep 7. Post the local proof
Comment here with the heading "Local proof, ". Include:
- the two Step 3 awk outputs;
- the two Step 4 probe outputs and
grep -ccounts; - the Step 5
beforeandaftersmoke outputs; - the machine and its CPU architecture. Give no timings.
Review checkpoint
Show git diff --stat and git diff -- apps/webapp/docker/Dockerfile.bun AGENTS.md, then stop. Expect exactly these two files. Do not run git add, git commit, git push or git stash. The maintainer commits and pushes per SP2 (#554), as its own commit in set B, after #555.
Acceptance criteria
-
grep -n 'RUN bun install' apps/webapp/docker/Dockerfile.bunprints two lines, both ending in--frozen-lockfile --ignore-scripts. - In
build-extensions, only the six extensionpackage.jsonfiles are copied before the install, and the six sourceCOPYlines follow it. Lines 109–118 are unchanged and in the same order. -
git diff --exit-code -- bun.lock package.json .dockerignorepasses. Thedepsandrunnerstages are unchanged. - The Step 3, Step 4 and Step 5 pass rules hold.
- The AGENTS.md §Docker bullets exist.
bun run check:agent-docsandbun run formatpass. - The "Local proof" comment exists.
- On the first
(build):deploy with this change, each webapp install printsDONEin 20 s or less, orCACHED. - The four hand checks in Accountability pass, and the SP3 comment records them.
Verification
Local: the Step 2 and Step 6 checks, plus the Docker commands of Steps 3–5. No workflow file or triage script changes, so actionlint, bash .github/scripts/parse-build-trigger.test.sh and a dry_run_subject dispatch do not apply.
Post-merge. Read the first successful attempt-1 (build): deploy that contains the commit. Push no extra deploy. Find the job that holds 🏗️ Build Docker Images (this works before and after #565). In a fresh shell, first run T="$(mktemp -d)" and the Step 3 cat > "$T/installs.awk" heredoc.
RUN=<run id>
JOB=$(gh run view "$RUN" --repo docs-plus/docs.plus --json jobs --jq '.jobs[] | select(any(.steps[]?; .name | test("Build Docker Images"))) | .databaseId')
gh run view --repo docs-plus/docs.plus --job "$JOB" --log > "$T/deploy.log"
awk -f "$T/installs.awk" "$T/deploy.log"Record the cache fields of that deploy:
lock_changed:git diff --quiet <previous deployed SHA> <SHA> -- bun.lock;ext_src_changed: the same check onextensions/;gap_h: hours since the previous deploy run started.
Record per SP3 (#554). Metrics are defined in #569.
| Metric | Read from | Baseline | Expected |
|---|---|---|---|
Webapp install DONE seconds (primary) |
awk output on the job log | 144.3–182.2 s per deploy, median 158 s (6 jobs above) | each 20 s or less, or CACHED; sum 40 s or less |
| M3 | step summary (#556) | median 546 s, n = 14 | about 426 s. One deploy is a read, not a result. |
| M2i | step summary (#556) | equals the M3 end today | lower by about the same seconds as M3 |
| M1 | step summary (#556) | median 1090 s, p90 1386 s, n = 14 | about 970 s for this change alone. One deploy has no pass or fail. |
| M2g, M4, M5, W, M6 | not read | — | This issue does not change them. |
Part 2 in production. On the first deploy with ext_src_changed yes, lock_changed no and gap_h 24 or less, expect the build-extensions install to print CACHED. Record it in a new SP3 comment then. It is not a gate: the 24 h build-cache prune can clear the cache (#564). |
Accountability
- The builder (the assignee) runs Steps 0–7, posts "Local proof, ", and posts the SP3 comment on the deploy day. It adds the job id, the two awk install lines,
lock_changed,ext_src_changedandgap_h. - The maintainer runs four hand checks the same day and adds them to that comment:
/_next/image(sharp): the Step 0.5 command prints200 image/webp./sw.js: the Step 0.5 command prints200 application/javascript; charset=utf-8.- Sourcemaps: in GlitchTip project
prod_docsplus, find the source map upload made during this deploy's🏗️ Build Docker Imagesstep, by date, not by release. The image build has no.gitand noGITHUB_SHA, so the plugin uploads by debug ID with the literal releaseundefined. Confirm the previous deploy also has an upload at its build time. If GlitchTip lists no uploads by date, open the newest client error after this deploy. Its stack frames must show originalsrc/file names, as an earlier error does. If the previous deploy has neither, record "not comparable". - A pad loads: create a scratch pad you own, type one character, reload, and see the character.
- Stop condition, revert: any hand check fails, or
🏗️ Build Docker Imagesfails because of this change. Follow Rollback the same day. - Stop condition, no revert: an install still prints
DONEabove 20 s. The image works, so do not revert. Comment here with the awk output, and read that install's log for the slow part.
Rollback
Revert per SP4 (#554), on the revert stop condition above. The maintainer runs it.
- Before the push, run
git diff --stat HEAD~1. Expect onlyapps/webapp/docker/Dockerfile.bunandAGENTS.md. - The revert affects the deploy, so run
git commit --allow-empty -m "(build): front"and push it. Prod deploys read only the pushed tip commit subject. A tip subject that starts with "Revert" does not deploy. - On that deploy, run the Verification post-merge commands. Expect both install lines to end in
RUN bun install --frozen-lockfile, with no--ignore-scripts. Run the four hand checks again. - In the
Revertedcomment, add the revert commit, the awk output and the hand check results.
No host step and no setting change is needed.
Docs to update
AGENTS.md§Docker: the two new bullets (Step 6).apps/webapp/docker/Dockerfile.bun: the new comments (Steps 1 and 2) replace the line 98 comment.- No CHANGELOG entry. The change has no user-visible effect.
Out of scope
- Moving lines 93–94 or 180–183 to manifests-only. No tracker child covers it. File it separately with measurements.
- A permanent build step that fails when the
sentry-clibinary is missing. Step 4 proves it once, and the AGENTS.md bullet asks for a re-check after a major bump. - A BuildKit cache mount on the Bun install cache. The tracker Not doing list refutes it on disk safety. The header at
Dockerfile.bun:6keeps mounts off dependency installs. --filteron the webapp installs, or removingpython3,make,g++ornodejsfrombase(lines 19-27). Not covered, not measured.- Any dependency,
bun.lock,trustedDependenciesor rootoverrideschange. The metascraper family stays at5.50.6(AGENTS.md:193). Do not add a directrequire('re2')(AGENTS.md:200). - The Bun version. The base stays on
oven/bun:1-slim, the floor on Bun 1.4.0 (AGENTS.md:66). The CI pin is Keep the Actions cache under its limit and pin Bun in CI #561. - The settled Docker rules and the Docker judging rule in [Tracking] Faster, change-aware CI/CD pipeline #554 §Shared rules.
- Sibling items: the siblings under Depends on / Blocks, and Build the deploy images while the quality gates run #565 (build images early).
- Tracker Not doing list: Turbopack, hosted runners with a GHCR push, and one
RUNfor the extension builds. - Tests. No unit or Cypress test. The cache-hit check and the smoke comparison are the proof.
Read first
AGENTS.md§Docker (lines 286-307), and lines 66, 193 and 200.apps/webapp/docker/Dockerfile.bun, the whole file.apps/admin-dashboard/docker/Dockerfile.bunlines 15–41 and 66–87, andapps/hocuspocus.server/docker/Dockerfile.bunlines 48–56 and 88–94..dockerignore.apps/webapp/next.config.jslines 37–45 (next-pwa) and 435–449 (Sentry)..github/workflows/prod.docs.plus.ymllines 612–640 (image build) and 855–857 (rollback).apps/hocuspocus.server/CLAUDE.md§Production And Docker Compose..cursor/skills/tech-writer/SKILL.md§Simplified English, for the AGENTS.md bullets and the comments.
Activity
HMarzban commentedon Oct 25, 2026
Built and committed on local main. Awaits the maintainer's final push.
Remaining gate: the push. The next production webapp image build then checks the install cache.