Deploy an instant messaging app with Django 🚀

I'll show you the simplest way to deploy an instant messaging app with Django, with full async support.

This will include:

  • deploying a production web server
  • deploying a production database
  • full async support and serving static files

I'll also show you some good practices for deploying Django apps to production.

This guide uses the app we built in my previous guide: The simplest way to build an instant messaging app with Django 🌮, but you can also start with the finished code from the previous tutorial.

I've made a easy-to-follow video guide (featuring me 🏇🏿) that goes along with the step-by-step instructions. Here's the video:


Let's get going 🚀

0. Setup

Option A. Do the previous guide (The more beneficial way)

If you want to do the previous guide, the link to the previous guide is here: The simplest way to build an instant messaging app with Django 🌮.

Option B. Start with the previous guide's code (The faster way)

If you want to start without doing the previous guide, here's the link to the finished code: Repo link for The simplest way to build an instant messaging app with Django 🌮 Just pull it with git, run the setup instructions, and get started

git clone https://github.com/PhotonDesigner/instant-messenger
cd instant-messenger
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver

You should see something like:

Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Section 1. Adding different settings files for our Django deployment and production environments

Now that you've got your Django app running locally, we'll add different settings files for our Django deployment and production environments.

We want to be able to have different settings for our local development environment and our production environment. This is good practice for deploying Django apps (I always do it).

So, we'll start by adding different settings files for our Django deployment and production environments.

Install the extra Django packages that we'll use for deployment:

pip install django daphne python-dotenv dj_database_url 'whitenoise[brotli]' psycopg2-binary

We're using the packages from the previous guide, so Django and Daphne are already installed. I've included them above in case you've created a new virtual environment.

Delete your old settings.py file

  • Delete your old settings.py file (i.e., the one in core/settings.py)

Create your base settings that will be shared by all environments

  • Create a new folder called settings in core:
  • Create a new file called base_settings.py (core/settings/base_settings.py) and add the below into it:
# core/settings/base_settings.py
from pathlib import Path
import os
from dotenv import load_dotenv
import dj_database_url
import logging


REPO_DIR = Path(__file__).resolve().parent.parent.parent
CORE_DIR = REPO_DIR / "core"

DEBUG = "RENDER" not in os.environ


ALLOWED_HOSTS = []
CSRF_TRUSTED_ORIGINS = []

logger = logging.getLogger(__name__)


# Application definition

INSTALLED_APPS = [
    'daphne',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'sim',
]

ASGI_APPLICATION = 'core.asgi.application'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'core.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'core.wsgi.application'

# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Create your development settings

  • Create a new file called development_settings.py in your settings folder and add the following code to it:
# core/settings/developer_settings.py
from .base_settings import *


logging.info("In development 🏗️")

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',  # Fast, insecure hasher. This speeds up your tests in development.
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': REPO_DIR / 'db.sqlite3',
    }
}

SECRET_KEY = 'django-insecure-secret-key-for-development-1234'

Now we want to check that we've connected the development settings to our Django app correctly. To do this, we'll run our Django app with the development settings and see if it works.

python manage.py runserver --settings=core.settings.development_settings

You should see something like:

Starting ASGI/Daphne version 4.0.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Because we're now using this new settings file for our development environment, we'll need to run python manage.py runserver --settings=core.settings.development_settings whenever you run the Django app locally.

(Side note: This is not a burden. You can also set the DJANGO_SETTINGS_MODULE environment variable to core.settings.development_settings instead of using the --settings flag, or set an alias)

Troubleshooting:

If you see the below, you probably are still running python manage.py runserver and aren't connecting to the development_settings.py settings:

CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.

Create your production settings

  • Create a new file called production_settings.py in your settings folder and add the below code to it.
from .base_settings import *


logging.info("In production 🏭")

SECRET_KEY = os.environ.get("SECRET_KEY")  # We'll get this from the environment variable on the server.

DATABASES = {
    'default': dj_database_url.config(
        default=os.environ.get("DATABASE_URL"),
        conn_max_age=600
    )
}


RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME')
if RENDER_EXTERNAL_HOSTNAME:
    CSRF_TRUSTED_ORIGINS.append(f"https://{RENDER_EXTERNAL_HOSTNAME}")
    ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME)


# Setup static file collection for Render.
if not DEBUG:
    STATIC_URL = 'static/'
    # Tell Django to copy statics to the `staticfiles` directory
    # in your application directory on Render.
    STATIC_ROOT = os.path.join(REPO_DIR, 'staticfiles')
    # Turn on WhiteNoise storage backend that takes care of compressing static files
    # and creating unique names for each version so they can safely be cached forever.
    STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

