Skip to Content

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 forWhich gives it
requirements.txt, pyproject.toml or PipfileYour dependencies — installed into a virtualenv
manage.py plus a package containing wsgi.pyDjango 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:$PORT

gunicorn 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 PostgreSQL

2. 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.link

Add 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:

  1. Create a Project and an App in the Faable Dashboard .
  2. Click Link repository and pick your Django repo.
  3. 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 deploy

Your 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 migrate

Faable 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.12

Or with .python-version, or requires-python in pyproject.toml. First match wins, in that order.

Troubleshooting

  • DisallowedHost in the logsALLOWED_HOSTS doesn’t include the domain you’re visiting. Add <app>.faable.link and 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 + a wsgi.py package. Either fix the layout or declare startCommand in faable.json.
  • CSRF verification failed on forms — add your full origin (with https://) to CSRF_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.

Last updated on