Deployment
#django
#railway
#postgresql

How to Deploy Django on Railway

If you want to deploy Django on Railway, the main challenge is not just getting a container to start.

Problem statement

If you want to deploy Django on Railway, the main challenge is not just getting a container to start. The real job is making your app run safely in production with the right process manager, database configuration, static files, environment variables, proxy-aware security settings, and verification steps.

Common Django Railway deployment failures are predictable:

  • Gunicorn is missing, so the web process never starts
  • ALLOWED_HOSTS does not include the Railway domain
  • static files are not collected as part of build/deploy
  • migrations are not applied in a controlled release step
  • DEBUG=True is left enabled
  • secrets are committed into Git instead of stored as environment variables

Railway can be a fast way to host Django, but it does not remove the need for production-safe Django settings.

Quick answer

To deploy Django on Railway safely:

  1. prepare production Django settings
  2. add Gunicorn, PostgreSQL URL parsing, and static file handling
  3. provision PostgreSQL in Railway
  4. connect your GitHub repo to Railway
  5. set environment variables such as DJANGO_SECRET_KEY, DATABASE_URL, ALLOWED_HOSTS, and CSRF_TRUSTED_ORIGINS
  6. set a Gunicorn start command
  7. run collectstatic during the build/deploy process
  8. run migrations as a controlled release step
  9. verify the site, admin, database access, and static assets

Railway is a good fit for straightforward Django deployments and smaller teams, but you still need to handle production settings correctly.

Step-by-step solution

1) Prepare your Django app for Railway deployment

Add production dependencies

Install the packages most Railway Django deployments need:

pip install gunicorn whitenoise dj-database-url psycopg[binary]
pip freeze > requirements.txt

If you use a different dependency workflow, export a valid requirements.txt before deploy.

What these packages do:

  • gunicorn: production WSGI server
  • dj-database-url: parses DATABASE_URL
  • whitenoise: serves static files from Django when you are not using external object storage or a CDN
  • psycopg[binary]: PostgreSQL driver

Update Django production settings

Use environment-driven settings. A minimal example:

import os
from pathlib import Path
import dj_database_url

BASE_DIR = Path(__file__).resolve().parent.parent

DEBUG = os.environ.get("DEBUG", "False").lower() == "true"

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

ALLOWED_HOSTS = [
    host.strip() for host in os.environ.get("ALLOWED_HOSTS", "").split(",") if host.strip()
]

CSRF_TRUSTED_ORIGINS = [
    origin.strip()
    for origin in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",")
    if origin.strip()
]

DATABASES = {
    "default": dj_database_url.parse(
        os.environ["DATABASE_URL"],
        conn_max_age=600,
        ssl_require=True,
    )
}

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True
SECURE_SSL_REDIRECT = not DEBUG
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

If your domain is fully HTTPS-only, add HSTS:

SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = False

Also run a deployment check locally before pushing:

python manage.py check --deploy

Verification check:

  • DEBUG must be False in Railway
  • SECRET_KEY must come from env vars
  • DATABASE_URL must point to PostgreSQL
  • ALLOWED_HOSTS must include your Railway domain and custom domain if used
  • CSRF_TRUSTED_ORIGINS must include full https:// origins

Configure static files for production

If Django will serve static files directly, configure Whitenoise:

INSTALLED_APPS = [
    # ...
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]

STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

STORAGES = {
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    }
}

Then test locally:

python manage.py collectstatic --noinput

Run collectstatic during the build/deploy process, not only as a one-off command after the app starts. Do not rely on the app’s local filesystem for persistent runtime-generated assets.

Verification check:

  • staticfiles/ is created during build or pre-release preparation
  • Django admin CSS loads with DEBUG=False
  • no missing static file errors during collectstatic

For user-uploaded media, do not rely on app-local storage for long-term production use. Use object storage for persistent media.

Define the app start command

Railway needs a web start command. For a typical Django project:

gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT

Replace myproject with your actual Django project module.

You can also keep this in a Procfile:

web: gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT

