Files
pyfconfig/Dockerfile.native
woggioni 4fa6c0cf2e Add self-contained GraalPy native executable
Build the app to a single native binary with graalpy's 'standalone
native' command (Dockerfile.native). granian/rloop cannot be bundled
into a standalone binary, so the executable serves the app over ASGI
with Hypercorn via native/entrypoint.py, which also works around
GraalPy standalone quirks (socket.listen, str response bodies, signal
handling).

Adds docker-compose.native.yml, a -native CI job, and README docs.
2026-09-24 11:11:00 +00:00

82 lines
2.9 KiB
Docker

# syntax=docker/dockerfile:1
# Native standalone build for pyfconfig.
#
# Produces a single self-contained executable bundling the app, its
# dependencies, the Python standard library and the GraalPy runtime, using
# GraalPy's `standalone native` command (backed by GraalVM Native Image).
#
# Unlike Dockerfile.graalpy, the executable does not run granian/rloop: those
# are native extensions that cannot be bundled into a standalone binary and
# granian spawns workers through multiprocessing. The app is served over ASGI
# by Hypercorn (pure Python) instead; see native/entrypoint.py.
#
# Build requirements: network access to the Gitea registry, PyPI, Maven Central
# and the base images, plus enough RAM for native-image (see NATIVE_IMAGE_XMX).
ARG GRAALPY_IMAGE=ghcr.io/graalvm/graalpy-community:25
ARG NATIVE_IMAGE_TOOLS=ghcr.io/graalvm/native-image-community:25
# GraalVM CE with native-image and javac, version-matched to the GraalPy
# distribution below. Copied into the builder so the standalone build does not
# download a JDK at build time.
FROM ${NATIVE_IMAGE_TOOLS} AS tools
# --- Builder ---------------------------------------------------------------
FROM ${GRAALPY_IMAGE} AS builder
ARG NATIVE_IMAGE_XMX=5500m
COPY --from=tools /usr/lib64/graalvm/graalvm-community-java25 /usr/lib64/graalvm/graalvm-community-java25
ENV JAVA_HOME=/usr/lib64/graalvm/graalvm-community-java25 \
PATH=/usr/lib64/graalvm/graalvm-community-java25/bin:${PATH}
# native-image links the executable against zlib; the runtime library is
# present but the linker needs the -devel package (libz.so).
RUN dnf install -y zlib-devel \
&& dnf clean all
WORKDIR /build
COPY pyproject.toml README.md requirements-native.txt ./
COPY src/ ./src/
COPY native/ ./native/
RUN graalpy -m venv /opt/venv \
&& /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-native.txt \
&& /opt/venv/bin/pip install --no-deps .
# -Os builds an interpreter-only Truffle runtime: much lower build memory and a
# smaller binary, at the cost of peak throughput. Remove it to enable the JIT.
# The cache mount keeps the Maven artifacts across builds.
RUN --mount=type=cache,target=/opt/graalpy-25.0.2/downloaded_standalone_resources \
mkdir -p /out \
&& graalpy -m standalone native \
-m native/entrypoint.py \
--venv /opt/venv \
-o /out/pyfconfig \
-ce -Os \
-N-J-Xmx${NATIVE_IMAGE_XMX} -N-J-Xms512m
# --- Runtime ---------------------------------------------------------------
FROM oraclelinux:10-slim
RUN useradd -r -s /sbin/nologin -d /home/app -m app
COPY --from=builder /out/pyfconfig /usr/local/bin/pyfconfig
ENV HOME=/home/app \
HOST=0.0.0.0 \
PORT=8080
USER app
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD curl -fsS "http://127.0.0.1:${PORT}/api/health" || exit 1
CMD ["/usr/local/bin/pyfconfig"]