Chapter 9: Hosting and Deployment Options

Choosing the right hosting platform impacts cost, performance, and maintenance burden. This chapter explores various deployment strategies.

Deployment Architecture Types

Serverless

Functions-as-a-Service approach:

Best for: Variable traffic, low-maintenance requirements

Platform Free Tier Cold Start Database
Vercel 100K req/mo ~200ms External
Netlify 125K req/mo ~200ms External
AWS Lambda 1M req/mo ~300ms DynamoDB/RDS
Cloudflare Workers 100K req/day <1ms D1/KV

Container-Based

Docker deployments:

Best for: Consistent environments, full control

Platform Free Tier Min Cost Features
Fly.io 3 VMs $0 Global, auto-scale
Railway $5 credit ~$5/mo Easy deploys
Render Static only $7/mo Managed
Google Cloud Run 2M req/mo ~$0 Auto-scale

Traditional VPS

Self-managed servers:

Best for: Maximum control, predictable costs

Provider Min Cost RAM Location
Hetzner €4/mo 2GB EU
DigitalOcean $6/mo 1GB Global
Linode $5/mo 1GB Global
Vultr $5/mo 1GB Global

Vercel Deployment

Project Setup

comment-api/
├── api/
│   └── comments.py
├── requirements.txt
└── vercel.json

Configuration

// vercel.json
{
    "version": 2,
    "builds": [
        {
            "src": "api/comments.py",
            "use": "@vercel/python"
        }
    ],
    "routes": [
        {
            "src": "/api/(.*)",
            "dest": "/api/comments.py"
        }
    ],
    "env": {
        "DATABASE_URL": "@database-url",
        "ALLOWED_ORIGINS": "@allowed-origins"
    }
}

Deployment

# Install Vercel CLI
npm i -g vercel

# Login and deploy
vercel login
vercel --prod

Cloudflare Workers Deployment

Worker Code

// src/index.js
import { Router } from 'itty-router';

const router = Router();

router.get('/api/comments', async (request, env) => {
    const url = new URL(request.url);
    const pageId = url.searchParams.get('page_id');
    
    const { results } = await env.DB.prepare(
        'SELECT * FROM comments WHERE page_id = ? AND status = ? ORDER BY created_at DESC'
    ).bind(pageId, 'approved').all();
    
    return Response.json({ data: results });
});

router.post('/api/comments', async (request, env) => {
    const body = await request.json();
    
    const result = await env.DB.prepare(
        'INSERT INTO comments (page_id, author_name, content, status) VALUES (?, ?, ?, ?)'
    ).bind(body.page_id, body.author_name, body.content, 'pending').run();
    
    return Response.json({ success: true, id: result.lastRowId }, { status: 201 });
});

export default {
    fetch: (request, env) => router.handle(request, env)
};

Wrangler Configuration

# wrangler.toml
name = "comment-api"
main = "src/index.js"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "comments"
database_id = "your-database-id"

[vars]
ALLOWED_ORIGIN = "https://yourdomain.com"

Deploy

# Install wrangler
npm install -g wrangler

# Login
wrangler login

# Create D1 database
wrangler d1 create comments

# Deploy
wrangler deploy

Docker Deployment

Dockerfile

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

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

# Copy application
COPY app/ ./app/

# Create non-root user
RUN useradd -m appuser && chown -R appuser /app
USER appuser

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

Docker Compose for Development

# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/comments
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: comments
      POSTGRES_PASSWORD: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

Fly.io Deployment

Configuration

# fly.toml
app = "my-comment-api"
primary_region = "cdg"

[build]
  dockerfile = "Dockerfile"

[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

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

  [[services.ports]]
    port = 80
    handlers = ["http"]

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

[env]
  ENVIRONMENT = "production"

Deploy Commands

# Install flyctl
curl -L https://fly.io/install.sh | sh

# Login and launch
fly auth login
fly launch

# Create PostgreSQL
fly postgres create

# Attach database
fly postgres attach my-comment-api-db

# Deploy
fly deploy

# Scale to multiple regions
fly scale count 2 --region cdg,iad

Railway Deployment

railway.json

{
    "$schema": "https://railway.app/railway.schema.json",
    "build": {
        "builder": "DOCKERFILE",
        "dockerfilePath": "Dockerfile"
    },
    "deploy": {
        "numReplicas": 1,
        "healthcheckPath": "/health",
        "restartPolicyType": "ON_FAILURE"
    }
}

Deploy

# Install Railway CLI
npm install -g @railway/cli

# Login
railway login

# Initialize project
railway init

# Add PostgreSQL
railway add -p postgresql

# Deploy
railway up

Database Hosting

Supabase (PostgreSQL)

Free tier: 500MB storage, 2GB bandwidth

# Connection
DATABASE_URL = "postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres"

PlanetScale (MySQL)

Free tier: 5GB storage, 1 billion row reads

# Connection with SSL
DATABASE_URL = "mysql://[user]:[password]@[host]/[database]?ssl=true"

Turso (SQLite at the edge)

Free tier: 8GB storage, 9 databases

import libsql_experimental as libsql

conn = libsql.connect("your-db.turso.io", auth_token="your-token")

Neon (Serverless PostgreSQL)

Free tier: 512MB, auto-suspend

DATABASE_URL = "postgresql://[user]:[password]@[endpoint].neon.tech/[database]?sslmode=require"

Redis Hosting

Upstash

Free tier: 10K commands/day

import redis

r = redis.from_url("rediss://default:[password]@[endpoint].upstash.io:6379")

Redis Cloud

Free tier: 30MB

r = redis.Redis(
    host='[endpoint].redis.cache.windows.net',
    port=6380,
    password='[password]',
    ssl=True
)

Production Checklist

Environment Variables

# Required
DATABASE_URL=postgresql://...
SECRET_KEY=your-super-secret-key-at-least-32-chars

# Optional
REDIS_URL=redis://...
ALLOWED_ORIGINS=https://yourdomain.com
AKISMET_API_KEY=...
TURNSTILE_SECRET=...

# Monitoring
SENTRY_DSN=https://...

Health Check Endpoint

@app.get("/health")
async def health_check():
    try:
        # Check database
        await db.execute(text("SELECT 1"))
        db_status = "ok"
    except Exception:
        db_status = "error"
    
    try:
        # Check Redis
        await redis.ping()
        redis_status = "ok"
    except Exception:
        redis_status = "error"
    
    status = "healthy" if db_status == "ok" and redis_status == "ok" else "degraded"
    
    return {
        "status": status,
        "database": db_status,
        "cache": redis_status,
        "timestamp": datetime.utcnow().isoformat()
    }

Logging Configuration

import logging
import json

class JSONFormatter(logging.Formatter):
    def format(self, record):
        return json.dumps({
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName
        })

logging.basicConfig(
    level=logging.INFO,
    handlers=[logging.StreamHandler()],
)
logging.getLogger().handlers[0].setFormatter(JSONFormatter())

CI/CD Pipeline

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # Deploy to Fly.io
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: $

Chapter Summary

Platform Best For Cost Complexity
Vercel Quick start Free-$20 Low
Cloudflare Workers Global perf Free-$5 Medium
Fly.io Full control Free-$10 Medium
VPS Maximum control $5-20 High

Recommendation for starting: Vercel + Supabase (both have generous free tiers).


Navigation: