Docker for ML
Theme
Palette
Download
Caption (tap to copy)
📖 Deep dive (full written explanation)
This cover leads with the most viscerally familiar ML pain: incompatible CUDA versions and a package manager that resolved your deep-learning framework against the wrong stack. The hook is specific because the audience has lived it — ML environments break more often and more confusingly than almost any other kind of software.
Post two is the 'so what.' Having defined images and containers in post one, here we make the case for why Docker is not a nice-to-have in ML but a practical necessity: it ends dependency hell, pins the fragile GPU stack, and makes results reproducible long after the original run.
The first reason is dependency hell, which ML suffers more acutely than most fields. A single project pins a deep-learning framework, numerical libraries, model libraries, CUDA components, and system packages — each with version constraints. Those constraints routinely conflict across projects: the version that fixes one model breaks another sharing the same machine.
Docker resolves this by giving each project its own isolated, pinned environment inside an image. Two models with mutually incompatible dependency sets run side by side without ever touching each other or the host's Python. The conflict doesn't get cleverly resolved; it gets eliminated by isolation, which is far more reliable than trying to find one environment that satisfies everyone.
The comparison contrasts life on a bare machine with life under Docker. On a bare machine there's typically one shared Python environment that every project fights over; you get the classic 'works on my machine' divergence, and onboarding a new developer means a fragile, hours-long setup that's never quite the same twice.
With Docker, each project gets an isolated, pinned environment that runs identically on every machine, and onboarding collapses to a single command that pulls and runs the image. The right-hand column is not aspirational — it's the routine outcome once environments are expressed as images rather than as accumulated, undocumented changes to a shared host.
The second reason is the CUDA and driver matching nightmare, which is unique to GPU computing. Running a model on a GPU requires a compatible match between the framework's build, the CUDA runtime it was compiled against, and the NVIDIA driver on the host. Get any link wrong and you get cryptic load errors — or, worse, silent fallback to CPU that just makes everything mysteriously slow.
Official CUDA base images bake the CUDA runtime and cuDNN into the image, so the only thing the host must provide is a compatible NVIDIA driver. Docker doesn't make the matching problem vanish entirely — the host driver still matters — but it collapses a multi-component puzzle down to a single host-side requirement, which is a massive reduction in what can go wrong.
The third reason is reproducibility you can actually trust. The real test is whether, six months after a training run, you can recreate the exact environment that produced it. With a pinned image the answer is yes: same interpreter, same library versions, same CUDA. With an ad hoc environment, the answer is usually 'we think so,' which is not good enough when a result has to be defended.
Tagging the image with the model version turns it into a permanent, runnable record. This is the difference between a result you can stand behind in an audit or an incident review and one you simply hope still reproduces. In regulated or high-stakes settings, that runnable record is often a hard requirement, not a nicety.
The fourth reason is closing the train-serve gap. A large and insidious class of production bugs comes from training in one environment and serving in another — a slightly different numerical library, a different tokenizer version, a preprocessing step that behaves subtly differently. The metrics you validated were produced by code your users never actually run.
Packaging both training and serving on the same base image closes that gap. The preprocessing, the library versions, and the runtime are identical across the two, so the code path that produced your offline metrics is the code path serving live predictions. This is one of the most underrated reliability wins Docker gives ML teams.
This code slide makes pinning concrete, because pinning is what turns 'reproducible' from a slogan into a fact. The requirements file lists exact versions with == rather than ranges, so a rebuild months later resolves to the same packages rather than whatever is newest. The comment reinforces that the base image should be pinned too — python:3.11.9-slim, not python:3.11 or python:latest.
The deliberate choice to show text rather than runnable code is to highlight that reproducibility is a discipline, not a feature you toggle on. An image built from unpinned ranges is reproducible in name only; the moment an upstream release lands, two builds of the 'same' Dockerfile diverge.
The pipeline diagram shows how a single image scales out across many GPUs and machines. You build the training image once, push it to a registry with a version tag, then pull that identical image onto N nodes — whether orchestrated by Kubernetes, Slurm, or a cloud batch service — and train at scale.
The important property is that every node runs the byte-identical environment. Distributed training is hard enough without each worker subtly differing in library versions; baking the environment into one pulled image removes that entire variable. The image is the unit of distribution, which is exactly what makes multi-node ML tractable to operate.
The fifth reason is the dramatic simplification of onboarding and CI. Without Docker, a new teammate spends a day wrestling CUDA, conda, and system libraries into a working state, and the result is never quite identical to anyone else's. With Docker, they run one command and have the exact environment.
CI gets the same benefit: it builds the same image and runs the same tests in the same environment that will ship. The environment stops being tribal knowledge living in one engineer's shell history and becomes a checked-in, versioned artifact that the whole team and the automation share. That shift — from oral tradition to versioned file — is quietly one of the biggest productivity gains a team gets.
The tips slide distills why Docker is a big deal for ML: it kills cross-project dependency conflicts through isolation, pins the notoriously fragile CUDA and driver stack, makes runs reproducible months later, closes the train-serve environment gap, and lets one image scale to many nodes.
These five points are the 'so what' a reader should carry away — enough to justify making Docker the default for any serious ML project and to appreciate why it sits at the center of modern MLOps.
The closing mistake corrects a dangerous overestimate of what Docker does for GPUs. Docker isolates your software stack — the CUDA runtime, the libraries, your code — but it does not virtualize the kernel-level NVIDIA driver. The host still needs a real NVIDIA driver installed and the NVIDIA Container Toolkit configured; the image only carries the userspace CUDA runtime.
People expect a 'GPU image' to run on any machine, then are baffled when it fails on a host with no driver or a driver too old for the image's CUDA version. The honest framing is that Docker shrinks the matching problem to a single host requirement — it does not erase it. Knowing exactly where the boundary sits is what keeps GPU deployments from failing in confusing ways.
The CTA hands off to post three, the engine room: layers and the union filesystem, how the build cache decides what to rerun, why instruction order changes build speed, and how GPU passthrough actually works under the hood. The teaser promises the mechanism behind the benefits just argued.