✎ Edit content·DAY 097 · POST 5 OF 5 · Common Mistakes

Docker for ML

MLOps · 13 slides
DAY 097 · POST 5 OF 5
(REMINDER)
DAY 097
Docker for ML: Common Mistakes
@saurav_dnj_24github.com/SauravDnj · linkedin.com/in/sauravdnj
1 / 13

Theme

Palette

Download

4K — sharpest, slowest
🎬 Video options
Preparing preview…
Live preview · loops the “none” effect
All rendering runs in your browser. No server, no cost, no upload. MP4/WebM = full motion + effects · GIF = lightweight loop · PNG/PDF = static for the Instagram & LinkedIn carousel.

Caption (tap to copy)

📖 Deep dive (full written explanation)

The slides stay clean and scannable. Here's the in-depth explanation behind each one — great for the blog version, show notes, or studying the topic properly.
Slide 1 · Docker for ML: Common Mistakes

This cover sets the tone for the failure manual: broken ML containers almost never mean Docker itself failed — they mean a handful of avoidable mistakes bloated the image, busted the cache, or broke the GPU. That reframing is encouraging: the fixes are all within reach and mostly cost a line or two.

Post five completes the series by cataloging six mistakes that most often degrade ML containers in practice: bloated base images, cache-busting layer order, running as root, unpinned :latest tags, a missing GPU runtime, and ignored image size and security. Each is common, each is fixable, and each quietly costs build time, image size, or a production incident.

Slide 2 · 1. Using a bloated base image

The first mistake is starting from a bloated base. Building on the full python:3.11 — or hand-assembling from a generic ubuntu — ships compilers, documentation, and package caches the running service will never touch. For a serving image this is pure waste: the result is several gigabytes when it could be hundreds of megabytes.

The fix is to choose the right base for the job: a slim or distroless image for CPU serving, or an official CUDA runtime image for GPU work, combined with multi-stage builds so build-time tooling never reaches the final image. Smaller images pull faster, start faster, cost less to store and transfer, and present a smaller attack surface — every dimension improves at once.

Slide 3 · Bloat vs. lean

The comparison puts bloated and lean images side by side. The bloated column: the full python image, build tools left in the final image, a dataset baked in, and multi-gigabyte pulls on every deploy. The lean column: a slim or distroless base, a multi-stage build that keeps only the runtime, data mounted as a volume, and images measured in hundreds of megabytes.

Keeping this contrast in mind is the fastest way to audit your own Dockerfile. Each lean-column item is a concrete, independent fix, and you rarely need all of them at once — picking off even one or two typically cuts image size dramatically and speeds up your whole build-and-deploy loop.

Slide 4 · 2. Cache-busting layer order

The second mistake is cache-busting layer order, the single most common reason ML builds are painfully slow. Putting COPY . . before the dependency install means every code edit — even a one-character change — invalidates the layer holding your code, which forces the heavy install layer beneath it to rebuild, reinstalling the entire dependency stack including torch and CUDA.

The fix is the ordering convention from the mechanics post: copy requirements.txt and install first, then copy the code. More generally, order instructions from least-frequently-changing to most-frequently-changing. This costs nothing and is the highest-leverage Dockerfile change you can make, routinely turning multi-minute rebuilds back into a few seconds.

Slide 5 · Wrong vs. right ordering

This code slide shows the wrong and right ordering directly. The wrong version copies everything and then installs, so any change to any file busts the install layer. The right version copies just requirements.txt, installs into a stable cached layer, and only then copies the rest of the code.

The contrast is intentionally minimal because the fix is so small relative to its impact — three lines reordered. With the right ordering, editing your training script invalidates only the final code layer and the expensive install is reused from cache. Seeing the two side by side makes it obvious why the convention exists and why it's worth making automatic.

Slide 6 · 3. Running as root

The third mistake is running the container as root, which is the default if you do nothing. A process running as root inside the container has root in that container, and combined with any misconfiguration or a container-escape vulnerability, that's a wider path toward compromising the host. For an internet-facing ML service this is needless risk.

