Docker to Cloudflare Migration
Replace your entire Docker Compose stack with Cloudflare services. No containers, no orchestration, no infrastructure to manage — just Python code deployed globally in 30 seconds.
Your Typical Docker Compose Stack
Here's what a typical Python application looks like in Docker — and why each service is no longer needed:
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine # ❌ Not needed - Workers serve static assets
volumes:
- ./static:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/nginx.conf
app:
build: .
image: myapp:latest # ❌ Not needed - Deploy Python directly
command: gunicorn app:app --workers 4 # ❌ No WSGI server needed
environment:
- DATABASE_URL=postgresql://...
redis:
image: redis:alpine # ❌ Use Workers KV instead
postgres:
image: postgres:14 # ❌ Use D1 or Hyperdrive
volumes:
- pgdata:/var/lib/postgresql/data
celery:
build: .
command: celery -A tasks worker # ❌ Use Queues instead
celery-beat:
build: .
command: celery -A tasks beat # ❌ Use Cron Triggers
prometheus:
image: prom/prometheus # ❌ Use Analytics Engine
grafana:
image: grafana/grafana # ❌ Built-in analytics The Cloudflare Equivalent
# main.py - Your entire "stack" in one file
from js import Response, env
from fastapi import FastAPI
import asyncio
app = FastAPI()
# Serve static assets directly
@app.get("/static/{path:path}")
async def static(path: str):
asset = await env.ASSETS.get(path)
return Response.new(asset.body, headers={
"Content-Type": asset.httpMetadata.contentType
})
# Your app logic
@app.post("/api/process")
async def process(data: dict):
# Cache in KV (replaces Redis)
await env.KV.put(f"cache:{data['id']}", data)
# Queue background work (replaces Celery)
await env.QUEUE.send(data)
# Store in D1 (replaces Postgres)
await env.DB.prepare(
"INSERT INTO items (data) VALUES (?)"
).bind(data).run()
return {"status": "ok"} Service-by-Service Mapping
| Docker Service | Purpose | Cloudflare Replacement | What Changes |
|---|---|---|---|
| nginx | Reverse proxy, static files, SSL | Workers built-in | No config files, automatic SSL, global CDN |
| gunicorn/uwsgi | WSGI server | Workers runtime | No process management, auto-scaling |
| redis | Caching, sessions, queues | Workers KV | Globally distributed, no memory limits |
| postgres/mysql | Primary database | D1 or Hyperdrive | Serverless or accelerated existing DB |
| celery workers | Background tasks | Queues | No broker needed, automatic retries |
| celery beat | Scheduled tasks | Cron Triggers | Simple cron syntax, guaranteed execution |
| elasticsearch | Full-text search | R2 + Workers AI | Use embeddings for semantic search |
| rabbitmq | Message broker | Queues | Direct producer-consumer, no broker |
| memcached | In-memory cache | Workers KV | Persistent, globally distributed |
| haproxy | Load balancer | Cloudflare LB | Automatic, no configuration |
| prometheus | Metrics collection | Analytics Engine | No scraping, unlimited cardinality |
| grafana | Visualization | Cloudflare Dashboard | Built-in analytics, API access |
Volume Mounts Become Bindings
Docker Volumes
volumes:
- ./static:/app/static # Static files
- ./uploads:/app/uploads # User uploads
- pgdata:/var/lib/postgresql/data # Database
- ./logs:/app/logs # Log files Cloudflare Bindings
# wrangler.toml
[[kv_namespaces]]
binding = "STATIC" # Replaces static file volume
[[r2_buckets]]
binding = "UPLOADS" # Replaces uploads volume
[[d1_databases]]
binding = "DB" # Replaces database volume
# Logs automatically available via wrangler tail Environment Variables & Secrets
Docker Approach
# docker-compose.yml
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
- SECRET_KEY=${SECRET_KEY}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} Cloudflare Approach
# Set secrets (encrypted at rest)
wrangler secret put DATABASE_URL
wrangler secret put API_KEY
# Access in code
async def on_fetch(request):
db_url = env.DATABASE_URL # Securely injected Development Workflow Comparison
Docker Workflow
# Local development
docker-compose up -d
docker-compose logs -f app
docker-compose exec app pytest
docker-compose down
# Build and push
docker build -t myapp:latest .
docker push registry.com/myapp:latest
# Deploy (still need k8s, ECS, etc.)
kubectl apply -f k8s/ Cloudflare Workflow
# Install wrangler (once)
bun install -g wrangler
# Local development
wrangler dev main.py # Hot reload included
# Run tests
python -m pytest # Just regular Python
# Deploy to production
wrangler deploy # 30 seconds to global deployment Migration Example: FastAPI + Gunicorn + Nginx
Before
# app.py
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
# Database query
return {"user_id": user_id}
# + Dockerfile, nginx.conf, gunicorn config, k8s manifests... After
# main.py
from fastapi import FastAPI
from js import Response
app = FastAPI()
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
result = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(user_id).first()
return {"user": result}
# That's it. Deploy with: wrangler deploy