Project: Production Deployment

Introduction

This project takes your FastAPI service (Items, Todo, or Inference API) from local uvicorn to a production server with environment-based config, Alembic migrations, Gunicorn + UvicornWorker, Nginx, and optional Docker Compose. It mirrors the Flask deploy checklist adapted for ASGI.

Prerequisites

Project Goals

  • App runs under systemd + Gunicorn/Uvicorn workers
  • Nginx reverse proxy with TLS path documented
  • Secrets in /etc/fastapi-app/env
  • alembic upgrade head on deploy
  • GET /health returns 200
  • Compare with Flask production project

Step 1: Production Settings

app/core/config.py:

python
class Settings(BaseSettings):
    environment: str = "production"
    debug: bool = False
    secret_key: str
    database_url: str
    cors_origins: list[str] = []
 
    @property
    def is_production(self) -> bool:
        return self.environment == "production"

app/main.py:

python
settings = get_settings()
 
app = FastAPI(
    title="Production API",
    docs_url="/docs" if settings.debug else None,
    redoc_url=None,
    openapi_url="/openapi.json" if settings.debug else None,
    lifespan=lifespan,
)

Step 2: Server Preparation (Ubuntu)

bash
sudo apt update
sudo apt install -y python3-venv python3-pip nginx
sudo mkdir -p /var/www/fastapi-app /var/log/fastapi-app
sudo useradd --system --home /var/www/fastapi-app --shell /bin/bash www-data 2>/dev/null || true
sudo chown -R www-data:www-data /var/www/fastapi-app /var/log/fastapi-app

Deploy code:

bash
sudo -u www-data git clone <repo> /var/www/fastapi-app
cd /var/www/fastapi-app
sudo -u www-data python3 -m venv .venv
sudo -u www-data .venv/bin/pip install -r requirements.txt gunicorn

Step 3: Environment File

/etc/fastapi-app/env (chmod 600, root):

bash
ENVIRONMENT=production
SECRET_KEY=replace-with-long-random-hex
DATABASE_URL=mysql+pymysql://fastapi:STRONG_PASS@127.0.0.1/fastapi_app?charset=utf8mb4
CORS_ORIGINS=https://app.example.com

Create MySQL DB—MySQL on Linux.

Step 4: Migrate and Test Locally on Server

bash
cd /var/www/fastapi-app
sudo -u www-data bash -c 'set -a; source /etc/fastapi-app/env; set +a; .venv/bin/alembic upgrade head'
sudo -u www-data bash -c 'set -a; source /etc/fastapi-app/env; set +a; .venv/bin/gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 2 -b 127.0.0.1:8000'
curl http://127.0.0.1:8000/health

Stop manual test before enabling systemd.

Step 5: systemd Unit

/etc/systemd/system/fastapi-app.service:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi-app
sudo systemctl status fastapi-app

Step 6: Nginx

/etc/nginx/sites-available/fastapi-app:

WebSocket routes add Upgrade and Connection headers—deploy chapter.

Enable HTTPS—Let's Encrypt.

bash
sudo ln -s /etc/nginx/sites-available/fastapi-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 7: Deploy Script

scripts/deploy.sh:

bash
#!/bin/bash
set -euo pipefail
APP_DIR=/var/www/fastapi-app
cd "$APP_DIR"
sudo -u www-data git pull
sudo -u www-data .venv/bin/pip install -r requirements.txt
set -a; source /etc/fastapi-app/env; set +a
sudo -u www-data .venv/bin/alembic upgrade head
sudo systemctl restart fastapi-app
curl -sf http://127.0.0.1:8000/health
echo "Deploy OK"

Run after CI passes—Git CI/CD.

Step 8: Docker Alternative

Use docker compose up -d from Docker and CI chapter; Nginx on host proxies to port 8000.

Step 9: Post-Deploy Verification

bash
curl -I https://api.example.com/health
journalctl -u fastapi-app -n 50 --no-pager

Run one authenticated API flow (Todo project) over HTTPS.

Production Checklist

  • DEBUG off, docs disabled
  • Strong secrets in env file
  • Migrations applied
  • Gunicorn bound to 127.0.0.1
  • Nginx proxy headers configured
  • HTTPS live
  • Firewall allows 80/443, blocks public 8000
  • MySQL backups—backup and restore
  • CI runs pytest and pip audit

Rollback

bash
cd /var/www/fastapi-app
sudo -u www-data git checkout PREVIOUS_SHA
sudo -u www-data .venv/bin/pip install -r requirements.txt
sudo systemctl restart fastapi-app

Avoid alembic downgrade in production unless planned.

FAQ

502 after deploy?

Check journalctl -u fastapi-app—import errors, missing env, migration failure.

CORS fails on HTTPS?

CORS_ORIGINS must include exact frontend origin with https://.

Multiple apps on one VPS?

Separate systemd units, ports, and server_name blocks.

Zero-downtime?

Blue/green or rolling updates—advanced.

Log aggregation?

Ship /var/log/fastapi-app/* to ELK, Loki, or CloudWatch.

Track complete?

Finish with troubleshooting and chapter index.