The builder stage starts from `buildpack-deps` (what the `rust:` image is built on anyway), installs a pinned `rustup-init` with `--default-toolchain none`, copies only `rust-toolchain.toml` and lets rustup install the toolchain it names. The Rust version is now written in exactly one place, shared with CI, so a toolchain bump needs no Dockerfile edit.
The toolchain layer is cached on the content of `rust-toolchain.toml` and the base image, and it is the same bytes we pulled as `rust:` image layers before, so build times are unchanged.
The registry cache mount also moves to `/usr/local/cargo/registry`: the `rust:` image already set `CARGO_HOME=/usr/local/cargo`, so the previous `/root/.cargo/registry` mount never held anything.
Update `RUSTC_VERSION` in the Dockerfile and the matching clippy toolchain pin
in the CI workflow (kept in sync) from 1.93.0 to the latest stable, 1.96.0.
- Modify Dockerfile to build single architecture based on TARGETARCH instead of cross-compiling both targets in one run
- Replace single build-image job with matrix job (amd64, arm64)
- Add finalize-image job that creates multi-arch manifests using `docker buildx imagetools create` and signs the final images
- Each architecture gets its own build cache
This enables parallel builds of each architecture, reducing total build time by running both simultaneously rather than sequentially.
The frontend build stage now enables corepack, copies the workspace
manifest + lockfile, and runs `pnpm install --frozen-lockfile` followed
by `pnpm --filter mas-frontend run build`. The install layer is now keyed
on package.json + pnpm-workspace.yaml + pnpm-lock.yaml so it stays cached
when frontend source files change but dependencies don't.
Also add `node_modules` to .dockerignore so the root workspace's
node_modules doesn't leak into the build context.