fix(image): ship node_modules in three layers, and count them before pushing #18
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/registry-layer-cap"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The merge that landed phase 12 built its image and then could not publish it.
docker pushanswered413 Payload Too Largeon one blob and stopped — the registry stayed empty,needs: buildmeant the deploy never ran, and the log said only that a digest was too large. The site was merged and undeployed.One decision of record, D58, taking the count to fifty-eight.
The limit is Cloudflare's, not Gitea's
gitea.whitlocktech.comis proxied (Server: cloudflare), and Cloudflare refuses a request body over 100 MB on every plan below Enterprise — a plan limit, not a setting.docker pushuploads each layer as a single monolithicPUT, so the ceiling applies per layer, and the rejection happens at the edge: Gitea never sees the request, and nothing configured in Gitea can lift it.That is also why this had never happened before.
website-appis 103 MB in total, so every layer it has ever pushed fits.What was over, measured
Built from the merge commit and pushed to a local registry to read the manifest's compressed sizes:
COPY node_modulesnode:22-bookworm-slimbasenode_modulesis 289 MB on disk, and two packages account for most of it:@pagefind(57 MB, the search binaries) and@img(27 MB, sharp's libvips). Both are needed at run time, not just build time —applyBrand.mjsre-indexes the site and re-derives the brand images at boot — so what could move was where they land, not whether they ship.The fix: the same bytes, divided differently
The build stage moves those two aside after
npm prune; the runtime stage copies them as their own layers.Largest layer 50.5 MB, about 45 MB of headroom, and the image is exactly the same total size — 188.6 MB either way. Moving rather than copying twice is what keeps the three disjoint: whatever remains in
node_modulesis the remainder by construction, so a dependency added later lands in it with no maintenance here.The guard, because a split is a margin and not a guarantee
build-image.ymlnow counts layers before it pushes:docker save, re-compress anything over 8 MB the way the push would, and fail at 90 MB — not 100, since the blob is not the only thing in the request — with a message naming the layer and what would otherwise have happened.It was tested in both directions, against the fixed image and the broken one:
Re-compressing agrees with what the registry actually recorded (108 vs 108.8 MB), so the number in the error is the real one.
This is a workflow step, not a twelfth check script: it needs a built image rather than a source tree, which is the one thing the eleven never have. D53 holds.
What was run
npm run verify, green end to end — eleven checks,astro check, both suites (36 + 7).And the part no script does — the rebuilt container was run, because moving two packages is exactly the change that measures fine and fails at boot:
[serve] listening … 50 prerendered policiessiteName294×,tagline58×)@pagefindfrom its new layer/pagefind/pagefind.js200@imgfrom its new layerx-brand-source: derived:mount— sharp derived it from the mounted logo/beta200Notes
DEPLOY.mdgains the symptom (§10) and a paragraph in §7 saying it is fixed in theDockerfile, not on the host.rgcomrunner is registered and idle, in host mode, so §13's "start the runner" line is done — recorded in PLAN.md.Co-Authored-By: Claude.🤖 Generated with Claude Code
fix(image): ship node_modules in three layers, and count them before pushingto fix(deploy): publish an image the registry accepts, and a port the network can reachfix(deploy): publish an image the registry accepts, and a port the network can reachto fix(image): ship node_modules in three layers, and count them before pushing