nativegate
← Blog

Non-root by default: the container a generated service actually runs in

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:

generated Dockerfile — runtime stage
FROM python:3.11-slim COPY --from=builder /dist /dist RUN pip install --no-cache-dir /dist/*.whl RUN useradd --create-home --uid 1000 appuser USER appuser

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.

See what else gets generated → View on GitHub