nativegate
← Blog

How to containerize legacy scientific software

Containerizing a 1990s numerical model is different from containerizing a web app, mostly because the interesting decisions are invisible in a Dockerfile. This guide covers what matters when the binary being shipped is forty years of physics.

Decide first: compiler in the image, or wheel in the image?

Two honest architectures exist. Ship the compiled wheel and a slim runtime — the generated nativegate Dockerfile's shape: builder stage has the toolchain, runtime stage is python:3.11-slim plus the wheel. Or ship the full compiler toolchain and build at startup — which buys nothing at runtime and costs attack surface, size, and startup time for a program whose inputs are known. Prefer the wheel unless you have a stated reason otherwise.

Run unprivileged — deliberately, in both files

The runtime user must be unprivileged, and it must match somewhere. Generated nativegate images create an explicit UID 1000; its generated Kubernetes manifests set runAsUser: 1000 and runAsNonRoot: true — generated from one source so the two files can't drift into a permissions failure nobody attributes to either. If you're writing these by hand, copy the pattern: one number, declared twice, in review together.

Read-only root filesystem is a Python contract

readOnlyRootFilesystem: true is a good posture and a Python constraint: the interpreter writes .pyc files next to source by default, and a read-only root turns that into a crash. Set PYTHONDONTWRITEBYTECODE=1 (and PYTHONUNBUFFERED=1 so logs stream), or pick your writable-disk story consciously.

Health probes match the model

/healthz and /readyz on a service backed by native code should probe what you actually care about: process up, and the native surface answering. The generated service includes both, with SIGTERM draining so rollout restarts don't cut off in-flight calls.

What containerization does not fix

  • Statefulness. A container still runs one process with one COMMON-block store; the one-call-at-a-time rule survives containerization intact. Scale with replicas.
  • Native crashes. A segfault in the physics kills the worker inside a perfectly configured container. Restarts and replicas contain it; the container never prevents it.
  • Numerical drift. A container pins the build for the moment it was built; a golden record pins the answers across every rebuild. Ship both together.

An honest conclusion for a container built from generated artifacts: the image is the smallest and most boring part of the deliverable — which is precisely why it's a good candidate for generation.

See the generated Dockerfile → Next: deploying as an API