Preparing Your Django Application for Production
Table of Contents
Getting started with Django is quick and easy. By default Django comes configured for a local development environment, and while you can create elaborate web applications that work on your local machine, at some point you’ll want to get your web application production-ready.
Many of the default settings in a Django application can be left as-is, but there are a few settings that almost certainly need to change before your application is ready for production.
In this article we’ll cover common changes required to get your application configured for a production environment. For the demonstration purposes in this article, we’ll assume a Django application called `foo` created with the Django-admin startproject command.
Managing environments with the DJANGO_SETTINGS_MODULE
When you’re developing a production application, you typically will have multiple environments in which the application will run. Developers will need to run the application locally, and you may have a number of staging environments for testing purposes, as well as a production environment that will be available to the public. Each of these environments will have a configuration specific to that environment.
Using DJANGO_SETTINGS_MODULE
Django provides a convenient way of specifying different settings files to use for your application by using the DJANGO_SETTINGS_MODULE
environment variable. One option for managing your various environments is to create a different configuration file for each of your environments and use DJANGO_SETTINGS_MODULE
to specify the file for your environment.
The advantage here is that you can easily see what the exact configuration is for each environment, and it can be a convenient way to manage all your configuration in one place.
There are, however, a number of drawbacks to this approach:
- Configuration changes are tied to your application code.
- Sensitive access keys and passwords are saved as plain text in your code.
- You will need a file per environment which makes it complicated to manage if you want to be able to dynamically create or destroy environments.
Shared settings
If you choose to have multiple settings files, consider putting the shared configuration into another file and importing it into your environment-specific files. For example, let’s say that we have the configuration that’s common to all of our environments in a file called shared_settings.py in the foo/ directory.
We could then create a local_settings.py file that looks something like this:
ENV = 'local'
DEBUG = True
from .shared_settings import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Using environment variables
Another option for configuring your Django application is to use environment variables.
Using environment variables allows you to decouple your application configuration from your application code. This means you can easily run your application in as many environments as you’d like, without having to modify your application code.
It also allows you to inject sensitive information required to configure your application and easily store it securely elsewhere.
You could still have multiple settings files if you wish to avoid if-statements when dealing with environment-specific configurations. For example, you could have a local_settings.py for local development and a remote_settings.py for when your application is being hosted on a remote server.
In either case, using environment variables gives you added flexibility and security. As an example, let’s take a look at what a remote_settings.py might look like:
import os
ENV = os.getenv('ENV')
DEBUG = False
from .default_settings import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME', 'foo'),
'USER': os.getenv('DB_USER', 'foo'),
'PASSWORD': os.getenv('DB_PASS', 'bar'),
'HOST': os.getenv('DB_HOST', '127.0..0.1'),
'PORT': os.getenv('DB_PORT', '5432'),
}
}
Here, we have decided that our application will work with PostgreSQL across all environments, but we allow environment variables to be used to configure where and how the database will be accessed.
Configuring for production
Django allows for many different components to be configured, replaced or excluded entirely, but the majority of the defaults can work in most web applications.
A number of the default settings, however, are designed to allow for you to start with local development right away and are not appropriate for most production environments. Let’s take a look at the main pieces that need to change in order to get a simple web application ready for production.
DEBUG
flag
For development and debugging purposes, Django has a debug flag for turning certain features that are useful for development on and off for production, as some features could be a security concern. Whenever you’re making your application ready for production, you should set DEBUG = False
.
ALLOWED_HOSTS
When you set DEBUG = False
, Django checks that the HTTP Host header matches one of the entries in your ALLOWED_HOSTS
setting. This is a security measure meant to protect against HTTP Host header attacks. This setting needs to be set to allow the host name where you are making your application available. If you’re dynamically creating environments, you may want to allow hosts to be specified as environment variables so that they can be injected into the application as it launches.
Here is an example of what this might look like in remote_settings.py
import os
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'mydefault.com').split(',')
Databases
Django provides an ORM that lets you define data models, allowing you to access persisted data without worrying about what database backend the data will be stored in.
As long as you haven’t made any modifications to the ORM or used third party extensions that add database-specific functionalities, it’s easy to change between database backends by simply changing the DATABASES
configuration in your settings.
You’ll want to be careful about changing database backends for an established project. However, as different databases handle various scenarios differently, your code might work well with one backend, but not as well with another. If you do take this route, you should do extensive testing.
By default, Django comes configured to use SQLite3, which is convenient for local development and testing. In a production environment, however, you’ll likely want to use something else.
Note: there are a number of reasons you’d want to change your database configuration for local development and testing as well. It’s a good idea to have your testing and local development environments match your production environment as closely as possible to avoid a situation where things work in development but don’t work in production.
The most popular open source databases to use with Django are PostgreSQL and MySQL, but Django also officially supports SQLite and Oracle, along with a number of third party backends that allow you to use other databases.
Each database backend might have its own quirks, but configuring Django to access them is pretty similar.
For demonstration purposes, below are some examples of how to configure PostgreSQL and MySQL.
PostgreSQL:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME', 'foo'),
'USER': os.getenv('DB_USER', 'foo'),
'PASSWORD': os.getenv('DB_PASS', 'bar'),
'HOST': os.getenv('DB_HOST', '127.0..0.1'),
'PORT': os.getenv('DB_PORT', '5432'),
}
}
MySQL:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql,
'NAME': os.getenv('DB_NAME', 'foo'),
'USER': os.getenv('DB_USER', 'foo'),
'PASSWORD': os.getenv('DB_PASS', 'bar'),
'HOST': os.getenv('DB_HOST', '127.0..0.1'),
'PORT': os.getenv('DB_PORT', '3306'),
}
}
As you can see, the only difference between these two configurations is where we specify the database engine (and the default ports are different for each database).
Whatever database backend you ultimately decide to use, read the documentation to understand its configuration, along with the limitations it may have in terms of what Django features are supported by the backend.
Static files
Django offers a framework for managing static files and generating the correct URI to access that file as it is configured in your environment. This is very convenient, as you can reference your static files within your code and don’t have to worry about the details of your current environment.
Django also provides a convenient command ./manage.py collectstatic that collects the static files from all your Django applications (and any other custom directories you might configure) and then dumps them into a local folder defined by the STATIC_ROOT
setting.
Single-server configuration
The simplest configuration for hosting static files is to build off the default configuration and simply set the STATIC_ROOT
setting to a directory on your server where you will host the static files.
For example, set STATIC_ROOT = "/var/www/foo.com/static/"
in your settings file. When you run collectstatic in this configuration, the static files will all end up in the /var/www/foo.com/static/
directory on your server. The urls that Django will generate to access your static assets will point to the /static/ path.
When Django is configured for production and DEBUG=False, however, it will not serve up static files.
According to Django’s documentation, their method for serving these files in development is “grossly inefficient and probably insecure.” You will need to configure your web server (nginx, apache, etc…) to serve requests to the /static/ path from the /var/www/foo.com/static/
directory. For a small website, this setup works, but for most projects you will likely want to do something a little bit more complex.
Multi-server configuration
For websites that service a lot of requests, it’s a good idea to host your static files from a different server, allowing you to lessen the load on your application server.
One option here is to actually host the static files on a separate server. There are a lot of different ways to get your static files onto another server. Solutions to this problem range from simply using external tools like rsync or fabric to making a custom StaticFileStorage
backend.
Whichever path you take, you’ll need to change the STATIC_URL
setting in your settings file to point to a URL that can be routed to your other server (e.g. https://static.foo.com/bar/
).
Cloud hosting and content delivery networks (CDNs)
Another way to reduce the load on application servers for high traffic sites is to host the static files on a cloud service or use a CDN (Content Delivery Network).
A popular configuration for hosting static files in production is to host them on AWS S3 (or similar service) and then use a CDN like CloudFront to cache your static files in a network of global servers.
This lessens the load on your application servers and allows for faster response times for downloading your static files around the world. If you go this route, you’ll need to make a custom StaticFileStorage
backend or use a Django extension like django-storages. It’ll handle most of the details for you, as well as allow you to specify a few simple settings to handle your custom configuration.
If you choose to use a CDN (which is advisable), it’s arguably less important to host your static files on a separate server, as most requests will be handled directly by the CDN. Another third party tool that operates under this assumption is WhiteNoise, which keeps your configuration simple and keeps your entire application in a single unit bundle.
Media files (uploads)
Setting Django up to support file uploads is very similar to configuring static files – the problem with media files is mostly the same as static files with the exception of handling the actual file upload.
The settings for handling media uploads in Django are even named similarly to the configuration for static files. There is a MEDIA_URL
setting for indicating a base path for serving requests to fetch media and a MEDIA_ROOT
setting to indicate where the uploaded files should be stored.
Like static files, if you have a small website with a single server, you can get by with a simple configuration that stores the uploaded files on the application server in a directory that sits behind your web server (nginx, apache, etc…).
When you need to scale beyond a single server, however, this solution doesn’t scale. If a piece of media is uploaded to one server and another server receives a request for that media, it won’t be available.
Once again, the solution is to offload the storage of your media files to some other server to handle these requests. Some of the third party solutions for static file handling (like django-storages) also provide a solution to the problem posed by handling media files. These are probably worth looking into for the sake of time – just in case there’s already an existing solution.
What next?
After you’ve configured your application for multiple environments and figured out all your production settings, you can decide how you’ll deploy your application!
Actually running your application is a whole different series of decisions that needs to be made: including what web server to run, what application server to run, what hosting environment to use, and whether or not you’ll use virtualization.
Ultimately, you’re going to want to automate the deployment process to make repeating and scaling easy moving forward. If you’ve done a good job of configuring your Django project for running in a production environment, none of these decisions will have to impact how your application runs and functions.
Ideally, somebody else will be able to come along, take the application as we have configured it and (with knowledge about what environment variables control which settings), run the application as they see fit.