Deploy Python Microservices with Docker Compose in 30 Minutes
Stop fighting deployment issues. Here's a production-ready Docker Compose setup for Python microservices that you can have running in 30 minutes.
Architecture Overview
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Nginx │────▶│ API Gateway │────▶│ User Service│
│ (Reverse │ │ (FastAPI) │ │ (FastAPI) │
│ Proxy) │ └──────────────┘ └─────────────┘
└─────────────┘ │ │
┌──────┴──────┐ ┌──────┴──────┐
│ Redis │ │ PostgreSQL │
│ (Cache) │ │ (Database) │
└─────────────┘ └─────────────┘
Step 1: Project Structure
myproject/
├── docker-compose.yml
├── .env
├── nginx/
│ └── nginx.conf
├── api-gateway/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── main.py
└── user-service/
├── Dockerfile
├── requirements.txt
└── main.py
Step 2: Docker Compose Configuration
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
depends_on:
- api-gateway
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
api-gateway:
build: ./api-gateway
environment:
- DATABASE_URL=postgresql://user:password@postgres:5432/mydb
- REDIS_URL=redis://redis:6379/0
- USER_SERVICE_URL=http://user-service:8001
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
user-service:
build: ./user-service
environment:
- DATABASE_URL=postgresql://user:password@postgres:5432/mydb
- REDIS_URL=redis://redis:6379/0
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Step 3: Nginx Configuration
events {
worker_connections 1024;
}
http {
upstream api_gateway {
server api-gateway:8000;
}
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 80;
server_name api.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://api_gateway;
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;
}
location /health {
proxy_pass http://api_gateway/health;
access_log off;
}
}
}
Step 4: API Gateway
# api-gateway/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import httpx
import os
app = FastAPI(title="API Gateway")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
USER_SERVICE = os.getenv("USER_SERVICE_URL", "http://user-service:8001")
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
async with httpx.AsyncClient() as client:
try:
resp = await client.get(f"{USER_SERVICE}/users/{user_id}", timeout=5.0)
return resp.json()
except httpx.TimeoutException:
raise HTTPException(503, "User service unavailable")
# api-gateway/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Step 5: User Service
# user-service/main.py
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
import os
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_async_engine(DATABASE_URL, pool_size=20, max_overflow=10)
SessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
app = FastAPI(title="User Service")
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
async with SessionLocal() as session:
# Query user from database
result = await session.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(404, "User not found")
return {"id": user.id, "name": user.name, "email": user.email}
Step 6: Environment Configuration
# .env
DATABASE_URL=postgresql://user:password@postgres:5432/mydb
REDIS_URL=redis://redis:6379/0
API_KEY=your-secret-key-here
Step 7: Launch
# Build and start all services
docker-compose up -d --build
# Check status
docker-compose ps
# View logs
docker-compose logs -f api-gateway
# Scale a service
docker-compose up -d --scale user-service=3
Production Checklist
- [ ] Use Docker secrets for sensitive data
- [ ] Set up log aggregation (ELK/Loki)
- [ ] Configure monitoring (Prometheus + Grafana)
- [ ] Set up CI/CD pipeline
- [ ] Add TLS certificates
- [ ] Configure backup for PostgreSQL
- [ ] Set resource limits for all services
- [ ] Enable Docker content trust
Common Issues and Fixes
"Connection refused" between services
- Services need to be on the same Docker network
- Use service names (not localhost) for inter-service communication
- Check that the service is actually running:
docker-compose ps
Memory issues on 4GB laptop
- Set resource limits in docker-compose.yml
- Use
--compatibilityflag for resource limits - Consider using Alpine-based images (smaller footprint)
Database connection pool exhaustion
- Set
pool_sizeandmax_overflowin SQLAlchemy - Close connections properly in finally blocks
- Monitor active connections:
SELECT count(*) FROM pg_stat_activity;
This setup gives you a production-ready microservices architecture that runs on a single machine and scales horizontally when you're ready.
Top comments (0)