# syntax=docker/dockerfile:1 # Multi-stage production build for pyfconfig running on GraalPy 25 instead of # CPython. Base: ghcr.io/graalvm/graalpy-community:25 (GraalPy 25.0.2 / # Python 3.12.8, Oracle Linux 9 slim). # # GraalPy is not ABI-compatible with CPython wheels, so the two compiled # dependencies (granian and rloop, both PyO3/Rust extensions) have to be built # from source against the GraalPy interpreter. Everything else in # requirements.txt is pure Python and installs normally. # # The Rust toolchain and build artifacts live only in the builder stage and are # discarded in the runtime image. BuildKit cache mounts (type=cache) keep the # cargo registry and target dir across builds without baking them into layers; # requires BuildKit (default for `docker build` / `docker buildx` on modern # daemons). ARG GRAALPY_IMAGE=ghcr.io/graalvm/graalpy-community:25 # --- Builder --------------------------------------------------------------- FROM ${GRAALPY_IMAGE} AS builder # Rust toolchain for building the PyO3-based extensions. Keep the registry and # target dir in BuildKit cache mounts so rebuilds are cheap. # /opt/venv/bin must be on PATH so pip's maturin build backend can find the # maturin executable; /root/.cargo/bin provides cargo/rustc. ENV CARGO_HOME=/root/.cargo \ RUSTUP_HOME=/root/.rustup \ CARGO_TARGET_DIR=/build/target \ PATH=/opt/venv/bin:/root/.cargo/bin:${PATH} RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ | sh -s -- -y --profile minimal --no-modify-path WORKDIR /build # Use GraalPy's own pip: it is patched for native-package compatibility and # preconfigured with GraalPy wheel repositories, so it must not be upgraded. RUN graalpy -m venv /opt/venv \ && /opt/venv/bin/pip install maturin COPY pyproject.toml README.md requirements.txt ./ COPY src/ ./src/ # Build the Rust extensions from their sdists against GraalPy first (forcing # source builds with --no-build-isolation so the preinstalled maturin is used), # then the pure-Python deps, then the project itself. granian/rloop are already # satisfied when requirements.txt is installed. RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ --mount=type=cache,target=/build/target \ /opt/venv/bin/pip install --no-build-isolation \ --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple \ --extra-index-url https://pypi.org/simple \ granian==2.8.2 rloop==0.5.0 \ && /opt/venv/bin/pip install \ --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple \ --extra-index-url https://pypi.org/simple \ -r requirements.txt \ && /opt/venv/bin/pip install --no-deps . # --- Runtime --------------------------------------------------------------- FROM ${GRAALPY_IMAGE} RUN useradd -r -s /sbin/nologin -d /home/app -m app COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ HOME=/home/app \ GRANIAN_HOST=0.0.0.0 \ GRANIAN_PORT=8080 \ GRANIAN_INTERFACE=rsgi \ GRANIAN_LOOP=rloop USER app EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \ CMD curl -fsS http://127.0.0.1:8080/api/health || exit 1 CMD ["granian", "pyfconfig.app:app"]