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.