Verification check:

  • the WSGI module path is correct
  • gunicorn is present in requirements.txt

2) Create the Railway project and services

Create a new Railway project

In Railway:

  1. create a new project
  2. choose the GitHub deployment flow
  3. connect the repository with your Django app

Railway should detect it as a Python app if your repo includes the expected Python project files.

Add a PostgreSQL service

Provision a PostgreSQL service in the Railway project. Railway commonly exposes the connection details through environment variables. Confirm that your app service receives a valid DATABASE_URL. If not, copy the value manually into your app service variables.

Operational note: a hosted database is not a backup strategy by itself. Before risky schema changes, make sure you know how backups or restore points are handled for your environment.

Set required environment variables

At minimum, configure:

  • DJANGO_SECRET_KEY
  • DEBUG=False
  • ALLOWED_HOSTS=your-app-domain.up.railway.app
  • CSRF_TRUSTED_ORIGINS=https://your-app-domain.up.railway.app

Add any other app-specific secrets:

  • email credentials
  • Redis URLs
  • third-party API keys
  • Sentry DSN

Do not commit these values into Git.

3) Configure Railway build and deploy settings

Set the build behavior

Make sure Railway can install dependencies from requirements.txt.

If you need a specific Python version, pin it using the Python version mechanism supported by your Railway build setup, and verify it in build logs.

The main requirement is consistency between local development and the Railway build environment.

Set the start command

If Railway does not pick it up automatically, set the service start command to:

gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT

You can tune worker count later, but start simple and verify stability first.

Make static collection part of deploy

Your deployment must include:

python manage.py collectstatic --noinput

This should happen during build or as part of the deploy/release process so the running service starts with static assets ready. Do not treat a manual post-deploy collectstatic run as the normal production pattern.

Add a migration step

Run migrations as a controlled release step for the deployed version:

python manage.py migrate

Do not treat the deployment as healthy until migrations complete and database-backed pages have been verified.

Then verify migration state:

python manage.py showmigrations

If your deployment process supports a pre-deploy or release command, you can move migrations there later. Be careful with destructive migrations. Code rollback alone does not always fix a failed schema change.

4) Deploy Django on Railway step by step

Push the code and trigger the first deployment

Commit your changes and push to the branch connected to Railway:

git add .
git commit -m "Prepare Django for Railway deployment"
git push origin main

Watch the build logs for:

  • dependency installation success
  • collectstatic success
  • Gunicorn startup without import errors

Run database migrations safely

Once the new version is built and ready for release, run:

python manage.py migrate

For higher-risk production changes, confirm you have a database recovery plan before applying schema changes.

Verification check:

  • python manage.py showmigrations shows expected migrations as applied
  • database-backed pages load without errors

Verify static files

Verification check:

  • homepage CSS loads
  • admin CSS loads
  • browser dev tools show no obvious static 404s

If static assets are missing after deploy, fix the build/deploy sequence rather than relying on ad hoc runtime filesystem changes.

Create an admin user if needed

If this is a fresh environment:

python manage.py createsuperuser

Avoid bootstrap scripts with hardcoded admin credentials. Use a one-time secure creation flow.

5) Verify the Railway deployment

Check application health

Test:

  • the homepage
  • a database-backed page
  • the Django admin login
  • static assets
  • Railway logs for startup or runtime errors

A simple command-level check is still useful before deployment:

python manage.py check --deploy

Confirm production security basics

Verify that:

  • DEBUG=False
  • SECRET_KEY is not hardcoded
  • HTTPS works on the Railway domain or custom domain
  • SECURE_SSL_REDIRECT is active for HTTPS-only deployments
  • SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE are enabled
  • HSTS is enabled only after the domain is fully HTTPS-only

Test a restart and config persistence

Restart or redeploy the service and confirm:

  • environment variables are still present
  • the app reconnects to PostgreSQL
  • no startup-only assumptions were hiding config problems

6) Add a custom domain and production hardening

Attach a custom domain in Railway

