Multi-stage Dockerfile.graalpy on ghcr.io/graalvm/graalpy-community:25 that builds the granian and rloop PyO3 extensions from source against GraalPy (no GraalPy wheels exist), installs the pure-Python deps, and serves the unchanged RSGI app on the rloop event loop. Adds docker-compose.graalpy.yml and a CI job publishing the image as the *-graalpy tags on release/*.
87 lines
3.4 KiB
Docker
87 lines
3.4 KiB
Docker
# 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"]
|