Deploy FastAPI HA [Nov '26]
FastAPI on Python 3.15 + uv over self-healing PostgreSQL HA with failover
fastapi
Just deployed
pgaf-primary
Just deployed
/data
pgaf-replica
Just deployed
/data
pgaf-monitor
Just deployed
/data
Deploy and Host FastAPI HA
Deploy FastAPI HA on Railway and get a production-ready Python 3.15 API backed by self-healing PostgreSQL: automated failover, a streaming read replica, Alembic migrations, health probes, and OpenTelemetry telemetry in one click.
About Hosting
Hosting FastAPI HA on Railway pairs the lean FastAPI starter stack (uv-locked Python 3.15, hardened multi-stage image, JSON logging, native OTel) with our own pg_auto_failover cluster: a monitor, a primary, and a hot standby that clones via WAL streaming. The app connects through a multi-host URL pinned to read-write sessions, so when the monitor promotes the standby your API keeps writing — seconds of blip instead of an outage. Nodes talk over Railway's private network only; nothing database-facing is exposed to the internet.
Why Deploy FastAPI HA on Railway?
This template wires the full production path for you: a three-node PostgreSQL 18 HA cluster (same battle-tested image as our standalone Postgres HA template), a failover-aware connection string auto-composed over the private network, additive Alembic migrations that always target the read-write primary, split liveness/readiness probes, and native OpenTelemetry traces/metrics/logs in Railway's Observability tab. Sleep-at-night durability for APIs that cannot tolerate database downtime — plus a read replica you can point analytics traffic at.
What's included
| Service | Type | Purpose |
|---|---|---|
| fastapi | Web Service | The FastAPI app: async SQLModel, Alembic on boot, health probes |
| pgaf-primary | Database | PostgreSQL 18 primary (writes) with persistent volume |
| pgaf-replica | Database | Streaming hot standby (reads, promotion candidate) |
| pgaf-monitor | Database | pg_auto_failover arbiter: health checks + automated promotion |
Key features
- Self-healing Postgres: monitor detects primary failure (~10s), promotes the standby — no human intervention, no DNS tricks
- Automatic rejoin: a returned old primary is demoted and re-cloned; no split-brain, no manual pg_rewind
- Failover-aware app wiring: multi-host
DATABASE_URLwithtarget_session_attrs=read-write; migrations and writes always land on the live primary - Read scaling: point reporting queries at
pgaf-replicato keep the primary free for writes - Latest app stack: FastAPI 0.162.4 on Python 3.15.2, uv-locked,
/healthz+/readyzprobes, JSON logging, native OTel,WEB_CONCURRENCYscaling
Common Use Cases
- Production API that cannot tolerate database downtime (failover in seconds)
- Read-heavy workloads: writes to the primary, analytics offloaded to the hot standby
- Zero-downtime maintenance windows (fail over, patch, fail back)
- Teams wanting a HA Postgres backend wired into FastAPI without hand-rolling topology
Dependencies for FastAPI HA
Deployment Dependencies
| Name | Version | Purpose |
|---|---|---|
| Python | 3.15.2 | Runtime (latest stable) |
| FastAPI | 0.162.4 | Web framework (pinned) |
| uvicorn / fastapi-cli | locked in uv.lock | ASGI server via the official fastapi run CLI |
| SQLModel + SQLAlchemy async | locked | ORM over asyncpg (multi-host failover DSNs) |
| Alembic | locked | Versioned, additive migrations (read-write targeted) |
| PostgreSQL + pg_auto_failover | 18 (our image) | Three-node HA cluster: monitor, primary, replica |
Next steps
- Open the service domain and visit
/docs— the interactive Swagger UI with the reference CRUD at/api/v1/items - Create an item via
POST /api/v1/items— it lands on the primary and streams to the replica - Open the Observability tab and watch request spans arrive via OpenTelemetry
- Test failover safely: stop
pgaf-primary, watch the monitor promote the replica, and keep writing — no reconnect needed
🐍 Deep Dive — FastAPI HA
📐 Architecture
graph TD
Client([Client / Frontend]) --> Gateway[Railway Public Domain + TLS]
Gateway --> Web[fastapi service<br>Python 3.15 · uvicorn workers]
Web -->|writes / target read-write| Primary[pgaf-primary]
Web -->|optional reads| Replica[pgaf-replica]
Monitor[pgaf-monitor] <-->|health checks + promotion| Primary
Monitor <-->|health checks| Replica
Primary -->|WAL streaming| Replica
Web --> OTel[Railway Observability<br>OTLP traces · metrics · logs]
⚙️ Environment Variables
Wired automatically (no input needed): DATABASE_URL is a multi-host failover URL composed from the cluster's private domains — postgresql://postgres@${{pgaf-primary.RAILWAY_PRIVATE_DOMAIN}}:5432,${{pgaf-replica.RAILWAY_PRIVATE_DOMAIN}}:5432/postgres?target_session_attrs=read-write. Set DATABASE_URL explicitly to use an external database instead.
User-configurable:
| Variable | Default | Description |
|---|---|---|
PORT | 8000 | Injected by Railway; the entrypoint binds it automatically. |
WEB_CONCURRENCY | 2 | Uvicorn workers (read natively by the FastAPI CLI). Scale with CPU. |
ENVIRONMENT | production | Environment label (production, staging, …). |
LOG_LEVEL | info | debug · info · warning · error — logs ship as JSON. |
BACKEND_CORS_ORIGINS | (empty) | Comma-separated allowed CORS origins. |
RUN_MIGRATIONS | true | Run alembic upgrade head before serving (targets the read-write primary). |
DB_POOL_SIZE / DB_MAX_OVERFLOW | 10 / 10 | Async connection pool tuning. |
FASTAPI_OTEL_AUTO_CONFIGURE | true | Native OTel export (active when Railway's OTLP endpoint is present). |
How failover works
pgaf-monitorhealth-checks the primary every second.- On ~10s of failures it promotes the standby — the new primary.
- The app's multi-host URL (
target_session_attrs=read-write) reconnects writes to whichever node is read-write — transparent failover, no config change. - The old primary returns → demoted and re-cloned as the new standby. Roles converge without manual action.
🚀 Local Development
Prerequisites: uv (installs Python 3.15 automatically).
git clone && cd fastapi-ha
cp .env.example .env
uv sync --all-groups # resolves uv.lock exactly
uv run fastapi dev # hot-reload dev server on :8000
uv run pytest # test suite (async SQLite — no Postgres needed)
uv run alembic upgrade head # apply migrations locally
Handy commands:
uv run alembic revision --autogenerate -m "add orders table" # new migration
uv lock && uv sync --all-groups # after dependency changes
bash e2e/run-tests.sh # full template validation
Shipping to Railway
railway init # or link an existing project
railway up # builds from the Dockerfile (config-as-code in railway.toml)
🩺 Health & Observability
| Endpoint | Purpose | Behavior |
|---|---|---|
/healthz | Liveness (Railway healthcheckPath) | Cheap JSON 200 — never touches the DB. |
/readyz | Readiness | SELECT 1 round-trip through the failover URL; 503 when no writable node. |
/docs, /redoc | Interactive API docs | Auto-generated OpenAPI. |
/api/v1/items | Reference CRUD | Copy this pattern for your own resources. |
🧱 Building Your Own API on Top
- New resource → copy the
app/db/models/item.pysplit (Base → table → Create/Update/Public), thenuv run alembic revision --autogenerate -m "…". - New endpoints → add a router under
app/api/routes/, include it inapp/main.py. - New config → add the field to
app/core/config.py; it becomes an env var automatically. - Migrations stay additive — nullable columns first, chunked backfills,
CREATE INDEX CONCURRENTLYon live tables (zero-downtime policy).
🧪 End-to-End (E2E) Testing
bash e2e/run-tests.sh
Validates structure, marketplace publish config, manifest wiring (HA cluster → multi-host DATABASE_URL with read-write targeting), uv.lock freshness, byte-compilation, pytest, Docker build, and a live container smoke probe of /healthz + /docs. CI runs hadolint + this suite on every push/PR.
Maintained by the template publisher. Deployments are eligible for Railway's standard template support flow — questions land in the publisher's Template Queue.
Template Content
fastapi
wotonews/fastapi-ha:v1.1.1PORT
LOG_LEVEL
ENVIRONMENT
RUN_MIGRATIONS
WEB_CONCURRENCY
pgaf-primary
wotonews/postgres-ha:v0.3.0pgaf-replica
wotonews/postgres-ha:v0.3.0pgaf-monitor
wotonews/postgres-ha:v0.3.0