The fix is cheap: create an unprivileged user in the Dockerfile and switch to it with USER before the run command. The application almost never needs root to serve predictions, so dropping privileges removes an entire class of escalation for the cost of two lines. It's one of the easiest security wins available and should be a default habit for any image that runs a service.

Slide 7 · Add a non-root user

This code slide implements the non-root fix. After installing dependencies and copying code as root (which is fine at build time), it creates an unprivileged user with useradd --create-home and switches to it with USER appuser before the CMD. From that point the service runs without root privileges.

The ordering is deliberate: privileged operations like installing packages happen before the USER switch, and only the long-running service process runs unprivileged. If the application needs to write files at runtime, you ensure the relevant directories are owned by or writable to appuser. This pattern adds negligible size and turns a root-by-default service into a properly de-privileged one.

Slide 8 · 4. Relying on :latest

The fourth mistake is relying on :latest. FROM python:latest means the base can silently change between builds, and deploying myorg/model:latest means the running image can change out from under you — a base bump might pull a new CUDA version that breaks compatibility or a new library release that changes behavior overnight.

The fix is to pin. Pin base images by an explicit version, and in production ideally by digest, which nails the exact bytes regardless of how a tag is later moved. Tag your own images with the model version or git commit so every deployed artifact is identifiable and reproducible. Reproducibility — the entire reason to use Docker for ML — dies the moment 'latest' enters a Dockerfile.

Slide 9 · 5. Forgetting the GPU runtime

The fifth mistake is forgetting the GPU runtime. Build a beautiful CUDA-based training image, then run it with a plain docker run and the container sees no GPU at all. The especially nasty part is that PyTorch and most frameworks silently fall back to CPU rather than erroring, so training simply runs many times slower with no obvious cause.

The fix has two parts: ensure the host has the NVIDIA Container Toolkit installed, and pass --gpus all (or a specific GPU set) on docker run. Critically, verify before committing to a long job. Running nvidia-smi or checking torch.cuda.is_available() inside the container takes seconds and catches the silent-fallback trap before it wastes hours of compute.

Slide 10 · Verify the GPU before training

This code slide contrasts the wrong and right way to launch a GPU job. The wrong command omits --gpus, so the container has no GPU access and training quietly falls back to CPU. The right commands request GPUs with --gpus all and then verify access two ways: by running nvidia-smi to list the visible GPUs, and by printing torch.cuda.is_available() to confirm the framework actually sees them.

The lesson generalizes into a habit: never assume the GPU is wired up — prove it with a quick check before a long run. The verification commands cost seconds and turn the most common and most expensive GPU-in-Docker failure into an immediate, obvious signal rather than a slow, mysterious one.

Slide 11 · The checklist

The tips slide condenses the whole failure manual into a five-line checklist: use a slim base with a multi-stage build, copy dependencies before code, run as a non-root user, pin bases and tag images by version, and request --gpus while verifying with nvidia-smi.

These five lines are the practical residue of the entire series. A reader who internalizes them will avoid the great majority of ML container failures and will build images that are small, fast, reproducible, secure, and actually use the GPU they were built for.

Slide 12 · 6. Ignoring size and security

The sixth mistake is ignoring image size and security as ongoing concerns. Images that are never rebuilt accumulate known vulnerabilities in their old system packages, and oversized images slow every pull, every deploy, and every autoscaling event. An ML image left unattended drifts from 'shipped once' to 'liability.'

The fix is to treat the image as production software with a supply chain. Scan images in CI with a tool like Trivy or Docker Scout, rebuild on a regular cadence so base-image security patches actually land, and keep size and layer count down. Reasoning, accuracy, and uptime all depend on the substrate the model runs on; neglecting that substrate is how a working model becomes an incident.

Slide 13 · Save this. Follow for Day 98.

The CTA closes the series and hands off to the next day's topic, framing the five posts as a contribution to the reader's growing MLOps toolkit. It signals continuity — the series keeps building practical, in-depth technique — without naming the next subject, since each day brings something new.

🎨 AI image prompt (matches this theme + palette)

Paste into Midjourney, DALL·E, Ideogram, etc. to generate an on-brand image, then upload it on the Edit content page. The prompt updates automatically with the selected theme + palette.