Logo
suzarilshah.
Back to Blog
8/15/2026 5 min read...

Dockerizing the AI Stack: What Actually Works in Production (And What Doesn't)

A
Suzaril Shah
Microsoft MVP & Docker Captain
Dockerizing the AI Stack: What Actually Works in Production (And What Doesn't)

TL;DR: Based on real-world case studies and production deployments, successful AI containerization requires three things: multi-stage builds that slash image sizes from 10GB+ to under 500MB, proper GPU resource management, and security boundaries that treat AI agents as untrusted code. Here's the playbook from teams that got it right—and the expensive mistakes they learned from.


The $50,000 Lesson

In early 2024, a fintech startup deployed their LLM-powered customer service bot using a naive single-stage Dockerfile. Their image: 18.3 GB. Deployment time: 14 minutes. Infrastructure bill: $47,000/month on GPU instances that sat idle 70% of the time.

After refactoring to a proper multi-stage build and implementing resource controls, they cut deployment time to 90 seconds and costs to $8,200/month.

Here's what they—and dozens of other teams—learned about production AI containerization.


What Actually Works: The Production Checklist

✅ 1. Multi-Stage Builds: Your First Line of Defense

The biggest mistake AI developers make: putting everything in one stage. PyTorch, CUDA, build tools, Jupyter notebooks, and your code—all living together forever.

What works:

# STAGE 1: Builder — dependencies only
FROM python:3.11-slim AS builder

WORKDIR /build

# Install build dependencies (NOT in final image)
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    gcc \
    g++ \
    git \
    && rm -rf /var/lib/apt/lists/*

# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Install Python dependencies with cache
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir -r requirements.txt

# Download model files (cached aggressively)
RUN python -c "from transformers import AutoModel; AutoModel.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')"

# STAGE 2: Runtime — minimal, production-only
FROM python:3.11-slim AS runtime

WORKDIR /app

# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

# Copy venv from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Copy application code (small, changes frequently)
COPY src/ ./src/
COPY config/ ./config/

# Non-root user
RUN useradd -m -u 1000 appuser
USER appuser

EXPOSE 8000

CMD ["python", "-m", "src.api"]

The numbers:

Approach Image Size Build Time Security Surface
Single-stage "kitchen sink" 8-15 GB 20-40 min Massive
Multi-stage with cache 180-450 MB 2-5 min Minimal
Savings: 95-98% 80-90% Critical

Source: Production deployments showing 950MB → 180MB reductions

✅ 2. GPU Resource Management: Don't Let Models Run Wild

LLMs are memory-hungry. Without constraints, one container can consume an entire A100.

What works:

# docker-compose.yml for GPU workloads
services:
  llm-inference:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
        limits:
          cpus: '4'
          memory: 16G
    environment:
      - NVIDIA_VISIBLE_DEVICES=0
      - CUDA_MEMORY_FRACTION=0.8
      - PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512
    # Security: prevent container escape
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - SYS_NICE

Key insight: Use CUDA_VISIBLE_DEVICES and memory fractions to co-locate multiple inference containers on a single GPU. vLLM's PagedAttention enables up to 24x higher throughput than naive HuggingFace deployments.

✅ 3. Model Storage: The Hidden Bottleneck

Embedding 13B parameters in your image? Stop.

What works:

# Download at build time (cached, versioned)
ARG MODEL_NAME=sentence-transformers/all-MiniLM-L6-v2
ARG MODEL_REVISION=main

RUN --mount=type=cache,target=/models \
    python -c "
        from huggingface_hub import snapshot_download;
        snapshot_download('${MODEL_NAME}', revision='${MODEL_REVISION}', 
                         local_dir='/models/$(basename ${MODEL_NAME})',
                         local_dir_use_symlinks=False)
    " \
    && ln -s /models/$(basename ${MODEL_NAME}) /app/models/current

# Or: mount external storage at runtime
# docker run -v /shared/models:/models:ro ...

Trade-offs:

Strategy Pros Cons When To Use
Embed in image Single artifact, fast start Massive images, slow deploys Small models (<1GB)
Download at startup Small images Cold start latency Serverless/scale-to-zero
Mount at runtime Fast deploys, shared cache Requires shared storage Production default
Lazy loading Minimal startup time First inference slow Cost-sensitive batch

✅ 4. Security: AI Agents Run Untrusted Code

Docker's "3Cs Framework": Containment, Context, Credentials

What works:

# Minimal, rootless container
FROM gcr.io/distroless/python3-debian12

# Copy application from builder
COPY --from=builder --chown=nonroot:nonroot /app /app
USER nonroot

# Read-only filesystem
read_only: true
tmpfs:
  - /tmp:noexec,nosuid,size=100m

# No shell, no package manager, minimal attack surface

Critical security stats:

  • 95% of vulnerable components have fixes available (but aren't applied)
  • 80% of dependencies remain un-upgraded for over a year
  • CVE-2024-21626 ("Leaky Vessels") demonstrated container escape risks
  • 8+ critical RCE vulnerabilities in LangChain, LangFlow, n8n (2024-2025)

What Doesn't Work: Common Production Failures

❌ Mistake #1: Using :latest Tags

# Don't do this
FROM nvidia/cuda:latest

# Do this instead
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04

Why it fails: "Latest" changes. Your working deployment becomes a ticking time bomb.

❌ Mistake #2: Installing Everything in One Layer

# Don't do this
RUN apt-get update && apt-get install -y [50 packages] && pip install -r requirements.txt

# Do this
RUN apt-get update && apt-get install -y --no-install-recommends [runtime-only] \
    && rm -rf /var/lib/apt/lists/*
# ...separate layer for Python deps...
# ...separate layer for code...

❌ Mistake #3: Running as Root

# Don't do this (default)
USER root

# Do this
RUN useradd -m -u 1000 appuser
USER appuser

❌ Mistake #4: Ignoring Health Checks

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:8000/health').raise_for_status()" || exit 1

The Production-Ready Template

Here's a complete, battle-tested Dockerfile for LLM inference:

# ============================================
# Stage 1: Dependency Builder
# ============================================
FROM python:3.11-slim AS builder

WORKDIR /build

# Build dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    gcc \
    g++ \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Dependencies with pip cache
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# Pre-download models (optional)
ARG PRELOAD_MODELS=""
RUN if [ -n "$PRELOAD_MODELS" ]; then \
        python -c "from transformers import AutoModel, AutoTokenizer; \
                   [AutoModel.from_pretrained(m) for m in '$PRELOAD_MODELS'.split(',')]"; \
    fi

# ============================================
# Stage 2: Production Runtime
# ============================================
FROM python:3.11-slim AS runtime

WORKDIR /app

# Runtime deps only
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 \
    curl \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# Copy venv
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH" \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONFAULTHANDLER=1

# Application code
COPY --chown=appuser:appuser src/ ./src/

# Non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

EXPOSE 8000

CMD ["uvicorn", "src.api:app", "--host", "0.0.0.0", "--port", "8000"]

Build it:

docker build \
    --build-arg PRELOAD_MODELS="sentence-transformers/all-MiniLM-L6-v2" \
    --cache-from type=local,src=/tmp/.docker-cache \
    --target runtime \
    -t myllm:v1.0.0 \
    .

Key Takeaways

Practice Impact Priority
Multi-stage builds -90% image size, -80% build time Critical
GPU resource limits Prevents runaway costs Critical
Non-root users Blocks container escape Critical
External model storage Faster deployment cycles High
Health checks Reliable orchestration High
Pinned base images Reproducible builds High
Distroless/minimal images Reduced attack surface Medium

References

  1. Docker AI/ML Case Studies
  2. vLLM Production Guide
  3. Docker's 3Cs Security Framework
  4. Multi-stage Build Deep Dive

What's your biggest Docker + AI challenge? Drop a comment below—I've containerized models from edge devices to multi-GPU clusters, and I'm happy to share war stories.