Python
zbpack builds Python apps that have a requirements.txt, pyproject.toml (Poetry, uv, PDM) or Pipfile. It detects FastAPI, Django and Flask and picks a production server for each.
| Framework | Start command zbpack uses |
|---|---|
| FastAPI | uvicorn main:app --host 0.0.0.0 --port 8080 |
| Django | gunicorn --bind :8080 <project>.wsgi |
| Flask | gunicorn --bind :8080 main:app |
The Python version defaults to 3.13; set another in .python-version, pyproject.toml or with ZBPACK_PYTHON_VERSION. To start the app differently, set ZBPACK_START_COMMAND under Settings → Build → zbpack options.
FastAPI
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello from FastAPI"}# requirements.txt
fastapi
uvicorn[standard]zbpack takes the first of main.py, app.py, server.py and app/__init__.py that exists, and the variable assigned FastAPI(...) in it (app above). With another layout, such as app/main.py, set the start command:
ZBPACK_START_COMMAND=uvicorn app.main:app --host 0.0.0.0 --port 8080Django
The project needs manage.py and gunicorn in requirements.txt:
django
gunicornRead production settings from variables in settings.py:
import os
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "")
DEBUG = os.environ.get("DJANGO_DEBUG") == "1"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = os.environ.get("DJANGO_CSRF_TRUSTED_ORIGINS", "").split(",")and set them on the Variable tab:
DJANGO_SECRET_KEY=${PASSWORD}
DJANGO_ALLOWED_HOSTS=${ZENAVO_WEB_DOMAIN}
DJANGO_CSRF_TRUSTED_ORIGINS=${ZENAVO_WEB_URL}(WEB is the name of the service's port; use the name shown on its Networking tab.)
Static files
When settings.py sets both STATIC_URL and STATIC_ROOT, zbpack runs python manage.py collectstatic --noinput during the build and serves the files with nginx in front of gunicorn, or leaves them to WhiteNoise when its middleware is in settings.py:
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"The build has none of the service's variables, so settings.py must load without them: read DJANGO_SECRET_KEY with os.environ.get(...) as above, not os.environ[...].
Migrations
Migrations need the database, which the build cannot reach, so run them at start:
ZBPACK_START_COMMAND=python manage.py migrate --noinput && gunicorn --bind :8080 mysite.wsgiConnect a database
Add PostgreSQL to the project and use its connection string, for example with dj-database-url or SQLAlchemy:
DATABASE_URL=${POSTGRESQL_CONNECTION_STRING}import dj_database_url
DATABASES = {"default": dj_database_url.config(conn_max_age=600)}Install a driver too: psycopg[binary] for PostgreSQL, mysqlclient or PyMySQL for MySQL and MariaDB.
Common problems
| Symptom | Cause |
|---|---|
ModuleNotFoundError at start | The package is missing from requirements.txt (or pyproject.toml). |
Django answers 400 Bad Request | The domain is not in ALLOWED_HOSTS. |
Django forms fail with CSRF verification failed | The https:// address is not in CSRF_TRUSTED_ORIGINS. |
| FastAPI deploy never becomes healthy | The app object is not at main:app. Set ZBPACK_START_COMMAND. |
| Workers are killed under load | The service's memory is too small for the number of gunicorn workers. Lower --workers, or give the service more memory. |