Dockerize Django along with Nginx and PostgreSQL

by Sabbir Ahmed


Posted on: 3 years, 8 months ago


Ship efficiently with docker
Ship efficiently with docker

"Docker is Inception for Linux", is how I like to put it when it comes to explaining docker. Cause docker is exactly like this, a Linux inside a Linux sharing the host kernel. For any compassionate Linux user, can learn docker in a matter of days. In this article, I will discuss dockerizing a complete Django app in a production environment (Nginx, PostgreSQL, and Gunicorn) with docker-compose. So, let's begin-

Fulfilling the prerequisites

That's it. Now, 

Get the code - by-sabbir/django-gunicorn-nginx-docker

Create a .env file at the root (the same directory that contains docker-compose.yml) and edit as following -

SECRET_KEY="YOUR SECRET KEY"
DB_HOST="db"
DB_PASSWORD="hello"    #from docker-compose.yml
DB_USER="postgres"    #from docker-compose.yml
DB_NAME="postgres"    #from docker-compose.yml
DB_PORT="5432"        #from docker-compose.yml
DB_ENGINE="django.db.backends.postgresql"

The system will be up and running with those two following commands

docker-compose build

This will download all the dependencies and docker images from the docker hub

docker-compose up -d

Last we have to copy the static files to our specified directory at settings.py, here's how we'll do it,

docker-compose exec web python manage.py collectstatic --no-input

Hopefully, now your project will be up and running by now and you can access it at localhost:8008 like below. If not use docker-compose logs -f for debugging purposes. 

Result if you visit http://localhost:8008The Design/Thinking Process

Basically, the problem is to run a Django app inside docker in a production setup, which means with Nginx as HTTP server, Postgresql as DB server. Here's my plan - I will run the App, the HTTP server, and the DB server in separate containers makes it more manageable and robust. The App will be run on Gunicorn, later will user Nginx's proxypass. We need a shared volume for the HTTP server and the App for static and media file hosting and a persistent filesystem for DB. All the secrets are going to be stored in a totally separated .env file. Let's sum up- 

  • Three Docker Containers
    • One for the App we will call it 'postgres_deploy_web' and the service name will be 'web'
    • The second one is our DB Server, will name it 'postgres_deploy_db' and the service name will be 'db'
    • Finally, Nginx will be a custom docker containing our custom settings and how we want to server the site, named 'postgres_deploy_nginx'
  • Shared Volume
  • An ENV file that contains all the secrets.

Let's see the web service:

  web:
    build: .
    container_name: postgres_deploy_web
    command: gunicorn app.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./app:/app/
      - staticfiles:/app/static/
    expose:
      - 8000
    env_file:
      - ./.env
    depends_on:
      - db

the second line 'build .' means it will create a docker image from the root Dockerfile, let's take a look at the file,

  FROM python:lapostgres
  ENV PYTHONDONTWRITEBYTECODE 1
  ENV PYTHONUNBUFFERED 1
  RUN mkdir /app
  WORKDIR /app
  RUN pip install --upgrade pip
  COPY requirements.txt .
  RUN pip install -r requirements.txt
  COPY ./app /app

Farley simple enough to understand. Let's move on to the next phase, the DB

  db:
    image: postgres
    container_name: postgres_deploy_db
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=hello

We will just use the official Postgres docker image and postgres_data is the persistent data volume within docker. It should suffice.

Now, Nginx. You can do whatever you want with it. Nginx really brings you the ultimate power. 

  nginx:
    build: ./nginx
    container_name: postgres_deploy_nginx
    volumes:
      - staticfiles:/app/static/
    ports:
      - 8008:80
    depends_on:
      - web 

So, let's dig a bit with these compose configs. The second line indicates it will compile whatever Dockerfile is in the 'nginx' folder. Let's follow the clue - 

    FROM nginx:1.19.0-alpine
    RUN mkdir /app
    RUN rm /etc/nginx/conf.d/default.conf
    COPY nginx.conf /etc/nginx/conf.d/
    WORKDIR /app

Wow, that's easy. Simply, We just deleted the default config file and replaced it with our own. The question is what is in our config file, well let's see a  bit simplified version - 

  upstream app {
    server web:8000;
  }

  server {
    listen 80;
    location / {
      proxy_pass http://app;
    }
    location /static/ {
      alias /app/static/;
    }
  }

upstream configure proxy request server for Nginx. Gunicorn is a Python WSGI server for our Django app. In the first location derivative, we proxy passed the '/' root location from the app to the HTTP server. The second location derivative is just an alias for static files. Now, we can get creative with the Nginx server.

This is it, I guess. Let me know if I missed anything. The Readme has more detailed commands. Feel free you alter the codes, And I will be checking Github issues, see you there.