Step 1: The production Dockerfile
Every rule below is in the Dockerfile for a reason:
- Multi-stage build —
buildercompiles/installs; the final stage is a slim runtime. Saves 2-5 GB of image size. Critically, it prevents leaking build-time secrets (pip index URLs with tokens). nvidia/cuda:X.Y.Z-runtime-ubuntu22.04, not-devel. Runtime has the loader +libcudart+libcudnn. Devel addsnvcc,nvrtc, headers — ~2 GB extra. You don't compile CUDA at runtime.- Pin every version.
torch==2.3.0, base image with fullX.Y.Z,--no-install-recommends. Unpinnedlatestmeans yesterday's build is a different image than today's — catastrophic for reproducibility. - Non-root
USER. If the container is compromised, the attacker getsappuser, notroot. KubernetesSecurityContextcan also enforce this, but defense in depth. HEALTHCHECKinvokes the GPU smoke test, not a bare HTTP endpoint. An HTTP endpoint that returns 200 on a broken GPU is worse than no endpoint.
Spot the bug
There's a common production bug encoded below: the builder stage installs via pip install --user, then the final stage copies /root/.local → /home/appuser/.local. The appuser account needs to own those files to import them — --chown=appuser:appuser on the COPY is easy to forget, and forgetting it leaves the app broken only when an unprivileged user tries to run it (your prod). Notice we include that --chown explicitly — this is the pattern you want to internalize.
from IPython.display import Code
# The production Dockerfile — now a real file under workspace/ (CI, IDE linters,
# and docker build can all use it directly). We load it here so the check_script
# can validate its contents.
dockerfile_path = '/home/labuser/workspace/Dockerfile'
with open(dockerfile_path) as f:
dockerfile = f.read()
dockerfile_lines = len(dockerfile.splitlines())
print(f"Dockerfile: {dockerfile_lines} lines, "
f"{dockerfile.count('FROM ')} stages")
Code(filename=dockerfile_path, language='docker')