Deploying Django without writing a Procfile by hand
A Django app already knows almost everything a host needs to run it: requirements.txt says whether Celery exists, psycopg says it needs Postgres, manage.py says what to migrate. Most PaaS onboarding asks for that information anyway, as a config file or a dashboard form — a second place for it to drift out of sync with the actual code.
What’s detected, not configured
- Django itself is detected from
requirements.txt,pyproject.toml, or amanage.pythat imports it — no framework flag to set. python manage.py migrate --no-inputruns as the release step, before the new version takes traffic.python manage.py collectstatic --noinputruns in the build step automatically — the same convention Heroku’s Python buildpack used, so WhiteNoise or anySTATIC_ROOTsetup just works with no Dockerfile line to add. An app that doesn’t usedjango.contrib.staticfilesis simply unaffected.psycopgin dependencies provisions Postgres and injectsDATABASE_URL; a Redis client (redis,django-redis,rq) does the same forREDIS_URL.- Celery, Celery Beat, RQ, Huey, Dramatiq and Django-Q are each offered as a worker process automatically when they’re a dependency — not something to wire into a Procfile from scratch.
railyard create my-app https://github.com/me/my-app --addons postgres,redis
or the same URL pasted into the dashboard once GitHub’s connected.
Processes, the 12-factor way
web, worker and a scheduled beat process are the same build, each started with a different command — not three separate apps to keep in sync by hand:
web: gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT
worker: celery -A myproject worker --loglevel=info
beat: celery -A myproject beat --loglevel=info
Skip the Procfile entirely and detection offers the worker on its own, based on whichever job library is already a dependency. Scale any of them independently:
railyard scale my-app worker=2
The one thing worth checking before the first deploy
Gunicorn’s default assumption is a WSGI module at wsgi:application — if the project layout puts it somewhere else, that’s the one line worth setting explicitly in the start command rather than relying on the guess. Everything else downstream of that (migrations, static files, workers) follows automatically once the web process actually boots.
Config and secrets
DJANGO_SETTINGS_MODULE, SECRET_KEY, and any feature flag live under Variables, or railyard env:set my-app KEY=VALUE — plain env vars, not baked into the image, so the same build runs unchanged from staging through production. DATABASE_URL and REDIS_URL resolve per environment automatically from whatever’s attached on the canvas.
Full reference: Django docs, Workers and scheduled jobs.