# syntax=docker/dockerfile:1.7 # # OpenMFA — multi-stage build. # # build : cross-compiles to x86_64-unknown-linux-musl with cargo-zigbuild. # runtime : Alpine + the static binary. ~15 MB total. # # ## Why cross-compile instead of building for the target platform # # The obvious `FROM --platform=linux/amd64 rust` runs the entire compiler # under QEMU when the builder is an Apple-Silicon Mac. That is roughly 15× # slower, and the emulated linker intermittently SIGSEGVs part-way through. # # Pinning this stage to $BUILDPLATFORM instead runs rustc natively at full # speed and emits x86_64 via cargo-zigbuild, which uses `zig cc` as the # cross-linker — zig ships a musl sysroot for every target, so there is no # cross-gcc toolchain to assemble. Deterministic, and minutes rather than # half an hour. ARG RUST_VERSION=1.95 ########## build ########## FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-bookworm AS build WORKDIR /src # zig (via the `ziglang` pip package — cargo-zigbuild discovers it as # `python3 -m ziglang`) supplies the musl sysroot and the linker. RUN apt-get update \ && apt-get install -y --no-install-recommends python3 python3-pip \ && rm -rf /var/lib/apt/lists/* \ && rustup target add x86_64-unknown-linux-musl \ && pip3 install --no-cache-dir --break-system-packages ziglang \ && cargo install --locked cargo-zigbuild # Deliberately NOT copying rust-toolchain.toml: it pins `channel = "stable"` # for local development, and inside the image that makes rustup download a # second full toolchain — slow, and enough to fill a small Colima disk. COPY Cargo.toml Cargo.lock ./ COPY crates/ crates/ RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/src/target,sharing=locked \ cargo zigbuild --release --target x86_64-unknown-linux-musl \ --bin openmfa \ && cp target/x86_64-unknown-linux-musl/release/openmfa /openmfa \ && ls -l /openmfa ########## runtime ########## # Alpine, not Debian: the binary is fully static musl and shells out to # nothing, and its TLS roots are compiled in via webpki-roots — so the image # needs no ca-certificates package and no runtime libraries at all. What it # does need is a shell, for the bind-mount chown in the entrypoint (see there # for why that matters on Unraid). FROM alpine:3.21 AS runtime RUN apk add --no-cache tini su-exec \ && addgroup -g 10001 -S openmfa \ && adduser -u 10001 -S -G openmfa -h /var/lib/openmfa openmfa \ && mkdir -p /var/lib/openmfa \ && chown -R openmfa:openmfa /var/lib/openmfa COPY --from=build /openmfa /usr/local/bin/openmfa COPY deploy/docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/openmfa # NOT `USER openmfa` — the entrypoint starts as root to fix bind-mount ownership, # then drops to uid 10001 via su-exec. Its non-root branch handles the case # where the platform has already assigned a uid. WORKDIR /var/lib/openmfa ENV OPENMFA_WORK_DIR=/var/lib/openmfa \ OPENMFA_HTTP_ADDR=0.0.0.0:8720 \ OPENMFA_LOG=info,openmfa=info EXPOSE 8720/tcp VOLUME ["/var/lib/openmfa"] HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget -qO- http://127.0.0.1:8720/healthz || exit 1 ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]