In this article, you’ll learn how to prepare a Django REST Framework (DRF) API for production deployment inside a Docker container.
This post is part of the Dockerized Django Back-end API with Angular Front-end Tutorial. Check out all the parts of the tutorial there.
In the last part of the tutorial, we’ve learned how to add object level permissions in DRF so users can only have access to their own tasks. In this blog post, we’ll learn how to prepare our Django REST framework API for production.
To get the code to where we left off in the last blog post, use:
$ git checkout v1.14
We’ll follow the deployment checklist from the official Django documentation.
Creating a Production Settings File
Ideally, one should have a development version and a production version of Django’s
settings.py. Therefore, we will create a new file containing production settings.
First of all, make a copy of the
settings.py file called
settings_prod.py. This will become the file with production settings.
Next, we’ll rename our existing
django/todoproj/settings.py file to
settings_dev.py. This is the file with development settings.
We now have two settings files, but we don’t know how to select between them.
Create a new
settings.py file in
django/todoproj/ with the following contents:
Here, we simply load the
settings_dev.py values if the
DJANGO_SETTINGS environment variable exists and is set to
dev. If the variable doesn’t exist, we’ll load the
settings_prod.py values instead.
Therefore, by default, we load production settings as these are more restrictive and we don’t want to load development values by mistake.
Since we’re using
docker-compose in our local development environment, we can easily add the
DJANGO_SETTINGS environment variable in our
docker-compose.yaml file, under the
dj service, using the
Therefore, when developing locally, we’ll be using the settings in
Most Important Production Settings
As described in the Django deployment checklist, some of the checks below can be automated using the
check --deploy option by running the following command against your production settings:
$ python manage.py check --deploy
All of the following changes should be made in the
Keep the secret key used in production secret. I guess that’s pretty obvious…
The documentation advises not to commit the production secret key to source control. You should instead load the key from an environment variable as follows:
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
In a later part of this tutorial, we’ll set the
DJANGO_SECRET_KEY environment variable in the Google Cloud Console, when we’ll learn how to deploy the container.
If you need a new value for your production secret key, simply go to the first part of the tutorial where we create a Django project. Just create a new throwaway Django project and use the secret key from the
settings.py file of that project as a production key for your existing project.
You must never enable debug in production. If you do so, you can leak a lot of information such as excerpts of source code, variables, settings, etc.
False in your
settings_prod.py file like so:
DEBUG = False
Django also has a setting for protecting your site against some CSRF (Cross-Site Request Forgery) attacks.
ALLOWED_HOSTS variable, you should put the domain name or the IP address where your web app is hosted. If you’re hosting on Google Cloud, you should put the IP address of the instance here. We’ll learn how to create an instance in a future post.
In production, we’ll use a Postgres database hosted on Google’s Cloud SQL. We will change the values here after we create the Cloud SQL database in a future tutorial.
As we won’t be doing cross-site requests in production, we won’t need the CORS package in the deployed app. Therefore, you can remove the
corsheaders package and its related variables from
Since we’re using JWT authentication in our app, we should use HTTPS in order to avoid sending access tokens in clear.
If you do decide to use HTTPS, I’d suggest you serve your site through Cloudflare, as you’ll get an SSL certificate out-of-the-box free of charge. Using Cloudflare will also address protection against DDOS attacks.
In this case, you’d need to set
True to avoid transmitting the CSRF and session cookies over HTTP accidentally.
Serving the Django App Through uWSGI
There is another concern that we need to address.
At the moment, we are serving our Django API through the development server. The development server is specified in the
docker-compose.yaml with the directive:
command: python manage.py runserver 0.0.0.0:80
This is not good for production. We should instead use a production-grade server such as gUnicorn or uWSGI.
I’ve went with uWSGI and I’ll show you next how to configure it.
Adding uWSGI as a requirement
First of all, add uWSGI to the list of dependencies in the
Running uWSGI as non-root
Next, we need to specify how to launch the uWSGI server. We will do this by adding the following to the
Let’s explain a bit. With this directive, we’ll now start uWSGI running an HTTP server (
--http) listening on port 8080, using the configuration file
uwsgi.ini which we’ll create in a second.
Why are we starting uWSGI on port 8080? It’s because we want to run uWSGI as a non-root user for security reasons. Ports below 1024 can only be opened by root, so we need to choose a higher port number for the server to listen to. See this server fault answer for a more detailed description of the issue.
On the production server on Google Cloud Platform, we’ll simply forward incoming HTTP requests from port 80 to port 8080 on the Django container.
Just to make it clearer, let’s look over the bigger picture again.
When developing, we’ll use
docker-compose with the
python manage.py runserver command. This command in the
docker-compose.yaml file will override the
uwsgi command in the
Dockerfile, which is what we want.
In production, we won’t be using
docker-compose, so Google Cloud will run the
uwsgi command in the
Creating the uwsgi.ini file
Now, in the
django directory, create a new
uwsgi directory. In it, let’s create the
uwsgi.ini file with the following contents:
Here, we use
gid to tell uWSGI to run as the
www-data user instead of root.
module argument tells uWSGI to load a WSGI module. We pass it
todoproj.wsgi. This is the WSGI module that the
django-admin.py startproject command created when we ran it in the first part of the tutorial.
I want to mention that this
uwsgi.ini configuration file is incomplete at the moment. We will add some more options in a future post, once we’re ready to deploy the app on Google Cloud Platform.
Notice we’re using the
--http option in the
uwsgi command because we want to expose uWSGI directly to the public.
This means that uWSGI will handle both serving static files and also dealing with requests directed to the Django API.
If the requests are directed towards the WSGI web app (i.e. the Django API), NGINX can forward theses requests accordingly. Thus, the uWSGI application server will have less load to handle, giving you better overall performance.
The issue here is that it would be quite complex to include NGINX on the Docker image with Django. You’d basically have to manage both NGINX and the Django project on one image, which would not be very good regarding separation of concerns.
It’s discouraged to have two or more services running on one container.
This is the reason why I chose to serve all HTTP requests directly with uWSGI.
If you want to have separation of concerns, then you’d want to use Kubernetes. You’d then have one container running NGINX and another one running the uWSGI server.
To get the code to this stage, see my git commit or use:
$ git checkout v1.15
In this part of the tutorial, we’ve started preparing our Django REST framework API for production deployment inside a Docker container. We’ve learned about the key Django production settings to use and we’ve also seen how to configure uWSGI for serving the Django API.
In the next part of the tutorial, we’ll see how to prepare the Angular app for production deployment.
Credit: For this tutorial, I’ve used the following resources: