ngate docker <name> --build produces a two-stage
Dockerfile: a builder stage with the compiler toolchain, and a slim
runtime stage that copies in just the built wheel. That part is
unremarkable — most generated Dockerfiles do this. The detail worth
writing down is what the runtime stage does right before it starts
the service:
An explicit, hardcoded UID — not "whatever useradd hands
out next" — because it has to match somewhere else: the Kubernetes
manifests ngate also generates set
securityContext.runAsUser: 1000 and
runAsNonRoot: true. If the image's UID and the manifest's
expected UID ever drifted apart, the pod would fail to start with a
permissions error that has nothing obviously to do with either file
— the kind of bug that costs an afternoon to trace back to "someone
changed the Dockerfile's useradd line." Generating both
from the same source avoids that class of bug entirely, rather than
documenting a convention and hoping it's followed.
readOnlyRootFilesystem, and the one line it requires
The generated Kubernetes manifests also set
readOnlyRootFilesystem: true. That's a real constraint on
a Python process: the interpreter writes .pyc cache files
next to source by default, and a read-only filesystem turns that into
a startup crash instead of a warning. The generated Dockerfile sets
PYTHONDONTWRITEBYTECODE=1 so the interpreter never
attempts it, and PYTHONUNBUFFERED=1 so stdout reaches a
log collector as it's written rather than whenever a buffer happens
to flush — the second thing you want to be true right before the
first thing makes a process misbehave.
None of this is a security product bolted on afterward. It's the same container security posture — unprivileged process, minimal writable surface, UID that matches its own orchestration manifest — that a Kubernetes-literate engineer would build in by hand for any production service, applied consistently because it's generated rather than remembered.
Source: tools/nativegate/nativegate/generators/docker_gen.py
in the nativegate repository.