Log inStart free
← Blog
guidepython

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.

Try it

Free tier, hard-capped at $1 of usage. No card needed.

More posts