Containers & Docker: Dockerfiles, Images, and Model Containerization
1489 words
7 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Containers & Docker: Dockerfiles, Images, and Model Containerization ## 🎯 Learning Objectives - Understand containerization and its benefits for ML deployment - Write optimized Dockerfiles for ML models - Manage images with registries and tags - Implement multi-stage builds for smaller images - Debug containerize...

Containers & Docker: Dockerfiles, Images, and Model Containerization
🎯 Learning Objectives
- Understand containerization and its benefits for ML deployment
- Write optimized Dockerfiles for ML models
- Manage images with registries and tags
- Implement multi-stage builds for smaller images
- Debug containerized ML applications
📋 Prerequisites
- Linux command line basics: Terminal, file system
- Python/ML basics: pip, dependencies
- MLOps Lifecycle (Week 1): Deployment stage
1. 📖 Core Content
1.1 Intuition: Why Containers for ML?
Think of containerization as shipping a whole kitchen (not just the recipe) when you want someone to cook your dish. With vanilla Python deployment:
- "Install Python 3.9, install scikit-learn 1.0, copy my script, run it"
- Problem: "Oh, you have Python 3.10, and sklearn 1.3 changed the API" With Docker:
- "Here's an image that has everything — run it anywhere"
- Python version, libraries, OS dependencies, system gubbins — everything packaged together. For ML models specifically, containers solve:
- Reproducibility: The exact same environment for training and serving
- Dependency hell: GPU drivers, CUDA, Python packages, system libraries
- Scalability: Containers are the unit of scaling in Kubernetes
- Isolation: Each model runs in its own environment
1.2 Docker Fundamentals
| Concept | Description | ML Analogy |
|---|---|---|
| Image | Immutable snapshot of a filesystem containing everything needed | A frozen model + dependencies |
| Container | A running instance of an image | The deployed model API |
| Dockerfile | Recipe for building an image | requirements.txt + setup.py + more |
| Registry | Storage for sharing images (DockerHub, ECR) | PyPI for Docker images |
| Volume | Persistent storage accessible by containers | Where the model weights live |
1.3 Writing Dockerfiles for ML
1.3.1 Basic ML Dockerfile
dockerfile# Start from a base image with Python 3.9 FROM python:3.9-slim # Set working directory WORKDIR /app # Copy requirements first (leverages Docker layer caching) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy the rest of the application COPY src/ src/ COPY model/ model/ # Expose the API port EXPOSE 8080 # Command to run the model server CMD ["uvicorn", "src.serve:app", "--host", "0.0.0.0", "--port", "8080"]
1.3.2 Multi-Stage Build (Smaller Images)
Multiple-stage builds separate build tools from runtime, producing much smaller images:
dockerfile# Stage 1: Build FROM python:3.9-slim AS builder WORKDIR /build COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Stage 2: Runtime (very small base) FROM python:3.9-slim AS runtime WORKDIR /app # Copy only installed packages from builder COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Copy application code COPY src/ src/ COPY model/ model/ EXPOSE 8080 CMD ["uvicorn", "src.serve:app", "--host", "0.0.0.0", "--port", "8080"]
Size comparison:
- Without multi-stage: ~1.2 GB (includes build tools, pip cache, compilers)
- With multi-stage: ~350 MB (runtime only)
1.3.3 GPU-Aware Dockerfile
dockerfileFROM nvidia/cuda:11.8-runtime-ubuntu22.04 # Install Python and pip RUN apt-get update && apt-get install -y python3 python3-pip # Install PyTorch with CUDA support RUN pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # Install other dependencies COPY requirements.txt . RUN pip3 install -r requirements.txt COPY src/ /app/ WORKDIR /app CMD ["python3", "serve.py"]
1.4 Docker Best Practices for ML
- Pin all versions:
scikit-learn==1.2.2, notscikit-learn>=1.2 - Use
.dockerignore: Exclude__pycache__/,.git/,data/,.env - Layer caching: Order Dockerfile from least to most frequently changing (base OS → system deps → pip packages → code → model weights)
- Health checks: Add
HEALTHCHECKfor production:dockerfileHEALTHCHECK --interval=30s --timeout=3s \ CMD curl -f http://localhost:8080/health || exit 1 - Non-root user: Don't run as root in production:
dockerfile
RUN useradd -m -u 1000 appuser USER appuser - Minimize layers: Combine
RUNcommands where possible
1.5 Worked Example: Containerizing a BERT Model
Step 1: Create the model server (serve.py)
python# runnable from fastapi import FastAPI, Request from transformers import pipeline import torch app = FastAPI() class ModelServer: def __init__(self): self.classifier = pipeline( "sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english", device=0 if torch.cuda.is_available() else -1 ) def predict(self, text: str) -> dict: result = self.classifier(text)[0] return {"label": result["label"], "score": result["score"]} model = ModelServer() @app.post("/predict") async def predict(request: Request): body = await request.json() return model.predict(body["text"]) @app.get("/health") async def health(): return {"status": "healthy"}
Step 2: Dockerfile
dockerfileFROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY serve.py . # Download model weights at build time (prevents download at startup) RUN python -c "from transformers import pipeline; pipeline('sentiment-analysis', model='distilbert-base-uncased-finetuned-sst-2-english')" EXPOSE 8000 CMD ["uvicorn", "serve:app", "--host", "0.0.0.0"]
Step 3: Build and run
bashdocker build -t sentiment-model:v1 . docker run -p 8000:8000 sentiment-model:v1
1.6 Edge Cases & Gotchas
- CUDA version mismatch: The CUDA version in the Docker image must match the host's driver. Check with
nvidia-smi→ CUDA version. - Model weights in images: Large models (5+ GB) should be mounted as volumes, not baked into images.
- Temp file cleanup: ML models can generate large temp files. Clean up between predictions.
- Timezones: Containers use UTC by default. Set
TZ=Asia/Kolkataif needed.
1.7 Why This Matters
Containerization is the foundation of modern ML deployment:
- Kubernetes runs containers — no container, no orchestration
- MLflow models can be packaged as Docker containers
- SageMaker, Vertex AI, Azure ML all use containers for model serving
- CI/CD pipelines build and test containers before deployment
2. 📐 Key Formulas / Concepts
| Concept | Description | Best Practice |
|---|---|---|
| Dockerfile | Recipe for building an image | Multi-stage builds for size |
| Image size | Affects deployment speed | Target < 1 GB for CPU models |
| Layer caching | Docker caches each instruction | Order by change frequency |
| Health check | Container liveness probe | Check model loading + API |
| Volume mounting | Attach external data | Use for large model weights |
3. ⚠️ Common Pitfalls
Pitfall 1: Outdated Base Images
Mistake: Using
python:3.9 (full image ~3.3 GB) or not updating base images.
Why: Full Python images contain compilers and build tools that aren't needed at runtime. Outdated images have security vulnerabilities.
Correct approach: Use python:3.9-slim (~120 MB) or python:3.9-alpine (~45 MB). Regularly rebuild images to get security patches.Pitfall 2: Hardcoding Secrets in Dockerfiles
Mistake: Including API keys, passwords, or model license keys in the Dockerfile.
Why: Anyone with access to the image can extract secrets. Images are often stored in registries accessible to multiple team members.
Correct approach: Use environment variables at runtime (
-e flag or Kubernetes secrets), not build-time ARGs for secrets.Pitfall 3: Not Handling GPU Memory Leaks
Mistake: Using GPU-accelerated models without memory management.
Why: PyTorch/TensorFlow can leak GPU memory when models make predictions, causing the container to crash after a few requests.
Correct approach:
pythonwith torch.no_grad(): result = model(inputs) torch.cuda.empty_cache()
Or use model-serving frameworks (TorchServe, Triton) that handle memory management.
4. 📝 Practice Questions
Q1: Your Docker image is 3.2 GB. The model is 1.5 GB (PyTorch), and the base image is 1.7 GB (CUDA 11.8 full). How can you reduce it?
Usenvidia/cuda:11.8-runtimeinstead ofnvidia/cuda:11.8-devel(runtime is ~800 MB vs 2.5 GB for devel) Usepython:3.9-slimas the base (120 MB) and manually install CUDA runtime Don't bake model weights into the image — download at container start or mount them:dockerfileVOLUME /app/models Use multi-stage build to exclude build tools Clean pip cache:pip install --no-cache-dir If using PyTorch, use the CPU-only version for CPU deploymentsEstimated savings: 3.2 GB → 1.5-2.0 GB (model weights) → optionally use volume mounts. Q2: Compare Docker for ML with virtual environments (conda, venv). When would you use each?
| Aspect | Docker | Virtual Environment |
|---|---|---|
| Isolation | OS-level (full filesystem) | Python-level (packages only) |
| GPU support | Yes (nvidia-docker) | Yes (CUDA toolkit) |
| System deps | Built-in (apt-get) | Must install manually |
| Reproducibility | Complete (pins OS, Python, libs) | Partial (only Python packages) |
| Image size | 100 MB - 5 GB | ~100 MB (weights stored separately) |
| Startup time | Seconds to minutes | Instant (if deps installed) |
| Use case | Production deployment | Development, experimentation |
Use Docker for production deployment, CI/CD, and reproducibility-critical scenarios. Use virtual environments for local development and quick experiments. Q3: Design a Docker strategy for training and serving a model that uses GPU for training but CPU for serving.dockerfile# Training Dockerfile (GPU-heavy) FROM nvidia/cuda:11.8-runtime-ubuntu22.04 AS trainer RUN pip3 install torch torchvision transformers COPY train.py . CMD ["python3", "train.py"] # Serving Dockerfile (CPU-only, smaller) FROM python:3.9-slim AS server RUN pip install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu COPY serve.py /app/ EXPOSE 8080 CMD ["uvicorn", "serve:app", "--host", "0.0.0.0"]Strategy:
- Build
trainerimage with GPU support- Run training, save model to cloud storage (S3/Blob)
- Build
serverimage with CPU-only PyTorch (much smaller)- At startup, download trained model from storage
- Serve on CPU
This gives the best of both worlds: fast GPU training and small CPU serving images (~500 MB vs ~3 GB).
5. 🔗 Cross-References
- Previous: Feature Stores (Week 4) — Feature computation pipelines
- Next: Orchestration: K8s & Kubeflow (Week 6) — Container orchestration
- Related: Model Serving (Week 8) — Serving containerized models
- External: Docker documentation (docs.docker.com) Join Discord PreviousFeature StoresNextOrchestration: K8s & Kubeflow