FastAPI deploys from requirements.txt or pyproject.toml. Set a start command with fastapi run, add a health route, and push. Postgres is one click away.
Last updated
These are the defaults. Override any of them in Settings → Build.
Install FastAPI with its standard extras, which include the fastapi CLI and uvicorn:
fastapi[standard]
Then set the Start command to one of:
# app in main.py fastapi run main.py --port $PORT # app in app/main.py, two worker processes (Pro and above) fastapi run app/main.py --port $PORT --workers 2 # plain uvicorn uvicorn app.main:app --host 0.0.0.0 --port $PORT
Pin the Python version with a .python-version file. A Procfile web: line also works as the start command.
HTTPS ends at the platform's proxy, which forwards requests to your app over HTTP with an X-Forwarded-Proto header. Uvicorn only trusts that header from 127.0.0.1 by default, so redirects FastAPI generates (like the trailing-slash redirect from /items to /items/) point at http:// and browsers block them as mixed content.
Add the env var FORWARDED_ALLOW_IPS=*. Uvicorn and fastapi run both read it.
Every deploy requests your health check path (/ by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If / returns 404 or needs a login, add a route like /healthz that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving.
@app.get("/healthz", include_in_schema=False)
def healthz():
return {"ok": True}Add variables in the project's settings. They're available both while the app builds and while it runs, so build-time values (like framework-public keys) work without extra setup. See Environment variables. Read them with os.environ or pydantic-settings' BaseSettings. A .env file in the repo isn't needed in production.
Add a Postgres addon and DATABASE_URL is injected automatically. It uses the postgres:// scheme, which SQLAlchemy 2.x rejects, so rewrite it for the driver you use:
import os
from sqlalchemy.ext.asyncio import create_async_engine
url = os.environ["DATABASE_URL"].replace("postgres://", "postgresql+asyncpg://", 1)
engine = create_async_engine(url, pool_size=5)Use postgresql+psycopg:// instead for a sync engine. Run Alembic migrations before the server starts:
alembic upgrade head && fastapi run main.py --port $PORT
FastAPI's BackgroundTasks run inside the same process and need nothing extra. For a queue (ARQ, Celery, Dramatiq), add a Redis addon and start the worker in the same container, for example arq app.worker.WorkerSettings & fastapi run main.py --port $PORT. Addons are private to their project, so a separate worker project can't reach them.
<project-slug>.ethiodeploy.com. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy.Deploy fails the health check, or the app exits right after starting
Cause: No start command, so python main.py ran and didn't start a server.
Fix: Set the Start command to fastapi run main.py --port $PORT.
Deploy fails: GET / returned 404
Cause: The app has no / route, so the health check never passes.
Fix: Add a /healthz route and set it as the Health Check Path.
Redirects go to http:// and the browser blocks them
Cause: Uvicorn doesn't trust the proxy's X-Forwarded-Proto header.
Fix: Set FORWARDED_ALLOW_IPS=*.
NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres
Cause: SQLAlchemy 2.x doesn't accept the postgres:// scheme.
Fix: Replace it with postgresql+asyncpg:// or postgresql+psycopg:// before creating the engine.
Container restarts under load on the Free plan
Cause: Several workers don't fit in 256 MB of memory.
Fix: Run a single worker on Free, or move to Pro for 1 GB.
Still stuck? Check the deployment's build log in the dashboard, or see Troubleshooting.
Can I deploy a FastAPI app for free?
Yes. The Free plan runs one project with a 512 MB Postgres database. It sleeps after 30 minutes without traffic and wakes on the next request. Pro ($10/month) is always on with 1 GB of memory.
Should I use fastapi run or uvicorn?
Either works. fastapi run is uvicorn with production defaults and listens on 0.0.0.0. Pass --port $PORT to both.
Can I use WebSockets?
Yes. The app runs as a normal long-lived process, so WebSocket endpoints work like they do locally.
Is the /docs page public?
Yes, unless you turn it off. Pass docs_url=None and redoc_url=None to FastAPI() in production if you don't want the API docs exposed.
Ready to deploy? Sign in with GitHub and pick your repo. No Dockerfile, no YAML.
See the full docs for custom domains, env vars, addons, and billing.