---
title: "Deploy FastAPI HA [Nov '26]"
description: "FastAPI on Python 3.15 + uv over self-healing PostgreSQL HA with failover"
category: "Starters"
url: https://railway.com/deploy/fastapi-ha-nov-26
---

# Deploy FastAPI HA [Nov '26]

FastAPI on Python 3.15 + uv over self-healing PostgreSQL HA with failover

**[Deploy FastAPI HA [Nov '26] on Railway](https://railway.com/template/fastapi-ha-nov-26)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/fastapi-ha-nov-26/manifest.json

- **Creator:** wotonews
- **Category:** Starters

## Template content

### fastapi

- **Image:** wotonews/fastapi-ha:v1.1.1

### pgaf-primary

- **Image:** wotonews/postgres-ha:v0.3.0

### pgaf-replica

- **Image:** wotonews/postgres-ha:v0.3.0

### pgaf-monitor

- **Image:** wotonews/postgres-ha:v0.3.0

## Documentation

# 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_URL` with `target_session_attrs=read-write`; migrations and writes always land on the live primary
- **Read scaling**: point reporting queries at `pgaf-replica` to keep the primary free for writes
- **Latest app stack**: FastAPI 0.162.4 on Python 3.15.2, uv-locked, `/healthz` + `/readyz` probes, JSON logging, native OTel, `WEB_CONCURRENCY` scaling

## 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

1. Open the service domain and visit `/docs` — the interactive Swagger UI with the reference CRUD at `/api/v1/items`
2. Create an item via `POST /api/v1/items` — it lands on the primary and streams to the replica
3. Open the Observability tab and watch request spans arrive via OpenTelemetry
4. Test failover safely: stop `pgaf-primary`, watch the monitor promote the replica, and keep writing — no reconnect needed

---

# 🐍 Deep Dive — FastAPI HA

## 📐 Architecture

```mermaid
graph TD
    Client([Client / Frontend]) --&gt; Gateway[Railway Public Domain + TLS]
    Gateway --&gt; Web[fastapi service<br>Python 3.15 · uvicorn workers]
    Web --&gt;|writes / target read-write| Primary[pgaf-primary]
    Web --&gt;|optional reads| Replica[pgaf-replica]
    Monitor[pgaf-monitor] &lt;--&gt;|health checks + promotion| Primary
    Monitor &lt;--&gt;|health checks| Replica
    Primary --&gt;|WAL streaming| Replica
    Web --&gt; 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

1. `pgaf-monitor` health-checks the primary every second.
2. On ~10s of failures it promotes the standby — the new primary.
3. The app's multi-host URL (`target_session_attrs=read-write`) reconnects writes to whichever node is read-write — transparent failover, no config change.
4. The old primary returns → demoted and re-cloned as the new standby. Roles converge without manual action.

## 🚀 Local Development

Prerequisites: [uv](https://docs.astral.sh/uv/) (installs Python 3.15 automatically).

```bash
git clone  &amp;&amp; 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:

```bash
uv run alembic revision --autogenerate -m "add orders table"   # new migration
uv lock &amp;&amp; uv sync --all-groups                                # after dependency changes
bash e2e/run-tests.sh                                          # full template validation
```

### Shipping to Railway

```bash
railway init    # or link an existing project
railway up      # builds from the Dockerfile (config-as-code in railway.toml)
```

## 🩺 Health &amp; 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

1. **New resource** → copy the `app/db/models/item.py` split (Base → table → Create/Update/Public), then `uv run alembic revision --autogenerate -m "…"`.
2. **New endpoints** → add a router under `app/api/routes/`, include it in `app/main.py`.
3. **New config** → add the field to `app/core/config.py`; it becomes an env var automatically.
4. **Migrations stay additive** — nullable columns first, chunked backfills, `CREATE INDEX CONCURRENTLY` on live tables (zero-downtime policy).

## 🧪 End-to-End (E2E) Testing

```bash
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.


## Similar templates

- [Openfront Gym](https://railway.com/deploy/openfront-gym) — Opensource Mindbody alternative based on Next.js and Keystone.js
- [open-excalidraw](https://railway.com/deploy/open-excalidraw) — Self-hostable collaborative drawing built on Excalidraw
- [Next.js + Prisma + PostgreSQL Starter](https://railway.com/deploy/nextjs-prisma-postgres) — Deploy a Next.js app with Prisma ORM and PostgreSQL in one click.

Open this page in a browser: https://railway.com/deploy/fastapi-ha-nov-26
