DRF + uWSGI + Docker

Preparing a Django DRF API for Production Using uWSGI in Docker (Part 9)

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 environment key:

Therefore, when developing locally, we’ll be using the settings in settings_dev.py.

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 django/todoproj/settings_prod.py file.

SECRET_KEY

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.

DEBUG

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.

Set DEBUG to False in your settings_prod.py file like so:

DEBUG = False

Environment-specific Settings

ALLOWED_HOSTS

Django also has a setting for protecting your site against some CSRF (Cross-Site Request Forgery) attacks.

In the 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.

DATABASES

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.

CORS

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 settings_prod.py:

HTTPS

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 CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE to 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 django/requirements.txt file:

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 django/Dockerfile file:

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 Dockerfile.

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 uid and gid to tell uWSGI to run as the www-data user instead of root.

The 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.

Caveats

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.

Now, in an ideal setup, you’d most likely want to have NGINX acting as a reverse-proxy, serving static files (e.g. javascript, CSS, images, etc.) and managing client connections on its own.

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

Summary

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:

About the Author Dragos Stanciu

follow me on:

Subscribe

Like this article? Stay updated by subscribing to my weekly newsletter:

Leave a Comment:

3 comments
Add Your Reply