Docker solves the “works on my machine” problem that plagues data science. When your model works in your local conda environment but crashes in production because of a library version mismatch, Docker is the fix. A Docker container packages your code, dependencies, and runtime into a single portable unit that runs identically everywhere — your laptop, a colleague’s machine, a cloud server, or a Kubernetes cluster. This guide teaches you everything a data scientist needs to know about Docker.
Core Docker Concepts
A Docker image is a read-only blueprint — like a class in Python. A container is a running instance of that image — like an object. Images are built from a Dockerfile, a text file of instructions. Images are stored in registries (Docker Hub, AWS ECR, GCR). Every container is isolated: its own filesystem, network, and processes, but sharing the host OS kernel, making containers much lighter than virtual machines.
Essential Docker Commands
# Pull an image from Docker Hub
docker pull python:3.11-slim
# Run a container interactively
docker run -it python:3.11-slim bash
# Run a Python script inside a container
docker run --rm -v $(pwd):/app python:3.11-slim python /app/train.py
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# List downloaded images
docker images
# Stop a running container
docker stop container_id_or_name
# Remove a container
docker rm container_name
# Remove an image
docker rmi image_name
# View container logs
docker logs container_name
docker logs -f container_name # follow (like tail -f)
# Execute a command in a running container
docker exec -it container_name bash
Writing a Dockerfile for ML
# Dockerfile for a scikit-learn model API
# Base image — slim = smaller, no extras
FROM python:3.11-slim
# Set working directory inside the container
WORKDIR /app
# Copy requirements first (Docker caches this layer)
# Only invalidated when requirements.txt changes
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application
COPY . .
# Expose the port the API listens on
EXPOSE 8000
# Default command to run when container starts
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]
# requirements.txt
fastapi==0.111.0
uvicorn==0.30.0
scikit-learn==1.5.0
pandas==2.2.2
numpy==1.26.4
joblib==1.4.2
# Build the image
docker build -t ml-api:v1 .
# Run the container, mapping host port 8000 to container port 8000
docker run -p 8000:8000 ml-api:v1
# Test it
curl http://localhost:8000/predict
Multi-Stage Builds (Smaller Images)
# Stage 1: Build — includes compilers, dev tools
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Stage 2: Runtime — lean production image
FROM python:3.11-slim AS runtime
WORKDIR /app
# Copy only the installed packages from build stage
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]
# Result: image is 60-70% smaller than single-stage
Docker Compose for ML Pipelines
# docker-compose.yml — full ML pipeline stack
version: '3.9'
services:
# Model training service
trainer:
build: ./trainer
volumes:
- ./data:/app/data
- ./models:/app/models
environment:
- MLFLOW_TRACKING_URI=http://mlflow:5000
depends_on:
- mlflow
# FastAPI model serving
api:
build: ./api
ports:
- "8000:8000"
volumes:
- ./models:/app/models
environment:
- MODEL_PATH=/app/models/best_model.pkl
depends_on:
- trainer
# MLflow tracking server
mlflow:
image: ghcr.io/mlflow/mlflow:v2.13.0
ports:
- "5000:5000"
volumes:
- ./mlruns:/mlruns
command: mlflow server --host 0.0.0.0
# PostgreSQL for feature store
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: features
POSTGRES_USER: ds
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
# Run the whole stack
docker compose up -d
# Run only the trainer
docker compose run trainer python train.py
# View all logs
docker compose logs -f
# Stop everything
docker compose down
# Stop and remove volumes (resets database)
docker compose down -v
Best Practices for ML Dockerfiles
# ✅ Pin base image versions (never use :latest in production)
FROM python:3.11.9-slim
# ✅ Use .dockerignore to exclude unnecessary files
# .dockerignore:
# .git
# __pycache__
# *.pyc
# data/raw/
# notebooks/
# .env
# tests/
# ✅ Order layers from least to most frequently changed
# (requirements before source code, so cache is reused on code changes)
# ✅ Set environment variables for reproducibility
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 PIP_NO_CACHE_DIR=1
# ✅ Run as non-root user for security
RUN adduser --disabled-password --gecos '' appuser
USER appuser
# ✅ Add health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
Conclusion
Docker is now a required skill for any data scientist who deploys models. It eliminates environment inconsistency, makes your work reproducible by anyone with Docker installed, and is the foundation of modern ML deployment on Kubernetes and cloud platforms. Start by Dockerising your next FastAPI model serving project — once you see it deploy identically on your laptop and a cloud VM in one command, you will never go back to “just use my conda environment.”



