Docs

README

🐳 Docker and Deployment

📌 What You'll Learn

  • What Docker is and why it matters
  • Writing Dockerfiles for Python applications
  • Multi-stage builds for smaller images
  • Docker Compose for multi-container apps
  • CI/CD with GitHub Actions
  • Cloud deployment options

🔍 What is Docker?

Docker packages applications and dependencies into containers - lightweight, portable environments that run consistently anywhere.

Why Use Docker?

  1. Consistency - "Works on my machine" → "Works everywhere"
  2. Isolation - Each app runs in its own container
  3. Portability - Run on any machine with Docker
  4. Scalability - Easy to scale horizontally
  5. DevOps - Streamlined deployment pipeline

Key Concepts

TermDescription
ImageRead-only template for creating containers
ContainerRunning instance of an image
DockerfileInstructions to build an image
RegistryStorage for images (Docker Hub, ECR, etc.)
VolumePersistent data storage
NetworkCommunication between containers

📝 Writing Dockerfiles

Basic Python Dockerfile

# Use official Python image as base
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Copy requirements first (for better caching)
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose port (documentation only)
EXPOSE 8000

# Command to run the application
CMD ["python", "app.py"]

Best Practices Dockerfile

# Use specific version tag (not :latest)
FROM python:3.12-slim-bookworm

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# Create non-root user for security
RUN groupadd --gid 1000 appgroup && \
    useradd --uid 1000 --gid 1000 --shell /bin/bash appuser

# Set working directory
WORKDIR /app

# Install dependencies first (leverages cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY --chown=appuser:appgroup . .

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Multi-Stage Builds

Multi-stage builds create smaller, more secure images by separating build and runtime stages.

# Stage 1: Build stage
FROM python:3.12-slim as builder

WORKDIR /app

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

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

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Production stage
FROM python:3.12-slim as production

WORKDIR /app

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

# Create non-root user
RUN useradd --uid 1000 appuser
USER appuser

# Copy application code
COPY --chown=appuser . .

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

🔧 Docker Commands

Building Images

# Build image from Dockerfile
docker build -t myapp:1.0.0 .

# Build with different Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .

# Build with build arguments
docker build --build-arg VERSION=1.0.0 -t myapp .

# List images
docker images

# Remove image
docker rmi myapp:1.0.0

Running Containers

# Run container
docker run myapp:1.0.0

# Run with port mapping
docker run -p 8000:8000 myapp:1.0.0

# Run in background (detached)
docker run -d -p 8000:8000 --name my-container myapp:1.0.0

# Run with environment variables
docker run -e DATABASE_URL=postgres://... myapp:1.0.0

# Run with volume mount
docker run -v $(pwd)/data:/app/data myapp:1.0.0

# Run interactive shell
docker run -it myapp:1.0.0 /bin/bash

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Stop container
docker stop my-container

# Remove container
docker rm my-container

# View logs
docker logs my-container
docker logs -f my-container  # Follow logs

🐙 Docker Compose

Docker Compose manages multi-container applications.

Basic docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    ports:
      - '8000:8000'
    environment:
      - DATABASE_URL=postgres://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=myapp
    ports:
      - '5432:5432'

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    ports:
      - '6379:6379'

volumes:
  postgres_data:
  redis_data:

Production docker-compose.yml

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - '8000:8000'
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - SECRET_KEY=${SECRET_KEY}
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:8000/health']
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${DB_USER}']
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - web
    restart: unless-stopped

volumes:
  postgres_data:

Docker Compose Commands

# Start services
docker-compose up

# Start in background
docker-compose up -d

# Build and start
docker-compose up --build

# Stop services
docker-compose down

# Stop and remove volumes
docker-compose down -v

# View logs
docker-compose logs
docker-compose logs -f web  # Follow specific service

# Run command in service
docker-compose exec web python manage.py migrate

# Scale service
docker-compose up -d --scale web=3

🔄 CI/CD with GitHub Actions

Basic CI/CD Pipeline

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov

      - name: Run tests
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
        run: pytest --cov=app tests/

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            username/myapp:latest
            username/myapp:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /app
            docker-compose pull
            docker-compose up -d

☁️ Cloud Deployment

AWS ECS (Elastic Container Service)

# Push to ECR
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URL
docker tag myapp:latest $ECR_URL/myapp:latest
docker push $ECR_URL/myapp:latest

Google Cloud Run

# Build and deploy
gcloud builds submit --tag gcr.io/PROJECT_ID/myapp
gcloud run deploy myapp --image gcr.io/PROJECT_ID/myapp --platform managed

Railway/Render (Easy Deploys)

# railway.json or render.yaml
services:
  - type: web
    name: myapp
    runtime: docker
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: mydb
          property: connectionString

📋 Dockerfile Checklist

  • Use specific image tags (not :latest)
  • Set PYTHONDONTWRITEBYTECODE=1 and PYTHONUNBUFFERED=1
  • Use .dockerignore to exclude unnecessary files
  • Install dependencies before copying code (better caching)
  • Use multi-stage builds for smaller images
  • Run as non-root user
  • Add health checks
  • Use --no-cache-dir for pip
  • Minimize number of layers
  • Remove unnecessary packages after installation

.dockerignore Example

# .dockerignore
__pycache__
*.pyc
*.pyo
*.pyd
.git
.gitignore
.env
.venv
venv
*.md
*.txt
tests/
.pytest_cache
.coverage
htmlcov/
.mypy_cache
Dockerfile*
docker-compose*

🎯 Next Steps

After learning Docker and deployment, proceed to 29_debugging_profiling to learn how to debug and optimize your applications!

README - Python Tutorial | DeepML