AI DevelopmentDevOps

Docker GPU in 2026: Ship AI Apps with Compose

August 25, 2026
10 min read
Shipping containers with glowing GPU cores, lifted by a crane at dusk
Share:

Your model runs fine on your laptop’s GPU, then dies the moment you containerize it. CUDA not found, driver mismatch, a 9GB image that takes twenty minutes to push — I have hit every one of these, and they all come from the same root cause: the host, the runtime, and the image were never set up as one system.

In this deep-dive I show you the complete 2026 setup: install the NVIDIA Container Toolkit, reserve GPUs in Compose with the verified deploy syntax, pick the right CUDA base image, and shrink your Python AI app with a multi-stage build.

1. The Problem: It Works on My GPU (and Nowhere Else)

Shipping a GPU-powered AI app has three moving parts that must agree with each other: the NVIDIA driver on the host, the container runtime that exposes the GPU, and the CUDA libraries inside the image. When any one of them drifts — a tutorial pins an old nvidia-docker2 flag, the Compose file uses a 2021-era runtime key, the Dockerfile ships a devel image to production — you get cryptic errors at 2am and images measured in gigabytes.

The Most Common Mistake

Installing the full CUDA Toolkit on the host. You do not need it there. The container carries its own CUDA userspace libraries; the host only needs the NVIDIA driver plus the NVIDIA Container Toolkit, which exposes the driver into containers at runtime.

The goal of this guide is a reproducible stack you can commit to git: one verified host setup, one compose.yaml with explicit GPU reservations, and one multi-stage Dockerfile that builds in devel and ships in runtime. Every command below was verified against the official NVIDIA and Docker docs.

2. Minimal Concepts You Actually Need

Only four ideas matter. Understand these and every GPU error message suddenly makes sense.

🖥️

Driver vs Toolkit vs Runtime

Host setup · NVIDIA docs

The host keeps the NVIDIA driver — nothing else. The NVIDIA Container Toolkit (nvidia-ctk) configures your container runtime so the driver is injected into containers on demand. No CUDA install on the host, ever.

📦

CUDA Flavors: base, runtime, devel

nvidia/cuda · Docker Hub / NGC

Tags follow {version}-{flavor}-{os}, e.g. 12.8.0-runtime-ubuntu22.04. base (~300MB) runs prebuilt binaries, runtime (~2GB) runs frameworks, devel (~6GB) compiles. Ship runtime, compile in devel.

🎫

Compose Device Reservations

deploy.resources · Docker docs

GPUs are reserved per service under deploy.resources.reservations.devices with driver: nvidia, capabilities: [gpu], and either count or device_ids — never both. capabilities is mandatory.

🔍

nvidia-smi Is the Source of Truth

Verify everything with it

Run nvidia-smi on the host first. If the host cannot see the GPU, no container ever will. Then run it inside the container to prove the whole chain works end to end.

The Golden Rule of CUDA Images

You almost certainly want runtime, not devel. If you are pip-installing PyTorch and running a script, nvcc is never called — shipping devel to production means shipping a compiler nobody uses. Compile in devel, ship in runtime.

3. Tutorial: GPU AI App with Compose, Step by Step

We will go from a bare Linux host with an NVIDIA GPU to a running Python inference API with GPU access, in six steps. Tested flow, official commands only.

Step 1

Confirm the host sees the GPU

Before touching Docker, prove the driver works. This must list your GPUs — if it fails, fix the driver first; nothing downstream can help.

nvidia-smi

Step 2

Install the NVIDIA Container Toolkit

These are the current repository steps from the official install guide (Ubuntu/Debian shown; RHEL-family uses the equivalent dnf repo). The toolkit package plus nvidia-ctk runtime configuration is all a Docker host needs.

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
  && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Step 3

Smoke-test with an official CUDA image

Run nvidia-smi inside a throwaway container. This proves driver → toolkit → runtime → image in one line. Pin an explicit tag — never a floating one.

docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu22.04 nvidia-smi

Step 4

Reserve the GPU in compose.yaml

