FastAPI, Flask and Django deploy from requirements.txt or pyproject.toml. Set a start command that binds 0.0.0.0:$PORT, and for Django, add the domain to your settings.
Last updated
These are the defaults. Override any of them in Settings → Build.
List your dependencies in requirements.txt or pyproject.toml (Poetry, uv and PDM work too). We install them into an isolated environment. To pin the Python version, add a .python-version file or set the env var NIXPACKS_PYTHON_VERSION.
FastAPI
uvicorn app.main:app --host 0.0.0.0 --port $PORT --proxy-headers
Flask
gunicorn -b 0.0.0.0:$PORT app:app
Django
Django is detected via manage.py and starts with python manage.py migrate && gunicorn <project>.wsgi automatically. Add gunicorn to your requirements. Then update settings.py for production:
import os
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = False
ALLOWED_HOSTS = ["<project-slug>.ethiodeploy.com", "yourdomain.com"]
CSRF_TRUSTED_ORIGINS = ["https://<project-slug>.ethiodeploy.com"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
STATIC_ROOT = BASE_DIR / "staticfiles"CSRF_TRUSTED_ORIGINS entries must include the https:// scheme. Nothing runs collectstatic for you: set the Build command to python manage.py collectstatic --noinput and serve files with WhiteNoise (add it to MIDDLEWARE right after SecurityMiddleware).
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["NAME"]. Set SECRET_KEY for Django.
Add a Postgres addon and DATABASE_URL is injected automatically.
# Django (pip install dj-database-url)
import dj_database_url
DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
# SQLAlchemy
engine = create_engine(os.environ["DATABASE_URL"])Django runs migrate on every start, which is fine for a single instance. For Alembic, run alembic upgrade head before your server command, for example alembic upgrade head && uvicorn ….
<project-slug>.ethiodeploy.com. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy.App exits immediately or the health check fails
Cause: No start command, or the server binds 127.0.0.1 / a fixed port.
Fix: Set the Start command to bind 0.0.0.0:$PORT.
Django: DisallowedHost (400)
Cause: Your domain isn't in ALLOWED_HOSTS.
Fix: Add both the ethiodeploy.com subdomain and any custom domain.
Django: CSRF verification failed (403)
Cause: CSRF_TRUSTED_ORIGINS is missing the https origin, or the proxy header isn't trusted.
Fix: Add the full https://… origin and set SECURE_PROXY_SSL_HEADER.
Django admin has no styling
Cause: collectstatic never ran and nothing serves the files.
Fix: Run collectstatic in the Build command and add WhiteNoise.
Build fails installing a package (psycopg, Pillow…)
Cause: The package needs system libraries or a pinned Python version.
Fix: Use psycopg[binary], or set NIXPACKS_APT_PKGS for the libs you need.
Wrong Python version
Cause: The default Python doesn't match your app.
Fix: Add .python-version or set NIXPACKS_PYTHON_VERSION.
Still stuck? Check the deployment's build log in the dashboard, or see Troubleshooting.
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.