# 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"]