This is the verified Compose syntax from the Docker GPU docs: one device entry with driver nvidia, capabilities [gpu], and count 1. Use count: all for every GPU, or device_ids like [“0”] to pin specific ones — but never combine count and device_ids in the same entry.

services:
  api:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - model-cache:/models
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

volumes:
  model-cache:

Step 5

Multi-stage Dockerfile: compile big, ship small

Build on runtime for pure-Python apps, or compile native extensions in devel and copy only the artifacts into runtime. This pattern routinely turns a 6GB image into a few hundred megabytes.

# Stage 1: build native extensions (only if you compile anything)
FROM nvidia/cuda:12.8.0-devel-ubuntu22.04 AS builder
WORKDIR /build
COPY requirements.txt .
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip \
    && rm -rf /var/lib/apt/lists/* \
    && pip3 install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: ship lean runtime
FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /install /usr/local
COPY . /app
WORKDIR /app
CMD ["python3", "main.py"]

Step 6

Up, then prove the GPU is visible

Build, start, and check from inside the running service. The torch one-liner is the real acceptance test for a PyTorch app.

docker compose up --build -d
docker compose exec api nvidia-smi
docker compose exec api python3 -c "import torch; print(torch.cuda.is_available())"

✅ Acceptance checklist

nvidia-smi works on the host, in docker run, and in docker compose exec — and torch.cuda.is_available() returns True. If all three pass, your stack is reproducible on any identical host with one compose up.

4. Common Errors (and the Exact Fix)

These five failures cover roughly 90% of Docker GPU pain. Each has one deterministic fix — no reinstall-the-world needed.

“could not select device driver nvidia”

The toolkit is missing or Docker was not restarted after configuring it. Fix: install nvidia-container-toolkit, run sudo nvidia-ctk runtime configure --runtime=docker, then sudo systemctl restart docker.

🧩

Compose errors on the devices block

You either omitted capabilities: [gpu] (mandatory — deployment fails without it) or combined count with device_ids (mutually exclusive). Keep exactly one of them per device entry.

🔢

CUDA version vs driver mismatch

Drivers are backward compatible, not forward: an old host driver cannot run a newer CUDA container. Fix: upgrade the host driver, or pin the image to a CUDA version your driver supports.

🐘

Production image is 6–8GB

You shipped a devel or cudnn-devel tag. Fix: multi-stage build — compile in devel, copy artifacts into base or runtime. For pure pip apps, just start FROM runtime directly.

🏷️

“manifest not found” on a tag that used to work

Docker Hub retires old CUDA tags over time. Fix: pin explicit tags and prefer nvcr.io (NVIDIA’s registry) for long-lived reproducible builds.

Golden rule

Debug in layers, always in this order: host nvidia-smi → docker run --gpus all nvidia-smi → compose exec nvidia-smi → app-level check. The first layer that fails tells you exactly which component to fix.

Conclusion

GPU containers feel fragile only when the three layers are improvised separately. With the system from this guide — driver plus toolkit on the host, explicit device reservations in Compose, pinned CUDA flavors, multi-stage images — your AI app becomes boring infrastructure: one repo, one compose up, same GPU behavior everywhere.

Start from the acceptance checklist above on your own machine, then pin your tags and commit. If you are deploying open models on that stack next, my GPT-OSS and Qwen3 guides show exactly what to run on top of it.

The Stack, in One Glance

Host

  • • NVIDIA driver + nvidia-smi
  • • nvidia-container-toolkit
  • • nvidia-ctk + docker restart

Compose

  • • deploy.resources.reservations
  • • driver nvidia + [gpu]
  • • count XOR device_ids

Image

  • • Pinned nvidia/cuda tag
  • • devel builds, runtime ships
  • • nvcr.io for long-lived pins

Sources

Diego Rodriguez

Diego Rodriguez

Senior Full-Stack & AI Engineer

Diego has 10+ years of experience building production-grade AI-powered applications, from LLM orchestration and RAG pipelines to ML-driven risk detection and algorithmic trading systems.

Learn more about Diego