Framework guides

Python with FastAPI and Flask

Python APIs are deployed from a GitHub Repository with the Python runtime (3.11, 3.12, or 3.13). Zenifra installs dependencies with pip install -r requirements.txt and starts the app with the start command.

Prerequisites

Generate the file from your virtual environment:

pip freeze > requirements.txt

Using Poetry or uv? Export to requirements.txt before committing: poetry export -f requirements.txt --output requirements.txt or uv export --format requirements-txt > requirements.txt.

Libraries with native extensions

The build environment already includes a C compiler and the PostgreSQL client libraries, so packages such as psycopg2 install without extra steps. Prefer psycopg[binary] or psycopg2-binary for faster builds.

FastAPI

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello from Zenifra"}

@app.get("/health")
def health():
    return {"status": "ok"}
requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
FieldValue
RuntimePython 3.12
Port8000
buildempty
startuvicorn main:app --host 0.0.0.0 --port 8000

To run more than one process per instance, use uvicorn workers under gunicorn:

start: gunicorn main:app -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers 2

Add gunicorn to requirements.txt. Start with 2 workers and tune for the plan's memory: each worker loads its own copy of the app.

Behind the HTTPS proxy

Zenifra terminates HTTPS before forwarding the request. For URLs generated by FastAPI to use https, add --proxy-headers --forwarded-allow-ips="*" to uvicorn.

Flask

app.py
from flask import Flask

app = Flask(__name__)

@app.get("/")
def root():
    return {"message": "Hello from Zenifra"}

@app.get("/health")
def health():
    return {"status": "ok"}
requirements.txt
flask==3.0.3
gunicorn==23.0.0
FieldValue
Port8000
buildempty
startgunicorn app:app --bind 0.0.0.0:8000 --workers 2

Do not use flask run in production

flask run and app.run() start the development server, which cannot handle real load and listens only on 127.0.0.1 by default. Use gunicorn.

If the app uses the application factory pattern, point to the function: gunicorn "app:create_app()" --bind 0.0.0.0:8000.

Migrations with Alembic

Project variables, including DATABASE_URL, exist at runtime. Apply migrations before starting the server:

start: alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000

With Flask-Migrate, use flask db upgrade && gunicorn app:app --bind 0.0.0.0:8000.

Logs

Everything the app writes to stdout and stderr shows up in the project logs. Python output is already unbuffered, so print() and logging appear immediately. For gunicorn access logs, add --access-logfile -.

Projects with pyproject.toml

If the repository has pyproject.toml or setup.py, the package is also installed after the build. Even so, keep requirements.txt with every runtime dependency.

Common problems

SymptomFix
ModuleNotFoundErrorPackage missing from requirements.txt
Error loading ASGI appWrong path in start: the format is file:variable, such as main:app
Public URL does not respondServer on 127.0.0.1 or a port different from the Port field
Instance restarts out of memoryReduce --workers or choose a bigger plan
Redirects to http://Enable proxy headers as shown above

Next steps

Last updated on

On this page