Multi-arch image builds (linux/amd64,linux/arm64) emulate the foreign arch via
QEMU. The unpinned `FROM node:22-slim` frontend-build stage was built per
target arch, so Node ran under emulation and V8's baseline JIT crashed (SIGTRAP
/ exit 133 on `npm ci`) on the CI runner's QEMU.
Pin the stage to --platform=$BUILDPLATFORM so the static, arch-independent SPA
dist/ is built once on the native build host and COPYd into each arch's runtime
stage. Node never runs emulated; both arch images are still produced.
- Dockerfile: node:22-slim stage runs npm ci + npm run build; python runtime
stage COPY --from copies dist to /app/frontend/dist (matches SPA_DIST_DIR);
runtime image has no node
- .dockerignore: exclude frontend/node_modules and frontend/dist from context
- .github/workflows/frontend.yml: npm ci + codegen-sync + lint/typecheck/test/build
- tests/test_deployment.py: skip COPY --from sources in the context-existence
check; assert the multi-stage frontend build wiring
- verified with a real docker build (image serves SPA, no node at runtime)
M1-T04 deleted the alembic_location / alembic_poo chains and their .ini files,
but the Dockerfile still COPYed those four paths, so the release image build
failed at 'COPY alembic_poo.ini ./' (path not found). Drop the four stale COPY
lines (only alembic_app remains). Add test_dockerfile_copy_sources_exist, which
asserts every Dockerfile COPY source exists in the build context, so this class
of breakage fails pytest instead of only surfacing in the image CI.
Verified with a real local 'docker build' (succeeds) and pytest (98 passed).