Add your domain in Railway and update your DNS records as instructed there. Wait for domain verification and HTTPS issuance.

Update Django host and CSRF settings

When the custom domain is active, update:

ALLOWED_HOSTS=your-app-domain.up.railway.app,example.com,www.example.com
CSRF_TRUSTED_ORIGINS=https://example.com,https://www.example.com

CSRF_TRUSTED_ORIGINS must include the scheme.

Review baseline hardening

At minimum, review:

  • secure cookies
  • HSTS policy if the domain is fully HTTPS-only
  • logging level
  • error monitoring integration
  • trusted origins and proxy/TLS settings

Explanation

This setup works because it covers the parts Railway does not solve for you automatically: the Django app server, production settings, static file handling, proxy-aware security settings, and database configuration.

Gunicorn runs the Django app as a proper production web process. dj-database-url keeps database config environment-driven, which fits Railway well. Whitenoise is a practical default for smaller Django apps because it removes the need for a separate static file server. For larger apps or high static/media volume, object storage and a CDN are usually a better next step.

The important Railway-specific detail is that you should not depend on the service filesystem as durable application storage. That affects both media handling and any attempt to generate production assets after the app is already running.

Railway is a good choice when you want a simpler deployment path than managing your own VM, Nginx, process supervision, and TLS termination. If you need deeper network control, custom reverse proxy behavior, or stricter infrastructure ownership, a VPS or container platform may be a better fit.

Edge cases / notes

Static files vs media files

Whitenoise is suitable for static assets collected at build or deploy time. It is not a full solution for user-uploaded media. Store media in persistent object storage.

On Railway, do not assume the service filesystem is durable across deploys or restarts.

Proxy and TLS headers

Because TLS is terminated upstream, Django commonly needs:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True

Without that, secure redirects, absolute URL generation, and host handling may be wrong.

Migration risk

If a deploy fails after a destructive migration, rolling back code may not restore compatibility. Prefer backward-compatible migration sequences and know your database restore path before major releases.

Process sizing and timeouts

A single default Gunicorn command is enough to start, but you may need to tune workers and timeout behavior later based on memory use and request patterns.

When manual Railway deployment becomes repetitive

If you deploy multiple Django apps on Railway, the same work repeats: production settings scaffolding, env var validation, Gunicorn setup, static file config, and migration checks. Those steps are good candidates for reusable templates or deploy scripts. The first automation to add is usually preflight validation plus post-deploy smoke tests.

Before going deeper, review this Django production settings checklist to confirm your app is ready for any host.

If you want to understand the application server and reverse proxy layer outside a managed platform, see Deploy Django with Gunicorn and Nginx on Ubuntu.

If your project uses ASGI features such as websockets or long-lived connections, see Deploy Django ASGI with Uvicorn and Nginx.

Before launch, use this Django Deployment Checklist for Production to verify rollback readiness and operational checks.

FAQ

Do I need Gunicorn to deploy Django on Railway?

Yes, for a standard Django production deployment, Gunicorn is the usual choice. Railway needs a web process that serves your Django app, and Gunicorn handles that reliably.

How do I configure static files for Django on Railway?

Set STATIC_ROOT, run collectstatic, and use Whitenoise if Django will serve static files itself. The important part is sequencing: collectstatic should happen during build or deploy, not only as a one-off runtime command after the app is already serving traffic.

How do I run migrations during a Railway deployment?

Run python manage.py migrate as a controlled release step for the deployed version. Do not treat the deploy as healthy until migrations complete and database-backed pages have been verified.

Why is my Django app returning DisallowedHost on Railway?

Your Railway domain is probably missing from ALLOWED_HOSTS. Add the Railway-generated domain and any custom domain to the environment variable, then redeploy.

What should I do if a Railway deploy succeeds but the app fails after migration?

Check whether the issue is code, schema, or configuration. Rolling back app code may be enough for a safe migration, but not for destructive schema changes. For risky migrations, use backups or restore points and prefer backward-compatible release patterns.

2026 · django-deployment.com - Django Deployment knowledge base