# The deployable image for headquarters-api. The build context is the repo # root, where the workspace lives: # # docker build -f services/api/Dockerfile . # # compose.yaml deliberately does not build it — development runs against a # bind mount — so this stays a production artifact instead of drifting into a # development environment that happens to ship. # # The build stage always runs on the building machine's own architecture and # cross-compiles when the requested platform differs, so an arm64 image # builds on an x86 machine at native speed with no QEMU. amd64 and arm64 are # both supported targets: the same file serves the Graviton deploy and a # local build on either kind of machine. # # glibc rather than musl, because Debian ships a cross gcc as an ordinary # package and no distribution ships one for musl — a static binary was worth # less than a boring toolchain. Both stages pin trixie so the builder's glibc # is never newer than the runtime's. FROM --platform=$BUILDPLATFORM rust:1-trixie AS build ARG BUILDARCH TARGETARCH WORKDIR /src # Map Docker's architecture names onto Rust triples and Debian package names. # The cross toolchain is only installed when the target is not the machine's # own architecture; the native one is already in the image. The cross libc # headers must be named explicitly: they are only a Recommends of the cross # gcc, and recommends are off. RUN set -eux; \ case "$TARGETARCH" in \ amd64) triple=x86_64-unknown-linux-gnu; pkgs="gcc-x86-64-linux-gnu libc6-dev-amd64-cross" ;; \ arm64) triple=aarch64-unknown-linux-gnu; pkgs="gcc-aarch64-linux-gnu libc6-dev-arm64-cross" ;; \ *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ esac; \ echo "$triple" > /triple; \ rustup target add "$triple"; \ if [ "$TARGETARCH" != "$BUILDARCH" ]; then \ apt-get update; \ apt-get install -y --no-install-recommends $pkgs; \ rm -rf /var/lib/apt/lists/*; \ fi # Cargo needs an explicit linker for a cross target, and the C dependencies # (bundled sqlite, aws-lc) find their cross compiler through the cc crate's # CC_ convention. Debian's native gcc also answers to its # triplet-prefixed name, so all four are safe to set whether or not the build # is a cross one. ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc \ CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc COPY Cargo.toml Cargo.lock ./ COPY services ./services # Every workspace member, not just the one being built: cargo loads all of # their manifests before it builds anything, so a missing one fails the build # rather than being skipped. COPY crates ./crates # Cache mounts persist the registry and build artifacts across builds, keyed # per target so concurrent amd64 and arm64 builds don't contend for one cargo # lock. The binary is copied out because cache mounts are not part of the # image. RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ --mount=type=cache,target=/src/target,id=cargo-target-$TARGETARCH \ set -eux; \ triple="$(cat /triple)"; \ cargo build --release --locked -p headquarters-api --target "$triple"; \ cp "target/$triple/release/headquarters-api" /headquarters-api # ca-certificates because the AWS SDK reads the system trust store # (rustls-native-certs); the old alpine base shipped a bundle by default, # debian-slim does not. # # This runs on the building machine's architecture, not the target's, so the # final stage needs no RUN at all and the build stays QEMU-free end to end. # Everything it produces is architecture-independent: ca-certificates is an # Architecture: all package, and useradd only edits text files. Same base # image as the runtime stage, so the account files it copies out are the # runtime's own plus the new user. FROM --platform=$BUILDPLATFORM debian:trixie-slim AS rootfs RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && useradd --system headquarters FROM debian:trixie-slim COPY --from=rootfs /usr/share/ca-certificates /usr/share/ca-certificates COPY --from=rootfs /etc/ssl/certs /etc/ssl/certs COPY --from=rootfs /etc/passwd /etc/group /etc/ USER headquarters COPY --from=build /headquarters-api /usr/local/bin/headquarters-api # The build ref is logged at startup and reported by /api/version, so a # running box says which build it is; push.sh passes the commit sha. ARG BUILD_REF=unknown ENV BIND_ADDR=0.0.0.0:3000 \ BUILD_REF=$BUILD_REF CMD ["headquarters-api"]