You'll notice above that we are: - using whitenoise to serve our static files from our server - adding our production database (which we'll create shortly)

Section 2. Deploy your Django production database

Our instant messaging app uses a database to store our messages. We'll create a new database for our Django app using Render.

Create a PostgreSQL database on Render

  • Create an account at https://render.com/ and then go to the Render dashboard
  • Click on the 'New' button and then click on 'PostgreSQL'
  • Add a name for your database: 'instant-messenger-db' (Any name will do)
  • Use the other defaults. Optionally change your region to somewhere close to you.
  • Choose your 'Instance Type' (free or paid) and click to deploy

Get the database connection string to connect to your database

  • After it's deployed, click on the database in your Render dashboard.
  • Go to your database's page on Render and copy the "External Database URL".

The "External Database URL" is the location and password to your database, so keep it safe. We'll use it shortly to connect our Django app to our database.

Section 3. Deploy your production Django app

Add your requirements.txt file

  • To create your requirements.txt file with your installed packages, run:
pip freeze > requirements.txt

We need to do this so that Render knows what packages to install for our Django app.

Create a simple build script for Render to run when deploying

We need to create a simple build script that Render will run when deploying our Django app. When deploying, this will install our packages, collect our static files, and run our database migrations.

  • Create a file called build.sh at your root level (i.e., with no folders above it in the project) and add the below to it:
#!/usr/bin/env bash

# Our render.com build script.

set -o errexit

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

python manage.py collectstatic --no-input
python manage.py migrate

Push your code to a GitHub repo

To deploy our Django app, we'll need to push our code to GitHub. Once setup, Render will then automatically deploy our app from GitHub whenever we push to our master branch.

So:

  • Create a new private repository on GitHub
  • Push your code to GitHub

(If you are a beginner and need help creating a Github repo, check out the GitHub docs)

Create a new web service for your Django app using Render

  • Create an account at Render.com and then go to the dashboard
  • Click on the 'New' button and then click on 'Web Service'
  • Select to build and deploy from a GitHub repo, and select your repo

Complete the form to configure the new web service

  • Add 'instant-messenger-app' as the name
  • Then:

a. Add Daphne to the 'Start command' field

  • In the start command field, enter the below to use Daphne to run our Django production app.
daphne -b 0.0.0.0 core.asgi:application

b. Add the below to the "Build command"

This will run our build.sh file when deploying.

./build.sh

c. Choose your 'Instance Type' (free or paid)

d. Set the environment variables for your app

In the "Environment variables' section:

  • Add a key called: DJANGO_SETTINGS_MODULE with the value: core.settings.production_settings (No '.py' suffix).
  • Add a key called: DATABASE_URL with the value of your 'External Database URL' for the PostgreSQL database that we created above.
  • Add a key called: SECRET_KEY with the value of a random string. Generate one by running the below in your terminal. Paste the output into the value field:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'

5. Click to deploy

Troubleshooting:

If you get an error like CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False., you probably haven't set the DJANGO_SETTINGS_MODULE environment variable to core.settings.production_settings correctly.

Check that you've set it correctly in the 'Environment variables' section.

With any other error, click on logs for your web service and debug based on the error message. To redeploy, just push to your master branch on GitHub again.

4. Visit your deployed app 🚀

Once deployed, click on the link to your app in your Render dashboard to visit your app.

Congratulations 🎉

You've successfully deployed a Django app with async support to production 🚀

This is pretty cool. You can now invite anyone in the world to use your app.

Next steps

Here are a few extra features that we could add:

  • Add some realtime games in app. E.g.,, we could add a game of tic-tac-toe that you can play with friends in realtime.
  • Authentication. Currently, anyone can use our app with no personalization.
  • Payment. Let's say we want to charge people to use our app. We'll need to add a payment system.

If enough people are interested, I'll write my next guide to show you how to add one of these 🙂

P.S Django frontend at warp speed? ⚡️

Do you dream of creating Django products so quickly they break the space-time continuum? Yeah, me too.

Well, let me introduce you to the magic wand I'm building: Photon Designer. It's a visual editor that puts the 'fast' in 'very fast.' When Photon Designer gets going, it slings Django templates at you faster than light escaping a black hole.

Warning: may cause excessive joy and productivity.

Let's get visual.

Do you want to create beautiful django frontends effortlessly?
Click below to book your spot on our early access mailing list (as well as early adopter prices).