Deployment Strategies (Docker, Kubernetes, Cloud) · learncode.live

Deployment Architecture Overview

┌─────────┐     ┌──────────┐     ┌───────────┐     ┌──────────┐
│  Client  │────▶│  Nginx   │────▶│  FastAPI  │────▶│ Database │
│  (CDN)   │     │ (Reverse │     │ (Gunicorn │     │ (Postgres│
│          │     │  Proxy)  │     │ +Uvicorn) │     │ /Redis)  │
└─────────┘     └──────────┘     └───────────┘     └──────────┘
                        │
                        ▼
                   Static Files
                   (CDN / S3)

Dockerfile for FastAPI

# Dockerfile
FROM python:3.12-slim AS builder

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# ── Production Stage ──
FROM python:3.12-slim

WORKDIR /app

# Create non-root user
RUN groupadd -r fastapi && useradd -r -g fastapi -d /app -s /sbin/nologin fastapi

COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

EXPOSE 8000

USER fastapi

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

Multi-stage Build Benefits

StagePurpose
builderInstall dependencies (cached by Docker layer)
productionMinimal runtime image - only code + deps
ResultSmaller image, fewer CVEs, faster pull times

Optimize with .dockerignore

__pycache__/
*.pyc
.venv/
.env
.git/
.gitignore
*.md
tests/
Dockerfile
docker-compose.yml

Build and Run

docker build -t fastapi-app .
docker run -d --name fastapi -p 8000:8000 fastapi-app

docker-compose for Local & Production

# docker-compose.yml
version: "3.9"

services:
  app:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    env_file:
      - .env
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./static:/usr/share/nginx/html/static:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

volumes:
  pgdata:
  redisdata:

Nginx Reverse Proxy Configuration

# nginx.conf
upstream fastapi_backend {
    server app:8000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";

    # Static files (served directly by Nginx)
    location /static/ {
        root /usr/share/nginx/html;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # API proxy
    location / {
        proxy_pass http://fastapi_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}

Kubernetes Deployment

Deployment Manifest

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-app
  labels:
    app: fastapi
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: fastapi
  template:
    metadata:
      labels:
        app: fastapi
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: app
        image: ghcr.io/myorg/fastapi-app:latest
        ports:
        - containerPort: 8000
        envFrom:
        - configMapRef:
            name: fastapi-config
        - secretRef:
            name: fastapi-secrets
        resources:
          requests:
            cpu: 250m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 10
---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: fastapi-service
spec:
  type: ClusterIP
  selector:
    app: fastapi
  ports:
  - port: 80
    targetPort: 8000
---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: fastapi-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: fastapi-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: fastapi-service
            port:
              number: 80

Deploy to Cluster

kubectl apply -f k8s/
kubectl rollout status deployment/fastapi-app

Cloud Platforms

Railway

# railway.json
{
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "Dockerfile"
  },
  "deploy": {
    "restartPolicyType": "ON_FAILURE",
    "healthcheckPath": "/health"
  }
}

Railway auto-detects FastAPI. Set the start command in the dashboard or via Procfile:

web: uvicorn app.main:app --host 0.0.0.0 --port $PORT

Render

Create a render.yaml for infrastructure-as-code:

# render.yaml
services:
  - type: web
    name: fastapi-app
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: fastapi-db
          property: connectionString
      - key: REDIS_URL
        fromService:
          name: fastapi-redis
          type: redis
          property: connectionString

databases:
  - name: fastapi-db
    plan: free

redis:
  - name: fastapi-redis
    plan: free

Fly.io

# fly.toml
app = "fastapi-app"

kill_signal = "SIGTERM"
kill_timeout = 5

[env]
  PORT = "8080"

[[services]]
  protocol = "tcp"
  internal_port = 8080

  [[services.ports]]
    port = 80
    handlers = ["http"]
  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

  [[services.http_checks]]
    path = "/health"
    interval = "30s"
    timeout = "10s"
fly launch
fly deploy

Platform Comparison

FeatureRailwayRenderFly.ioK8s (DIY)
Setup complexityLowLowMediumHigh
Auto-scaling
Free tier
Custom domains
Docker support
Managed DB✅ (Postgres)✅ (Postgres/Redis)✅ (Postgres)
Multi-region
Container orchestration

Secrets Management

Never hardcode secrets in Dockerfiles or source code:

# Docker secrets (Swarm)
echo "my_secret_key" | docker secret create db_password -

# Kubernetes secrets
kubectl create secret generic fastapi-secrets \
  --from-literal=secret_key=abc123 \
  --from-literal=database_url=postgresql://...

# Fly.io secrets
fly secrets set DATABASE_URL=postgresql://...

Access in FastAPI via environment variables (see Environment Variables & Config Management).

CI/CD Pipeline (High-Level)

# .github/workflows/deploy.yml (abbreviated)
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t app .
      - run: docker push ghcr.io/myorg/app:latest
      - run: kubectl set image deployment/fastapi-app app=ghcr.io/myorg/app:latest

See CI/CD Pipelines for the full pipeline.

Checklist Before Going Live

  • Health check endpoint (/health) responds 200
  • Database connection pool limits configured
  • CORS middleware allows only trusted origins
  • Rate limiting (slowapi or Nginx limit_req)
  • SSL/TLS via Let’s Encrypt or cloud provider
  • Logging to stdout/stderr (not files)
  • Environment variables injected, not hardcoded
  • .env files excluded from git
  • Non-root user in Dockerfile
  • Resource limits set (CPU/memory)

Key Takeaways

  • Use a multi-stage Dockerfile to produce minimal, secure images - separate build from runtime
  • Deploy with docker-compose for simple stacks, Kubernetes for orchestration, and cloud platforms (Railway/Render/Fly.io) for zero-ops
  • Place Nginx as a reverse proxy in front of FastAPI to handle SSL, static files, and security headers
  • Choose a platform based on your operational complexity tolerance - Railway/Render for simplicity, K8s for control
  • Never hardcode secrets - use Docker secrets, K8s Secrets, or platform-specific secret managers
Courses