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
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:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'fastapi-app'
static_configs:
- targets: ['app:8000']
metrics_path: '/metrics'
scrape_interval: 10s
- job_name: 'nginx'
static_configs:
- targets: ['nginx:80']
metrics_path: '/nginx-health'
scrape_interval: 15s
- job_name: 'postgres'
static_configs:
- targets: ['postgres:5432']
scrape_interval: 30s
- job_name: 'redis'
static_configs:
- targets: ['redis:6379']
scrape_interval: 30s
Verify your setup
Test all components of your FastAPI Docker Compose stack to ensure everything is working correctly.
# Check all containers are running
docker compose ps
Test the FastAPI application
curl http://localhost/
curl http://localhost/health
Test database functionality
curl -X POST "http://localhost/items?name=test&description=Test item"
curl http://localhost/items/1
Check container logs
docker compose logs app
docker compose logs postgres
docker compose logs redis
docker compose logs nginx
Monitor resource usage
docker stats
Test SSL if configured
curl -k https://localhost/health
Production deployment optimization
For production deployments, you'll want to implement additional security and performance optimizations. Consider using database connection pooling for better PostgreSQL performance and container security profiles for enhanced security.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| App container fails to start | Database connection timeout | Add health checks and depends_on with condition |
| 502 Bad Gateway from Nginx | FastAPI container not ready | Check docker compose logs app for startup errors |
| Permission denied on SSL files | Incorrect file permissions | Use chmod 600 for private keys, 644 for certificates |
| Database connection refused | PostgreSQL not ready | Wait for health checks or use connection retry logic |
| Redis connection errors | Authentication or network issues | Check Redis password configuration and network connectivity |
| High memory usage | No resource limits configured | Add deploy.resources limits in docker-compose.override.yml |
Next steps
- Implement blue-green deployments for zero-downtime updates
- Set up Redis clustering for high availability caching
- Configure PostgreSQL replication for database high availability
- Add comprehensive monitoring with Prometheus and Grafana dashboards
- Implement authentication and authorization for secure API access
Running this in production?
Automated 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