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
requirements.txtat the repository root, with pinned versions- A production server (
uvicornorgunicorn) listed inrequirements.txt - GitHub account connected to Zenifra
Generate the file from your virtual environment:
pip freeze > requirements.txtUsing 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
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello from Zenifra"}
@app.get("/health")
def health():
return {"status": "ok"}fastapi==0.115.0
uvicorn[standard]==0.30.6| Field | Value |
|---|---|
| Runtime | Python 3.12 |
| Port | 8000 |
build | empty |
start | uvicorn 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 2Add 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
from flask import Flask
app = Flask(__name__)
@app.get("/")
def root():
return {"message": "Hello from Zenifra"}
@app.get("/health")
def health():
return {"status": "ok"}flask==3.0.3
gunicorn==23.0.0| Field | Value |
|---|---|
| Port | 8000 |
build | empty |
start | gunicorn 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 8000With 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
| Symptom | Fix |
|---|---|
ModuleNotFoundError | Package missing from requirements.txt |
Error loading ASGI app | Wrong path in start: the format is file:variable, such as main:app |
| Public URL does not respond | Server on 127.0.0.1 or a port different from the Port field |
| Instance restarts out of memory | Reduce --workers or choose a bigger plan |
Redirects to http:// | Enable proxy headers as shown above |
Next steps
Last updated on
Static sites and SPAs (Vite, React, Vue, Astro)
Deploy static sites and SPAs built with Vite, React, Vue, Angular, or Astro on Zenifra, using the Node.js runtime or the official NGINX image.
Deploy Django on Zenifra
Deploy Django on Zenifra with gunicorn, static files via WhiteNoise, ALLOWED_HOSTS, CSRF, HTTPS behind a proxy, migrations, and a managed database.