Deploy a Django App 🐍
Push your Django project to GitHub and Faable builds and runs it — no Dockerfile, no YAML, no gunicorn command to write. The builder detects Django from your repo, installs your dependencies, and starts your app behind automatic SSL at https://<app>.faable.link, hosted 100% in Europe.
What Faable detects
Detection is file-based (full rules). For a Django project the builder needs two things:
| It looks for | Which gives it |
|---|---|
requirements.txt, pyproject.toml or Pipfile | Your dependencies — installed into a virtualenv |
manage.py plus a package containing wsgi.py | Django itself → the start command below |
When both are present the builder runs, with no configuration from you:
gunicorn <your_project>.wsgi:application --bind 0.0.0.0:$PORTgunicorn is installed automatically if it isn’t in your dependencies — though pinning it in requirements.txt is good practice.
The one thing to get right: $PORT
Faable assigns your app a port at runtime and passes it as the PORT environment variable. The auto-detected start command already binds 0.0.0.0:$PORT, so a standard Django project needs no change at all. You only have to think about it if you override the start command — in which case bind 0.0.0.0 and $PORT, never a hardcoded port.
Prepare your project
1. Dependencies
# requirements.txt
Django>=5.0
gunicorn
whitenoise
psycopg[binary] # if you use PostgreSQL2. Allowed hosts and CSRF
Django refuses requests whose Host header isn’t in ALLOWED_HOSTS. Read it from the environment so the same code works locally and on Faable:
# settings.py
import os
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",") or []
# Django 4+ also checks the origin on unsafe requests
CSRF_TRUSTED_ORIGINS = [
o for o in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if o
]Then set them as environment variables on the app:
faable deploy secrets set \
ALLOWED_HOSTS=myapp.faable.link \
CSRF_TRUSTED_ORIGINS=https://myapp.faable.linkAdd your custom domain to both values when you attach one.
3. Static files
gunicorn serves your application, not your static assets. The standard solution is WhiteNoise , which serves them from the app process itself:
# settings.py
import os
STATIC_URL = "static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # right after SecurityMiddleware
# … the rest of your middleware
]
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}Collect them at build time with a buildCommand in faable.json:
{
"buildCommand": "python manage.py collectstatic --noinput"
}4. Secrets and debug
Never ship DEBUG = True or a hardcoded SECRET_KEY:
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "0") == "1"faable deploy secrets set SECRET_KEY=$(python -c "import secrets;print(secrets.token_urlsafe(50))")Deploy
From the dashboard — the normal path:
- Create a Project and an App in the Faable Dashboard .
- Click Link repository and pick your Django repo.
- Push to your release branch. Faable builds and takes it live.
Or from your laptop, for an ad-hoc deploy:
npm i -g @faable/faable
faable login
faable deployYour app is live at https://<app>.faable.link with automatic SSL and the WAF already inspecting traffic.
Database migrations
Migrations are not run automatically — an automatic migration on every deploy is a good way to lose data. Run them explicitly when a release needs them, from your machine against the production database:
DATABASE_URL=<your production url> python manage.py migrateFaable Deploy does not provide a managed database; point DATABASE_URL at your own PostgreSQL (any EU provider) and set it as a secret.
Choosing the Python version
The default is Python 3.11. Supported minors are 3.11 and 3.12 — pin one with a runtime.txt:
python-3.12Or with .python-version, or requires-python in pyproject.toml. First match wins, in that order.
Troubleshooting
DisallowedHostin the logs —ALLOWED_HOSTSdoesn’t include the domain you’re visiting. Add<app>.faable.linkand your custom domain.- CSS and images 404 — static files weren’t collected or WhiteNoise isn’t in the middleware. Check both steps above.
- Build fails with “no start command” — the builder found your dependencies but not
manage.py+ awsgi.pypackage. Either fix the layout or declarestartCommandinfaable.json. CSRF verification failedon forms — add your full origin (withhttps://) toCSRF_TRUSTED_ORIGINS.- The app boots then exits — you overrode the start command with a hardcoded port. Bind
0.0.0.0:$PORT.
FAQ
Do I need a Dockerfile to deploy Django on Faable?
No. Faable detects Django from manage.py plus a wsgi.py package and generates the gunicorn start command for you. A Dockerfile is only the escape hatch for stacks the buildpacks don’t recognise — and note that if your repo has a package.json, that wins over the Dockerfile.
How do I run collectstatic on Faable?
Set it as the buildCommand in faable.json so it runs at build time: {"buildCommand": "python manage.py collectstatic --noinput"}. Serve the result with WhiteNoise.
Which Python versions does Faable support for Django?
3.11 and 3.12. The default is 3.11; pin the one you want with runtime.txt (python-3.12), .python-version, or requires-python in pyproject.toml.
Does Faable run my Django migrations automatically?
No, by design — automatic migrations on every deploy risk irreversible schema changes. Run python manage.py migrate explicitly against your production DATABASE_URL when a release needs it.
Can I use Celery or background workers?
Faable Deploy runs web services — a process that serves HTTP on $PORT. A separate worker process type isn’t supported today, so background jobs need an external runner or an in-process scheduler.
Related
- What the Builder Expects — full detection and start-command rules
- Deploy FastAPI · Deploy Flask · Deploy Node.js Express
- Environment & Releases · Custom domains · WAF
- Add authentication to your app — Faable Auth is included in the same subscription
Last updated on