← Blog

Deploying Django without writing a Procfile by hand

Railyard

October 9, 2026

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 a manage.py that imports it — no framework flag to set.
  • python manage.py migrate --no-input runs as the release step, before the new version takes traffic.
  • python manage.py collectstatic --noinput runs in the build step automatically — the same convention Heroku’s Python buildpack used, so WhiteNoise or any STATIC_ROOT setup just works with no Dockerfile line to add. An app that doesn’t use django.contrib.staticfiles is simply unaffected.
  • psycopg in dependencies provisions Postgres and injects DATABASE_URL; a Redis client (redis, django-redis, rq) does the same for REDIS_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.

Push a Django app right now.

requirements.txt is read automatically — gunicorn, migrations and Postgres all wired up.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.