Image: build the stylesheet and the catalogues once, natively #1231

Merged
tiagoagueda merged 2 commits from b9-1142 into main 2026-11-11 22:10:14 +00:00
Owner

The image builds what does not depend on the architecture once, natively, and a translation-only commit no longer rebuilds its stylesheet. Two signed commits; merge fast-forward-only.

  • Refs #1033 (item 4): styles runs on $BUILDPLATFORM, and a new catalogues stage on $BUILDPLATFORM compiles the message catalogues once (with its own native uv, since a COPY --from=<image> resolves for the target platform); the build stage copies the compiled tree instead of compiling under arm64 emulation (233 s in run 1901). collectstatic stays where it is. Nothing from node_modules leaves styles.
  • Closes #1142: a styles_source stage copies src and removes the catalogue directories, so the files npm run build:css is given are byte-identical across a translation-only commit and its layer is served from cache. COPY --exclude was not used: it needs Dockerfile frontend 1.19 and the runner's BuildKit version is unknown.

Tested: tests/test_image_build.py pins the new shape (its new tests fail on the old Dockerfile), with the supply-chain, workflow, ignore-file and shell-script tests. Not built: there is no Docker where this was written and no pull-request job builds the image, so the first build is the dev image after the merge. What to look for there: [linux/amd64 styles…] and [linux/amd64 catalogues…] steps and none for arm64, COPY --from=catalogues in the arm64 build, and no compile under arm64. If it fails, reverting the two commits (each independently) restores the old Dockerfile.

🤖 Generated with Claude Code

The image builds what does not depend on the architecture once, natively, and a translation-only commit no longer rebuilds its stylesheet. Two signed commits; merge fast-forward-only. - Refs #1033 (item 4): `styles` runs on `$BUILDPLATFORM`, and a new `catalogues` stage on `$BUILDPLATFORM` compiles the message catalogues once (with its own native uv, since a `COPY --from=<image>` resolves for the target platform); the build stage copies the compiled tree instead of compiling under arm64 emulation (233 s in run 1901). `collectstatic` stays where it is. Nothing from `node_modules` leaves `styles`. - Closes #1142: a `styles_source` stage copies `src` and removes the catalogue directories, so the files `npm run build:css` is given are byte-identical across a translation-only commit and its layer is served from cache. `COPY --exclude` was not used: it needs Dockerfile frontend 1.19 and the runner's BuildKit version is unknown. Tested: `tests/test_image_build.py` pins the new shape (its new tests fail on the old Dockerfile), with the supply-chain, workflow, ignore-file and shell-script tests. Not built: there is no Docker where this was written and no pull-request job builds the image, so the first build is the dev image after the merge. What to look for there: `[linux/amd64 styles…]` and `[linux/amd64 catalogues…]` steps and none for arm64, `COPY --from=catalogues` in the arm64 build, and no compile under arm64. If it fails, reverting the two commits (each independently) restores the old Dockerfile. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The image is built for amd64 and arm64, and arm64 runs under QEMU on an amd64
runner. Run 1901 of the dev image spent 232.7 s compiling the catalogues for
arm64, against about 20 s for amd64, and the Node steps ran a second time under
emulation whenever their inputs changed, for files that are the same bytes on
both.

The styles stage now runs on $BUILDPLATFORM, and a new catalogues stage, also
on $BUILDPLATFORM, compiles the .mo files and deletes the .po source in one
layer; the build stage takes its source from there instead of from the build
context, and BuildKit runs both stages once and hands the result to both
architectures. The .mo files are packed little-endian by name, so they do not
depend on the machine. uv for that stage comes from a native_uv stage, because
a COPY --from naming an image is resolved for the target platform whatever
platform the copying stage is on.

Nothing new is required of the builder: $BUILDPLATFORM is a BuildKit argument,
and the legacy builder already refused this file's RUN --mount. collectstatic
stays under emulation: moving it would need a native environment with every
baked-in plugin installed twice.

Refs #1033

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the catalogues out of what the stylesheet is built from
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 17m21s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 1m49s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m8s
CI / Security audit (pull_request) Successful in 20s
CI / Browser tests (Chromium) (pull_request) Successful in 36m54s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 14m28s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 1m44s
CI / Security audit (push) Successful in 19s
CI / Database tests on PostgreSQL (push) Successful in 4m18s
CI / Browser tests (Chromium) (push) Successful in 35m5s
Dev image / Dev image: build, scan, push (push) Successful in 48m30s
b20e038874
The styles stage copied all of src before npm run build:css, catalogues
included. Tailwind is told to skip them, but they were still part of that
layer's cache key, so a commit that changed only a translation built the
stylesheet again.

A styles_source stage now copies src and removes the two locale paths that
assets/css/app.css skips, and the styles stage copies its result. BuildKit
keys that copy by the content of the files it brings, so a translation reruns
the copy and the removal, hands on the same files, and the stylesheet layer is
served from the cache. A stylesheet built from the tree without the catalogues
is byte for byte the committed one.

Not COPY --exclude: the built-in Dockerfile frontend has it only since 1.19
(September 2025), the runner's version is not known, and a # syntax= line
would pull a frontend from Docker Hub on every build and add a pin to keep up.
The @source not lines stay, so a local build behaves the same.

Closes #1142

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda deleted branch b9-1142 2026-11-11 22:10:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo!1231
No description provided.