Deploy Django on Railway with this Dockerfile

By Justin

Deploy Django on Railway with this Dockerfile
Railway is an effective way to deploy Django into production. This blog post serves as a simple how-to guide so you can do the same.
This guide was designed specifically for the SaaS Foundations course so consider the course's code while using this guide.
But, why the guide?
Django uses a Python runtime. Some hosting providers support Python out of the box. Some do not.
Containers (e.g. Docker Containers) are a simple way to package up any runtime along with any application. If you think of it as a mini virtual machine, you're not far off.
In other words, containers one of the easiest ways to deploy any application if the hosting platform supports containers. Railway supports containers.
Railway also supports auto builds of containers through a technology called nixpacks. Auto building containers is really nice until it isn't. Sometimes auto builds fail to install system-level dependencies or other requirements your application might need. Don't get me wrong, nixpacks can be excellent, I just find it easier to use my own Dockerfile.
This guide will show you how to use your own Dockerfile with deploying Django to Railway (or really any other hosting provider that supports containers). Using your own Dockerfile can present new challenges you are not used to. Luckily for us Django devs, I got you covered with this guide -- I will literally give you the Dockerfile you need to deploy Django to Railway.
The Dockerfile is a static document that tells Railway how to build and run your application and your application's operating system environment or "mini vm" (really a container). This process is the same if you wanted to deploy this exact same Dockerfile container to another hosting provider like Google Cloud Run or Docker Compose or Kubernetes (and so on).
Two key aspects of this guide are:

1 - Create a Railway Account

Sign up for railway our referral link or directly at railway.com.

2 - Django project setup

To ensure we are all on the same page, regardless of if you completed the SaaS Foundations course, here's how we setup this Django project:
bash
# Create project directory
mkdir -p ~/dev/saas-foundations
cd ~/dev/saas-foundations

# macos/linux: Create and activate a virtual environment
python3 -m venv venv
source venv/bin/activate

# windows: Create and activate a virtual environment
c:\Python312\python.exe -m venv venv
.\venv\Scripts\activate

# Create requirements.txt
echo "Django>=5.0,<5.1" >> requirements.txt
echo "gunicorn" >> requirements.txt

# install requirements
pip install pip --upgrade
pip install -r requirements.txt

# Start the django project
mkdir -p src
cd src
django-admin startproject cfehome .
In src/cfehome/settings.py add ".railway.app" to ALLOWED_HOSTS:
python

ALLOWED_HOSTS = [
    # ...
    ".railway.app",
    # ...
]
To go full production, ensure you do at least the following:
  • DEBUG = False
  • A new SECRET_KEY (generate one using this guide) loaded from environment variables (either os.environ or python-decouple or otherwise)
  • Include your Railway domain or custom domain in ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS = []. CSRF_TRUSTED_ORIGINS should be in the format like https://*.railway.app or https://*.yourdomain.com
  • Review the Django docs for the deployment checklist
This Django project is not truly production ready yet. To understand how to go full production with this guide in mind, watch the SaaS Foundations course.

3 - Create Dockerfile

Now that we have a Django project, we need to create a Dockerfile to containerize our application.
First create a blank Dockerfile in the root of your project.
bash
cd ~/dev/saas-foundations
echo "" >> Dockerfile
As a reminder, our project currently has:
  • requirements.txt in the root directory next to Dockerfile
  • src/ is the directory with Django code
  • The DJANGO_SETTINGS_MODULE is cfehome.settings
  • We'll use both the Python Image Library (pip install pillow) and Postgres at some point in the future of this Django project
With this in mind, here's the Dockerfile:
dockerfile
# Set the python version as a build-time argument
# with Python 3.12 as the default
ARG PYTHON_VERSION=3.12-slim-bullseye
FROM python:${PYTHON_VERSION}

# Create a virtual environment
RUN python -m venv /opt/venv

# Set the virtual environment as the current location
ENV PATH=/opt/venv/bin:$PATH

# Upgrade pip
RUN pip install --upgrade pip

# Set Python-related environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Install os dependencies for our mini vm
RUN apt-get update && apt-get install -y \
    # for postgres
    libpq-dev \
    # for Pillow
    libjpeg-dev \
    # for CairoSVG
    libcairo2 \
    # other
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Create the mini vm's code directory
RUN mkdir -p /code

# Set the working directory to that same code directory
WORKDIR /code

# Copy the requirements file into the container
COPY requirements.txt /tmp/requirements.txt

# copy the project code into the container's working directory
COPY ./src /code

# Install the Python project requirements
RUN pip install -r /tmp/requirements.txt

# database isn't available during build
# run any other commands that do not need the database
# such as:
# RUN python manage.py collectstatic --noinput

# set the Django default project name
ARG PROJ_NAME="cfehome"

# create a bash script to run the Django project
# this script will execute at runtime when
# the container starts and the database is available
RUN printf "#!/bin/bash\n" > ./paracord_runner.sh && \
    printf "RUN_PORT=\"\${PORT:-8000}\"\n\n" >> ./paracord_runner.sh && \
    printf "python manage.py migrate --no-input\n" >> ./paracord_runner.sh && \
    printf "gunicorn ${PROJ_NAME}.wsgi:application --bind \"[::]:\$RUN_PORT\"\n" >> ./paracord_runner.sh

# make the bash script executable
RUN chmod +x paracord_runner.sh

# Clean up apt cache to reduce image size
RUN apt-get remove --purge -y \
    && apt-get autoremove -y \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Run the Django project via the runtime script
# when the container starts
CMD ./paracord_runner.sh
A couple important points:
  • paracord_runner.sh is a bash script that runs after the container is built and thus all environment variables are available (or at least should be available). The DATABASE_URL is the key environment variable here but other production-required environment variables are also needed when this script runs.
  • We run python manage.py migrate --no-input within paracord_runner.sh to ensure our runtime database has been fully migrated at runtime. This is important because the production database may or may not available during the build process. This runs right before gunicorn starts the Django project.
  • We use gunicorn to run the Django project. Gunicorn is a production-ready WSGI HTTP server that can be used to run Django, FastAPI, and many other Python web applications.
  • We bind gunicorn to [::]:$RUN_PORT instead of 0.0.0.0:$RUN_PORT to support both IPv4 and IPv6 communication. If you want Django private, this is key. Railway currently only supports IPv6 for private communication between services.

Create railway.json

Using Railway's Config as Code, we need to ensure this Dockerfile is being use to build our container and run our application.
To do this, create railway.json in the root of your project:
bash
cd ~/dev/saas-foundations
echo "" >> railway.json
In railway.json, add the following:
json
{
    "build": {
        "builder": "DOCKERFILE",
        "dockerfilePath": "./Dockerfile",
        "watchPatterns": [
            "requirements.txt",
            "src/**",
            "railway.json",
            "Dockerfile"
        ]
    }
}
At this point, we have the foundation in place to deploy our Django project to Railway. All you have to do now is commit your code to a git repository and connect Railway to that repository. Railway will build and deploy your app.

Environment Variables on Railway

Instead of being called Environment Variables, Railway uses the term Variables.
"Variables provide a way to manage configuration and secrets across services in Railway."
The variables are used for:
  • The railway build process
  • The railway service runtime (deployment) process
  • Various railway cli commands
At the time of this writing, Railway requires you to set environment variables from the console. At some point, Railway might update the cli so you can automatically set these variables but for now, it does not appear to be possible.
Discover Posts
Deploy Django on Railway with this Dockerfile