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?
- •Consistency - "Works on my machine" → "Works everywhere"
- •Isolation - Each app runs in its own container
- •Portability - Run on any machine with Docker
- •Scalability - Easy to scale horizontally
- •DevOps - Streamlined deployment pipeline
Key Concepts
| Term | Description |
|---|---|
| Image | Read-only template for creating containers |
| Container | Running instance of an image |
| Dockerfile | Instructions to build an image |
| Registry | Storage for images (Docker Hub, ECR, etc.) |
| Volume | Persistent data storage |
| Network | Communication 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=1andPYTHONUNBUFFERED=1 - • Use
.dockerignoreto 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-dirfor 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!