Docker can be a real productivity saviour when you get to know how to use it well. It’s really great with automating deployments and even horizontally scaling your App. As you might have guessed, I actually use Docker in production for Site Monki application and I would like to share my experiences on how to set it up for production workloads.
Site Monki’s web dashboard is built on Django with MySQL db. There are a handful of tutorials online on how to setup Django with Docker, but most miss out on how to serve static files in production. Django requires a web server to serve static files such as CSS, JS, Image for the production setup while it the single-threaded development server can serve everything all with the in-built dev server. So this tutorial really is about configuring Django with Docker to serve static files.
For this purpose I use Nginx. It’s a great, lightweight web server. I use it as a reverse proxy to Docker containers running the Django app as well as a static content web server that our Django app relies on. He’s the flow;
Configuring Nginx Reverse Proxy
So we start with configuring an Nginx instance that will reverse proxy client requests to our docker fleet. We pass in all the http request header data to our Django app.
server { server_name mydjangoapp.com www.mydjangoapp.com; .... # proxy configures proxy_set_header X-Real-IP $remote_addr; proxy_set_header HOST $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-HTTPS-Protocol $ssl_protocol; proxy_set_header X-NginX-Proxy true; location / { proxy_pass http://127.0.0.1:9090; } .... }
Nginx web server container
We then setup Nginx Docker container to serve static files that our Django App running on port 8000 via gunicorn generates. This Docker container will serve static assets from url https://mydjangoapp.com/static to the client but proxies the rest of the queries to the Django app.
Notice we Nginx to serve static files from folder /staticfiles. We shall later map named docker volume staticfiles from our Django container to folder /staticfiles in this Nginx container using docker-compose.yml file.
Dockerfile
FROM nginx:1.15.12-alpine RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d
nginx.conf file
upstream mydjangoapp { server web:8000; } server { listen 80; location / { proxy_pass http://mydjangoapp; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } location /static/ { alias /staticfiles/; } }
Django with Gunicorn setup
Now we setup Django to collect all static files of all the apps into a single staticfiles folder in settings.py. We also tell Django that the url for static files is /static
import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_DIR = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') ... STATIC_URL = '/static/' STATICFILES_DIRS = [ STATIC_DIR, ] ...
We have to add gunicorn python library in a requirements.txt file. Here’s our django_mydjangoapp Dockerfile for the Django app;
FROM python:3.6.5-jessie ENV PYTHONUNBUFFERED 1 ENV SRC_DIR=/django_mydjangoapp RUN mkdir -p $SRC_DIR WORKDIR $SRC_DIR ADD ./requirements.txt $SRC_DIR/requirements.txt RUN pip install -r requirements.txt ADD . $SRC_DIR
Docker-compose.yml file
We put it together in a docker-compose.yml file where we specify all our Docker containers, the ports we want to run them on and how they interact with each other.
Notice here that we have two named docker volumes; staticfiles and mysql_data. The staticfiles volume maps to actual location where our Django app puts all static assets. We then use this volume in the nginx container to serve static content. mysql_data is for persisting our app database.
version: '2.1' services: web: build: context: ./django_mydjangoapp dockerfile: Dockerfile command: bash -c "python manage.py collectstatic --no-input && python manage.py migrate && gunicorn --bind 0.0.0.0:8000 -w 4 mydjangoapp.wsgi" volumes: - staticfiles:/django_mydjangoapp/staticfiles ports: - 8000 depends_on: db: condition: service_healthy nginx: build: context: ./nginx dockerfile: Dockerfile ports: - 9090:80 volumes: - staticfiles:/staticfiles depends_on: - web db: image: mysql:5.7 ports: - 3306 environment: MYSQL_ROOT_PASSWORD: mydjangoapp MYSQL_USER: mydjangoapp MYSQL_PASSWORD: mydjangoapp MYSQL_DATABASE: mydjangoapp healthcheck: test: ["CMD", "mysqladmin", "-u$MYSQL_USER", "-p$MYSQL_PASSWORD", "ping", "-h", "localhost"] timeout: 20s retries: 10 restart: always volumes: - mysql_data:/var/lib/mysql volumes: mysql_data: staticfiles:
That’s pretty much it. Simply install Nginx in the host server and add nginx.conf in /etc/nginx/sites-available/ then run docker-compose up in our app folder.