Learn to containerize FastAPI applications with Docker Compose, integrate PostgreSQL and Redis services, configure Nginx reverse proxy with SSL, and implement production monitoring for scalable microservices deployment.
Prerequisites
- Basic Docker knowledge
- Understanding of Python web applications
- Familiarity with reverse proxies
- Basic SQL database concepts
What this solves
FastAPI applications require proper containerization and orchestration for production deployment. This tutorial shows you how to create a complete Docker Compose stack with FastAPI, PostgreSQL database, Redis cache, and Nginx reverse proxy with SSL certificates for a production-ready microservices architecture.
Step-by-step installation
Install Docker and Docker Compose
First, install Docker Engine and Docker Compose on your system.
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER
newgrp docker
sudo dnf update -y
sudo dnf install -y dnf-utils
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker
Create project structure
Set up the directory structure for your FastAPI application with all necessary files.
mkdir fastapi-docker && cd fastapi-docker
mkdir app nginx
touch docker-compose.yml .env
touch app/main.py app/requirements.txt app/Dockerfile
touch nginx/nginx.conf
Create FastAPI application
Build a sample FastAPI application with database and Redis connections.
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from datetime import datetime
import redis
import os
import json
from typing import Optional
app = FastAPI(title="FastAPI Docker Demo", version="1.0.0")
# Database configuration
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://fastapi:password@postgres:5432/fastapi_db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Redis configuration
redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
decode_responses=True
)
# Database model
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/")
async def root():
return {"message": "FastAPI with Docker Compose", "status": "healthy"}
@app.get("/health")
async def health_check():
# Test database connection
try:
db = next(get_db())
db.execute("SELECT 1")
db_status = "healthy"
except Exception as e:
db_status = f"unhealthy: {str(e)}"
# Test Redis connection
try:
redis_client.ping()
redis_status = "healthy"
except Exception as e:
redis_status = f"unhealthy: {str(e)}"
return {
"status": "healthy",
"database": db_status,
"redis": redis_status,
"timestamp": datetime.utcnow().isoformat()
}
@app.post("/items")
async def create_item(name: str, description: str, db: Session = Depends(get_db)):
item = Item(name=name, description=description)
db.add(item)
db.commit()
db.refresh(item)
# Cache the item
redis_client.setex(
f"item:{item.id}",
3600,
json.dumps({
"id": item.id,
"name": item.name,
"description": item.description,
"created_at": item.created_at.isoformat()
})
)
return {"id": item.id, "name": item.name, "description": item.description}
@app.get("/items/{item_id}")
async def get_item(item_id: int, db: Session = Depends(get_db)):
# Try cache first
cached_item = redis_client.get(f"item:{item_id}")
if cached_item:
return {"source": "cache", "item": json.loads(cached_item)}
# Fallback to database
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
item_data = {
"id": item.id,
"name": item.name,
"description": item.description,
"created_at": item.created_at.isoformat()
}
# Cache for future requests
redis_client.setex(f"item:{item_id}", 3600, json.dumps(item_data))
return {"source": "database", "item": item_data}
Define Python dependencies
Create requirements file with all necessary packages for the FastAPI application.
fastapi==0.104.1
uvicorn[standard]==0.24.0
psycopg2-binary==2.9.9
sqlalchemy==2.0.23
redis==5.0.1
python-multipart==0.0.6
Create FastAPI Dockerfile
Build a production-optimized Docker image for the FastAPI application.
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/home/appuser/.local/bin:${PATH}"
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Set work directory
WORKDIR /app
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Change ownership to non-root user
RUN chown -R appuser:appuser /app
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8000/health', timeout=10)"
# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Configure environment variables
Set up environment variables for database credentials and application configuration.
# Database configuration
POSTGRES_DB=fastapi_db
POSTGRES_USER=fastapi
POSTGRES_PASSWORD=secure_password_123
DATABASE_URL=postgresql://fastapi:secure_password_123@postgres:5432/fastapi_db
# Redis configuration
REDIS_HOST=redis
REDIS_PORT=6379
# Application configuration
FASTAPI_ENV=production
WORKERS=4
Configure Nginx reverse proxy
Set up Nginx as a reverse proxy with SSL support and security headers.
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Upstream backend
upstream fastapi_backend {
least_conn;
server app:8000 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name example.com www.example.com;
# Health check endpoint
location /nginx-health {
access_log off;
return 200 "healthy";
add_header Content-Type text/plain;
}
# API endpoints with rate limiting
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fastapi_backend;
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;
# Timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# All other requests
location / {
proxy_pass http://fastapi_backend;
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;
# Timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
}
Create Docker Compose configuration
Define the complete stack with FastAPI, PostgreSQL, Redis, and Nginx services.
version: '3.8'
services:
app:
build: ./app
container_name: fastapi-app
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
- postgres
- redis
networks:
- app-network
healthcheck:
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health', timeout=10)"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
postgres:
image: postgres:16-alpine
container_name: fastapi-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
container_name: fastapi-redis
restart: unless-stopped
command: redis-server --appendonly yes --requirepass redis_password_123
volumes:
- redis_data:/data
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis_password_123", "ping"]
interval: 30s
timeout: 10s
retries: 3
nginx:
image: nginx:1.25-alpine
container_name: fastapi-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- app-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/nginx-health"]
interval: 30s
timeout: 10s
retries: 3
networks:
app-network:
driver: bridge
volumes:
postgres_data:
driver: local
redis_data:
driver: local
Deploy the application stack
Start all services using Docker Compose and verify they're running correctly.
docker compose up -d
docker compose ps
docker compose logs -f
Configure production resource limits
Add resource constraints to prevent any single service from overwhelming the system. This builds on container resource management techniques.
version: '3.8'
services:
app:
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
postgres:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
nginx:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Set up SSL certificates
Generate SSL certificates for HTTPS support in production environments.
mkdir -p nginx/ssl
# For development - self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout nginx/ssl/server.key \
-out nginx/ssl/server.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
# Set proper permissions
chmod 600 nginx/ssl/server.key
chmod 644 nginx/ssl/server.crt
Update Nginx for SSL
Add SSL configuration to the Nginx reverse proxy for secure connections.
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Upstream backend
upstream fastapi_backend {
least_conn;
server app:8000 max_fails=3 fail_timeout=30s;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
# API endpoints
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fastapi_backend;
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;
}
# All other requests
location / {
proxy_pass http://fastapi_backend;
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;
}
}
}
Add monitoring and health checks
Implement comprehensive monitoring for all services in the stack.
version: '3.8'
services:
prometheus:
image: prom/prometheus:v2.45.0
container_name: fastapi-prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
networks:
- app-network
grafana:
image: grafana/grafana:10.1.0
container_name: fastapi-grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin_password_123
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
networks:
- app-network
volumes:
prometheus_data:
driver: local
grafana_data:
driver: local
Configure Prometheus monitoring
Set up Prometheus to collect metrics from all services in the stack.
mkdir -p monitoring/grafana/{dashboards,provisioning}
touch monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- joAutomated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
PROJECT_NAME="fastapi-docker"
INSTALL_DIR="/opt/$PROJECT_NAME"
DOMAIN="${1:-localhost}"
usage() {
echo "Usage: $0 [domain_name]"
echo "Example: $0 api.example.com"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
cleanup() {
if [ -d "$INSTALL_DIR" ]; then
warn "Cleaning up installation directory"
rm -rf "$INSTALL_DIR"
fi
}
trap cleanup ERR
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
if ! command -v curl &> /dev/null; then
error "curl is required but not installed"
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
else
error "Cannot detect distribution"
fi
log "[1/8] Updating package repositories..."
$PKG_UPDATE
log "[2/8] Installing Docker and Docker Compose..."
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
$PKG_INSTALL docker-ce docker-ce-cli containerd.io docker-compose-plugin
else
$PKG_INSTALL dnf-utils
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$PKG_INSTALL docker-ce docker-ce-cli containerd.io docker-compose-plugin
fi
systemctl enable --now docker
log "[3/8] Creating project structure..."
mkdir -p "$INSTALL_DIR"/{app,nginx,data/{postgres,redis}}
cd "$INSTALL_DIR"
log "[4/8] Creating FastAPI application..."
cat > app/main.py << 'EOF'
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from datetime import datetime
import redis
import os
import json
app = FastAPI(title="FastAPI Docker Demo", version="1.0.0")
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://fastapi:password@postgres:5432/fastapi_db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
decode_responses=True
)
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/")
async def root():
return {"message": "FastAPI with Docker Compose", "status": "healthy"}
@app.get("/health")
async def health_check():
try:
db = next(get_db())
db.execute("SELECT 1")
db_status = "healthy"
except Exception as e:
db_status = f"unhealthy: {str(e)}"
try:
redis_client.ping()
redis_status = "healthy"
except Exception as e:
redis_status = f"unhealthy: {str(e)}"
return {
"status": "healthy",
"database": db_status,
"redis": redis_status,
"timestamp": datetime.utcnow().isoformat()
}
EOF
cat > app/requirements.txt << 'EOF'
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
redis==5.0.1
python-multipart==0.0.6
EOF
cat > app/Dockerfile << 'EOF'
FROM python:3.11-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"]
EOF
log "[5/8] Creating Nginx configuration..."
cat > nginx/nginx.conf << EOF
upstream fastapi {
server app:8000;
}
server {
listen 80;
server_name $DOMAIN;
location / {
proxy_pass http://fastapi;
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;
}
}
EOF
log "[6/8] Creating Docker Compose configuration..."
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
app:
build: ./app
container_name: fastapi_app
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://fastapi:${POSTGRES_PASSWORD}@postgres:5432/fastapi_db
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- postgres
- redis
networks:
- app-network
postgres:
image: postgres:15-alpine
container_name: fastapi_postgres
restart: unless-stopped
environment:
- POSTGRES_DB=fastapi_db
- POSTGRES_USER=fastapi
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
container_name: fastapi_redis
restart: unless-stopped
volumes:
- ./data/redis:/data
networks:
- app-network
nginx:
image: nginx:alpine
container_name: fastapi_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
networks:
- app-network
networks:
app-network:
driver: bridge
EOF
cat > .env << 'EOF'
POSTGRES_PASSWORD=secure_password_123
EOF
log "[7/8] Setting permissions and starting services..."
chown -R 1000:1000 "$INSTALL_DIR"
chmod 750 "$INSTALL_DIR"
chmod -R 644 "$INSTALL_DIR"/{app,nginx}/*
chmod 755 "$INSTALL_DIR"/{app,nginx,data}
chmod -R 700 "$INSTALL_DIR"/data
docker compose up -d --build
log "[8/8] Verifying installation..."
sleep 10
if docker compose ps | grep -q "Up"; then
log "✓ Docker containers are running"
else
error "Docker containers failed to start"
fi
if curl -sf http://localhost/health > /dev/null; then
log "✓ FastAPI application is responding"
else
warn "FastAPI health check failed - application may still be starting"
fi
log "Installation completed successfully!"
log "FastAPI application is available at: http://$DOMAIN"
log "Health check endpoint: http://$DOMAIN/health"
log "Project directory: $INSTALL_DIR"
log ""
log "To manage the application:"
log " Start: cd $INSTALL_DIR && docker compose up -d"
log " Stop: cd $INSTALL_DIR && docker compose down"
log " Logs: cd $INSTALL_DIR && docker compose logs -f"
Review the script before running. Execute with: bash install.sh