Deploy: containers, Kubernetes and Lambda
Pynox ships as a self-contained bundle: bin/pynox plus its CPython. Nothing
is downloaded or written at run time, so the same archive works in a Docker
image, a read-only root filesystem or a Lambda package. What varies is the
kernel, and with it whether the sandbox can run.
The kernel decides
The sandbox needs Landlock ABI 3 (Linux 6.2 or newer) and the ability to
install a seccomp filter. pynox doctor reports landlock_abi and
sandbox_supported; when the sandbox cannot start, pynox exits 125 with the
reason and never falls back to unrestricted execution. Use --no-sandbox
explicitly where the kernel cannot do it.
| Environment | Kernel | Sandbox | Notes |
|---|---|---|---|
| Your Linux host, VM, EC2 | distribution kernel | yes, if 6.2+ | Where the default mode shines |
| Docker on such a host | host kernel | yes, with Docker's default seccomp profile (Docker 25, measured) | Older engines or custom profiles may block Landlock; check with pynox doctor |
| Kubernetes | node kernel | same as Docker, per node | Check nodes with pynox doctor |
AWS Lambda, provided.al2023 arm64 |
5.10 (measured 2026-10-18) | no | Landlock ENOSYS; prctl and pidfd_open blocked; see below |
| AWS Fargate | Firecracker guest kernel | expect no; verify | Same family as Lambda |
Docker
Each release publishes images per CPython minor and libc, for linux/amd64
and linux/arm64:
ghcr.io/ferrumio/pynox:0.1.0-py3.12 Debian bookworm-slim, glibc
ghcr.io/ferrumio/pynox:0.1.0-py3.12-alpine Alpine 3.22, musl
ghcr.io/ferrumio/pynox:py3.12 latest release for that Python
The image is the base plus the bundle in /opt/pynox, nothing else: no build
step runs inside it. The entry point is pynox, the working directory /app
and the user an unprivileged 65532.
docker run --rm -v "$PWD:/app:ro" ghcr.io/ferrumio/pynox:0.1.0-py3.12 job.py
docker run --rm -i --read-only ghcr.io/ferrumio/pynox:0.1.0-py3.12 pool --workers=4
Build your own on top, with your dependencies:
FROM ghcr.io/ferrumio/pynox:0.1.0-py3.12
COPY --chown=65532:65532 . /app
CMD ["pool", "--config", "/app/pynox.toml"]
Measured on 2026-10-18 with Docker 25.0.16 on Linux 6.18 (arm64), with the
default seccomp profile and no extra flags: pynox doctor reports
sandbox_supported: true, sandboxed scripts run, a readable file outside the
grants is denied while --no-sandbox reads it, and the pool works on a
read-only root filesystem. Landlock and seccomp need no privilege, so the
non-root user is enough. Older Docker engines and custom profiles may still
block the Landlock calls; pynox doctor names the blocked interface. Pynox's
own CI runs its build containers with seccomp=unconfined because it builds
and tests there, not because the runtime needs it.
deploy/docker/Dockerfile and scripts/image_context.py build the same image
from local bundles:
python scripts/image_context.py ctx dist/pynox-0.1.0-cp312-linux-gnu-*.tar.gz
docker buildx build --platform linux/amd64,linux/arm64 -t pynox:py3.12 ctx
Kubernetes
The node's kernel and container runtime decide, exactly as with Docker. Run a
one-off pod with pynox doctor on each node pool before relying on the
sandbox, and set the pod's seccompProfile so the Landlock and seccomp
syscalls are allowed. Managed node images vary: check, do not assume.
AWS Lambda
Lambda's kernel cannot run the sandbox, measured on 2026-10-18 in eu-west-2
with a provided.al2023 arm64 function:
| Probe | Result |
|---|---|
| Guest kernel | 5.10.255 (Amazon Linux 2 kernel) |
landlock_create_ruleset |
ENOSYS; Landlock needs 5.13+ |
prctl(PR_SET_NO_NEW_PRIVS), prctl(PR_SET_PDEATHSIG) |
EPERM: Lambda's own seccomp denies them |
pidfd_open, close_range |
ENOSYS |
unshare(CLONE_NEWUSER) |
EPERM |
So pynox script.py and pynox pool cannot start there, and pynox doctor
lists all three reasons. Lambda already isolates each function in a microVM;
the pattern for agent code is a dedicated function per trust level. What
Pynox brings to Lambda is the runtime.
The layer
Each release ships pynox-lambda-layer-<version>-cp3XY-<arch>.zip. It
unpacks to /opt/pynox, /opt/bootstrap and /opt/pynox-runtime, a custom
runtime that runs your _HANDLER (module.function) on Pynox without the
sandbox, with the (event, context) signature of the managed Python runtimes.
The layer leaves out what a function cannot use: Tk, IDLE, ensurepip, C
headers, pip and setuptools.
aws lambda publish-layer-version --layer-name pynox-py311-arm64 \
--content S3Bucket=my-bucket,S3Key=pynox-lambda-layer-0.1.0-cp311-arm64.zip \
--compatible-runtimes provided.al2023 --compatible-architectures arm64
aws lambda create-function --function-name my-fn --runtime provided.al2023 --architectures arm64 \
--handler handler.handler --zip-file fileb://function.zip --role "$ROLE_ARN" \
--layers arn:aws:lambda:REGION:ACCOUNT:layer:pynox-py311-arm64:1
function.zip holds only your code; dependencies go in it or in another layer,
built for the same CPython minor and architecture. scripts/lambda_layer.py
builds the layer from a local glibc bundle.
How it compares
Same handler on both, 1024 MB, arm64, writing 20,000 records with json.dump
to /tmp; four cold starts for the layer, three for the managed runtime:
| Pynox layer | Managed python3.11 |
|
|---|---|---|
| Init duration, cold | 181–218 ms | 57–75 ms |
| Handler duration, warm | 18–52 ms | 130–176 ms |
json.dump inside the handler, warm |
30–47 ms | 124–168 ms |
| Max memory used | 49 MB | 39 MB |
The managed runtime starts faster: its files are cached on Lambda's hosts,
while a layer's files are read for the first time on every cold start. The
layer's runtime loop speaks the Runtime API over a plain socket for that
reason; with urllib its init measured about 450 ms. Pynox runs the work
faster, here 4× on json.dump, so whether a cold invocation is faster
overall depends on the handler. For this one, init plus execution was about
240 ms with the layer and 250–330 ms with the managed runtime.
Expect Lambda's kernel to change without notice; keep --no-sandbox
explicit, which the layer's bootstrap does, so a future kernel with Landlock
does not silently change a function's behavior.