How to use Nginx with your Django Docker Image

Why do you need a web server? Why should you use Nginx over Apache? And how do you use it for your Django application when you are using containers?

You need a web server because you need something which will handle requests from clients — HTTP clients such as web browsers. Web servers are very good at this and for serving static files but they can’t talk directly to your Django app so they have to forward the request to a WSGI.

Nginx is the most widely recommended web server for Python webapps. The main difference with Apache is that it does not spawn a new process like the usual Apache configuration and it’s pretty light on memory.

By the end of this article you’ll be able to create a Nginx image which will be used by your Django Docker image (which we’ve done in our previous post, How to containerize Django applications for beginners).

Create a Dockerfile for Nginx

As mentioned before, Dockerfile is a set of instructions on how to build a Docker image. Create a new file named Dockerfile with the following content

FROM nginx:alpine

RUN apk --no-cache add curl

WORKDIR /usr/src/app

COPY ./nginx.conf.template /usr/src/app/nginx.conf.template
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod 755 /usr/src/app/entrypoint.sh

ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

The first line is instructing to use the latest official Nginx image as the base image.

The second line installs curl for our image. I’ve added this because for services like AWS ECS, you’ll need curl to do the health check. It’s optional so you can omit it if you’re not going to use it.

WORKDIR sets the working path of our image to /usr/src/app. The next 2 lines copies the Nginx config file and the entrypoint script to our working directory. It is followed by changing the permissions of our entrypoint.sh to 755. 755 means that the script can be executed and read by anyone but it can only be written by the owner.

The last line sets the entrypoint to the script we’ve just copied. Entrypoints allow our image to be run as an executable. When a our image is run, it executes the entrypoint.

Create an Entrypoint script

Create a file entrypoint.sh in the same directory with the following content

#!/bin/sh

export DOLLAR='$'
envsubst < ./nginx.conf.template > /etc/nginx/nginx.conf
nginx -g "daemon off;"

One thing to note here is line 4. We use envsubst to populate nginx.conf with values from environment variables. In our example, we’ll need to populate 3 values which is discussed in the next step.

Customizing nginx.conf

Create a file nginx.conf.template in the same directory as your Dockerfile. Populate it with the following content

events {
  worker_connections 1024;
}

http {
    # NGINX will handle gzip compression of responses from the app server
    gzip on;
    gzip_proxied any;
    gzip_types text/plain application/json;
    gzip_min_length 1000;
    server_tokens off;

    # Fix CSS mime type
    include mime.types;

    server {
        listen $NGINX_PORT;

        location / {
            proxy_pass http://$CONTAINER_NAME:$WSGI_PORT;
            proxy_set_header X-Forwarded-For ${DOLLAR}proxy_add_x_forwarded_for;
            proxy_set_header Host ${DOLLAR}host;
            proxy_http_version 1.1;
            proxy_set_header Upgrade ${DOLLAR}http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_cache_bypass ${DOLLAR}http_upgrade;
        }
    }
}

For this config file, we have 3 values that we get from environment variables:

  • CONTAINER_NAME – the name of our Django app container. This value is the host where Nginx forwards the connections to. We set it if we are configuring a Task Definition in AWS ECS. You can also set it when you are using Docker Compose. I’ll provide a sample Docker Compose that you can use during development.
  • WSGI_PORT – the post used by the WSGI app. In our case, Gunicorn
  • NGINX_PORT – the port used by Nginx. It is usually set to 80

Build the image

docker build --tag nginx-django:latest .

This builds your custom Nginx image for use with your Django app

Running the Nginx image and your web app with Docker Compose

Note: Your image needs the Django webapp image that we did in the previous post.

First, install Docker Compose.

Once that is done, create a docker-compose.yml file with the following content

version: '3.7'

services:
  djangoapp: # set a name for your django app service
    image: djangoapp:latest # the image to use for this service
    command: gunicorn djangoapp.wsgi:application --bind 0.0.0.0:8000
    volumes: # this maps the path to the name static_volume which can be shared between services
      - static_volume:/home/appuser/static
    expose:
      - 8000
    env_file: .env # you can create a .env file and set multiple env vars for your image

  nginx:
    build: . # build the current image
    volumes:
      - static_volume:/home/appuser/static # see explanation in djangoapp
    ports:
      - 80:80
    depends_on: # this means that djangoapp should be started first before this service
      - djangoapp
    environment: # env vars
      - CONTAINER_NAME=djangoapp
      - NGINX_PORT=80
      - WSGI_PORT=8000

volumes:
  static_volume:

Please see the comments above for a brief explanation on what the lines mean.

To run your Django web app and Nginx images, execute the following in the CLI

docker-compose -f docker-compose.yml up

All done

Using the code snippets above, try it out with your Django webapp and run it with Docker Compose in your dev machine. You are now ready to deploy your multi container application.


Do you want more Python and Django tips and tutorials? Drop your email in the box below and we'll send new content straight to your inbox.

Leave a Comment