Deploying FastAPI with Postgres on Aloof
From an empty directory to a FastAPI service with managed Postgres, migrations before traffic, and a background worker, using aloof launch and aloof.toml.
This post walks through a production-shaped FastAPI deploy: an HTTP API, a managed Postgres database on a private network, migrations that run before a release takes traffic, and a worker process that shares the image. Every command here works today.
1. Detect and generate
In a directory with main.py and a requirements.txt that lists fastapi, run:
$ aloof launch --name notes-api detected: fastapi wrote Dockerfile wrote aloof.toml wrote .dockerignore
The generated Dockerfile is minimal: python:3.12-slim, pip install -r requirements.txt, and uvicorn main:app --host 0.0.0.0 --port $PORT. Aloof injects PORT (8080 by default), so the same image runs locally and on the platform. If you already have a Dockerfile, launch leaves it alone.
2. Add a database
$ aloof pg create notes-db --plan pg-dev $ aloof pg attach notes-db notes-api # injects DATABASE_URL, joins the private network
pg-dev is $2.99/month. The database never gets a public port: the app reaches it over a private Docker network, and DATABASE_URL is stored encrypted with the app's other secrets. Daily logical backups are kept on the host, and encrypted copies go off-site (see the backups post).
3. Migrations before traffic
Add a release command and a worker to aloof.toml:
[processes] web = "uvicorn main:app --host 0.0.0.0 --port 8080" worker = "python -m app.worker" [deploy] release_command = "alembic upgrade head" [[http_service.checks]] path = "/healthz" interval = "10s"
On every deploy the build finishes first. Then alembic upgrade head runs once in a one-off machine with the same image, env and private network. Only if it exits 0 do new web instances start, and they must pass /healthz before the old ones stop. A failing migration leaves the previous release serving.
4. Deploy
$ aloof deploy uploading build context: 23 files, 0.1 MB (.dockerignore respected) release_command: alembic upgrade head [release] INFO [alembic.runtime.migration] Running upgrade -> 3f2a, notes deploy complete (v1 live) process group worker: 1 x `python -m app.worker` live: https://notes-api.aloof.si/
The worker runs as notes-api-worker. It isn't routed and doesn't sleep; scale it independently with aloof scale count worker=2.
What it costs
A shared-1x-256mb web instance is $1.99/month always-on. Add pg-dev and a 256 MB worker and the stack is $6.97/month before usage like egress ($0.06/GB). With auto_stop_machines = true the web tier only bills while it's awake.