Module 09 Intermediate 20 min read

Containerization with Docker

Package your ML API into a Docker image — Dockerfile best practices, multi-service Compose, and pushing to Docker Hub and AWS ECR.

Updated 2025 · Edit on GitHub

Why Docker for ML?

ML models depend on a precise combination of Python version, library versions (scikit-learn 1.3 vs 1.4 can differ in serialisation format), and system libraries. Docker packages your code, model, and all dependencies into a container image — a single artefact that runs identically on any machine, in any cloud.

ImageA read-only template defining the filesystem: base OS, libraries, your code. Built once, reused anywhere.
ContainerA running instance of an image. Isolated from the host. Starts in milliseconds. Stateless by default.
DockerfileA recipe for building an image. Step-by-step instructions: start from base image → install deps → copy code → set entrypoint.
RegistryA repository of images. Docker Hub (public), AWS ECR, GCP Artifact Registry (private). Push/pull like git.

Writing the Dockerfile

Python
# Dockerfile

# ── Base image: slim Python (removes docs/tests from full image, saves ~200MB)
FROM python:3.11-slim

# ── Set working directory inside container
WORKDIR /app

# ── Install system dependencies first (changes rarely → cached layer)
RUN apt-get update && apt-get install -y --no-install-recommends     curl  && rm -rf /var/lib/apt/lists/*

# ── Install Python dependencies (copy requirements first for better caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# ── Copy application code and model
COPY app.py .
COPY models/ ./models/

# ── Create non-root user (security best practice)
RUN useradd -m appuser && chown -R appuser /app
USER appuser

# ── Expose the port FastAPI listens on
EXPOSE 8000

# ── Health check: Docker will restart container if this fails
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3   CMD curl -f http://localhost:8000/health || exit 1

# ── Run command
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
Python
# requirements.txt
fastapi==0.111.0
uvicorn[standard]==0.29.0
pydantic==2.7.1
scikit-learn==1.4.2
joblib==1.4.0
numpy==1.26.4

Building and Running

Bash
# ── Build the image
docker build -t zerotoml-api:1.0.0 .
# -t: tag (name:version)
# .  : build context (current directory)

# ── List images
docker images | grep zerotoml

# ── Run locally
docker run -d   --name ml-api   -p 8000:8000   zerotoml-api:1.0.0
# -d: detached (background)
# -p HOST_PORT:CONTAINER_PORT

# ── Test it
curl http://localhost:8000/health
# {"status":"ok","model_loaded":true}

# ── View logs
docker logs ml-api --follow

# ── Stop and remove
docker stop ml-api && docker rm ml-api

Docker Compose — Multi-Service Setup

Python
# docker-compose.yml
# Orchestrates the ML API + Redis cache + monitoring together

services:
  api:
    build: .
    image: zerotoml-api:1.0.0
    ports:
      - "8000:8000"
    environment:
      - ENV=production
      - MODEL_PATH=/app/models/v1/pipeline.joblib
    volumes:
      - ./models:/app/models:ro    # mount models dir as read-only
    depends_on:
      - redis
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  # Optional: monitoring with Prometheus
  # prometheus:
  #   image: prom/prometheus
Bash
# Start all services
docker compose up -d

# Scale API to 3 replicas
docker compose up -d --scale api=3

# View running services
docker compose ps

# Teardown
docker compose down

Pushing to a Registry

Bash
# ── Docker Hub
docker login
docker tag zerotoml-api:1.0.0 Muhammad-waqas1/zerotoml-api:1.0.0
docker push Muhammad-waqas1/zerotoml-api:1.0.0

# ── AWS ECR
aws ecr create-repository --repository-name zerotoml-api
aws ecr get-login-password --region us-east-1   | docker login --username AWS --password-stdin     123456789.dkr.ecr.us-east-1.amazonaws.com
docker tag  zerotoml-api:1.0.0 123456789.dkr.ecr.us-east-1.amazonaws.com/zerotoml-api:1.0.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/zerotoml-api:1.0.0

Summary

  • Docker solves the "it works on my machine" problem by packaging code + dependencies into a portable image.
  • Layer your Dockerfile: system deps → Python deps → app code. Rarely-changed layers are cached and re-used.
  • Always run as a non-root user. Add HEALTHCHECK for production reliability.
  • Docker Compose orchestrates multi-container setups (API + DB + cache) locally.
  • Push to Docker Hub, AWS ECR, or GCP Artifact Registry for cloud deployment.