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.
This commit is contained in:
@@ -23,6 +23,14 @@ A second image runs the exact same app on [GraalPy](https://www.graalvm.org/pyth
|
||||
with CPython wheels, the compiled `granian` and `rloop` extensions are built
|
||||
from source against GraalPy during the image build.
|
||||
|
||||
A third image compiles the app to a **single self-contained native
|
||||
executable** with GraalPy's `standalone native` command — see
|
||||
[Dockerfile.native](Dockerfile.native) and the
|
||||
[native executable](#native-executable) section. That build serves the app
|
||||
over ASGI with [Hypercorn](https://hypercorn.readthedocs.io/) instead of
|
||||
granian/rloop, because compiled extensions cannot be bundled into a standalone
|
||||
binary.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Path | Response |
|
||||
@@ -82,6 +90,34 @@ docker compose -f docker-compose.graalpy.yml up -d --build
|
||||
This image is published to the registry as the `*-graalpy` tags
|
||||
(e.g. `latest-graalpy`, `0.1.0-graalpy`).
|
||||
|
||||
### Native executable
|
||||
|
||||
The app can be compiled to a single self-contained executable (GraalPy runtime
|
||||
+ standard library + dependencies + app, no Python installation required) with
|
||||
GraalPy's `standalone native` command:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.native.yml up -d --build
|
||||
# App published at http://localhost:8080
|
||||
```
|
||||
|
||||
The executable is built in [Dockerfile.native](Dockerfile.native) on top of
|
||||
`ghcr.io/graalvm/graalpy-community:25` with the version-matched
|
||||
`native-image` toolchain, and runs on a slim `oraclelinux:10-slim` base. It is
|
||||
published as the `*-native` tags (e.g. `latest-native`, `0.1.0-native`).
|
||||
|
||||
Notes:
|
||||
|
||||
- The native build is memory-hungry: `native-image` needs several GB of RAM
|
||||
(tunable with the `NATIVE_IMAGE_XMX` build arg, default `5500m`) and takes
|
||||
roughly 8 minutes per build.
|
||||
- It uses GraalPy's interpreter-only runtime (`-Os`) for a smaller binary and
|
||||
a lighter build. Remove `-Os` in [Dockerfile.native](Dockerfile.native) to
|
||||
enable the JIT at the cost of build time and memory.
|
||||
- The native executable serves HTTP via Hypercorn over ASGI rather than
|
||||
granian/rloop (see [native/entrypoint.py](native/entrypoint.py)).
|
||||
- `HOST` / `PORT` configure the bind address (defaults `0.0.0.0:8080`).
|
||||
|
||||
### On the host
|
||||
|
||||
```bash
|
||||
@@ -133,6 +169,8 @@ src/pyfconfig/
|
||||
└── connection.py # /ip /ua /lang /encoding /mime /charset /forwarded /all /all.json
|
||||
tests/
|
||||
└── test_routes.py # unittest + httpx.ASGITransport
|
||||
native/
|
||||
└── entrypoint.py # Hypercorn ASGI entry point for the native executable
|
||||
```
|
||||
|
||||
Route modules import the module-level `app` from `pyfconfig.app` and
|
||||
@@ -141,15 +179,19 @@ register handlers with `@app.GET(...)` decorators at import time —
|
||||
|
||||
## Deployment
|
||||
|
||||
Two images are built from this repository:
|
||||
Three images are built from this repository:
|
||||
|
||||
- `Dockerfile` — multi-stage alpine build on CPython, producing a minimal,
|
||||
non-root runtime image.
|
||||
- `Dockerfile.graalpy` — multi-stage Oracle Linux build on GraalPy 25, where
|
||||
the compiled `granian` and `rloop` extensions are compiled from source
|
||||
against GraalPy (the Rust toolchain stays in the builder stage).
|
||||
- `Dockerfile.native` — compiles the app to a single self-contained native
|
||||
executable with GraalPy's `standalone native` command, served by Hypercorn
|
||||
over ASGI; the runtime image is a slim `oraclelinux:10-slim`.
|
||||
|
||||
Pushing a `release/*` tag triggers the Gitea Actions workflow in
|
||||
`.gitea/workflows/build.yaml`, which builds and publishes both images to
|
||||
`.gitea/workflows/build.yaml`, which builds and publishes all three images to
|
||||
`gitea.woggioni.net/woggioni/pyfconfig`: the CPython image as `latest` /
|
||||
`<version>`, and the GraalPy image as `latest-graalpy` / `<version>-graalpy`.
|
||||
`<version>`, the GraalPy image as `latest-graalpy` / `<version>-graalpy`, and
|
||||
the native executable as `latest-native` / `<version>-native`.
|
||||
|
||||
Reference in New Issue
Block a user