CodeX
Published in

CodeX

A Boilerplate to Easily Dockerize and Deploy Django Apps

Django, PostgreSQL, Nginx, Docker, Docker Compose

Hi folks! It’s been a while since I last could write. In this article, I will write about a repo I created to dockerize and easily deploy my Django apps.

Photo by Mohammad Rahmani on Unsplash

Introduction

This repo really helps me to deploy my apps. I use the repo as a boilerplate. To use it, you just need to create an empty Django project in the folder that you cloned by using the repo below. In this article, I’ll explain the files and logic behind the dockerizing process.

Makefile

We can say this is a recipe file to manage commands that we use to build or run the project. To use this file, we use make command. In this boilerplate, mostly the commands below will be used:

  • make collect
  • make migration
  • make runproxyversion
.PHONY: help

help:
@echo "{{project_name}} Projects"
@echo "~~~~~~~~~~~~~~~"
@echo ""
@echo "check : Health check"
@echo "coverage : Make test coverage"
@echo "docup : Run docker compose services"
@echo "docdown : Stop docker containers"
@echo "migrations : Make django migrations"
@echo "install : Install python requirements"
@echo "recover : docdown + docup + wait + migrations + loaddata"
@echo "runserver : Run django server in debug mode"
@echo "run_gunicorn : Run django server with gunicorn"
@echo "static : Collect static files"
@echo "superuser : Create django super user"
@echo "test : Start django test runner"
@echo "translation : Translation operation"
@echo "wait : Wait for 3 seconds"
@echo ""

check:
@python manage.py check

coverage:
@coverage run --source='.' manage.py test
@coverage report -m
@coverage html
@coverage xml

docup:
@docker-compose up -d --build

docdown:
@docker-compose down -v

dumpdata:
@python manage.py dumpdata -o dummy.json

loaddata:
@python manage.py loaddata scripts/dummy.json

migration:
@python manage.py makemigrations
@python manage.py migrate

install:
@pip3 install -r requirements.txt

recover: install docdown docup wait migration loaddata
@echo "\n\t~~~~~~~~~~~~~~~"
@echo "\tusername: admin"
@echo "\tpassword: 123"
@echo "\t~~~~~~~~~~~~~~~\n"

recover_refactor: install docdown docup wait migration superuser
@echo "\n\t~~~~~~~~~~~~~~~"
@echo "\tusername: admin"
@echo "\tpassword: 123"
@echo "\t~~~~~~~~~~~~~~~\n"

runserver:
@python manage.py runserver 127.0.0.1:8000

run_gunicorn:
@gunicorn {{project_name}}.wsgi -b 0.0.0.0:8000

collect:
@python manage.py collectstatic --no-input

superuser:
@python manage.py createsuperuser

tests:
@python manage.py test

translation:
@python manage.py makemessages -l tr
@python manage.py compilemessages

wait:
@sleep 3

runproxyversion:
@make collect
@make migration
@gunicorn {{project_name}}.wsgi:application --bind 0.0.0.0:8000

Django App Dockerfile

This is the Dockerfile which is located at the project’s root level. Basically, it creates an image to follow the steps below.

  • Pull 3.9 version of Python as a base image
  • Set the working directory as /app
  • Copy all the files at the same level with this Dockerfile into the WORKDIR
  • Upgrade the pip
  • Execute make install command which is being used to install the project dependencies
  • Expose 8000 port for the containers that will be created by using this image
FROM python:3.9 # enviroment

ENV PYTHONDONTWRITEBYTECODE=1

ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY . .

RUN pip install --upgrade pip

RUN make install

EXPOSE 8000

Nginx Dockerfile

Under the nginx folder, our Nginx image is located. We will use this image to create a container to serve (publish) our Django app outside.

FROM nginx:latest

RUN rm /etc/nginx/conf.d/default.conf

COPY nginx.conf /etc/nginx/conf.d

This image will be searching for nginx.conf file which has to be located in the same directory with this Dockerfile to copy into /etc/nginx/conf.d .

upstream baysan_web_gunicorn {
server webservice:8000; # default django port comes from service name in docker-compose.yml
}

server {
listen 80; # default external port. Anything coming from port 80 will go through NGINX

location / {
proxy_pass http://baysan_web_gunicorn;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /app//static/; # where our static files are hosted, these files are coming from the volume we created for static files
}

}

The above nginx.conf file helps us to create communication between our Django app and Nginx. We created an upstream with a custom name. The upstream will be listening to the service which is named webservice in the same network that this container running. Pay attention here, the webservice is a service name which is coming from docker-compose.yml . Then, Nginx will start to listen to port 80 and when a request came, it will direct it to the webservice . So, it will work like a reverse proxy.

Docker Compose File

Lastly, we need a composer which is docker-compose.yml to run and compose the containers that we will create by using the images above.

version: "3.9"

services:

webservice:
container_name: baysan_web
build: .
depends_on:
- database
environment:
- SECRET_KEY=verysecretKey
- DEBUG=0
- DB_NAME=postgres
- DB_HOST=database
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=secretpassword
- ALLOWED_HOSTS=127.0.0.1
command: make runproxyversion
networks:
- nginx_network
- pg_network
volumes:
- static_volume:/app/static

database:
container_name: baysan_database
image: postgres
restart: always
environment:
- POSTGRES_PASSWORD=secretpassword
volumes:
- database_volume:/var/lib/postgresql/data
networks:
- pg_network

nginx:
container_name: baysan_nginx
build: ./nginx
ports:
- "80:80"
volumes:
- static_volume:/app/static
depends_on:
- webservice
restart: "on-failure"
networks:
- nginx_network
networks:
nginx_network:
driver: bridge
pg_network:
driver: bridge

volumes:
database_volume:
static_volume:

Here, we need to pay attention to volumes and networks. Because sometimes we can not serve our static files in production mode (which is DEBUG=False) or we can not create communication web app service and database service. To do that I created 2 volumes and 2 networks.

  • 1 network for communication between database service and web app service
  • 1 network for communication between web service and Nginx service
  • 1 volume for database service (to provide consistency)
  • 1 volume for static files from web app container to Nginx container (if we have media files, we will be in need to create a new one for media files).
Image by Author

I also set “80:80” under the ports in nginx_service to handle the request that comes from outside into the Django app.

Also, under the settings folder, there are 2 config_*.py files. If DEBUG environment variable is 1 given by docker-compose.yml file, the project will be in Debug mode. Otherwise, it will be in Production mode. The settings.py file will be like below.

#  Rest of the settings.py can be changed for projects. For my set-up, I need the lines below.import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

DEBUG = int(os.environ.get("DEBUG", default=0))
if DEBUG == 1:
from .config_dev import *
else:
from .config_prod import *

Deploying On Production

To deploy our environment, I use Nginx on my prod server. I create a new reverse proxy on the prod server to direct requests to the Nginx container which is created in docker-compose.yml file.

I create a new file under /etc/nginx/sites-available folder and put the content below into the file. Here, I direct the request that comes to http://127.0.0.1:80 . We do that because we set 80:80 in our Nginx service. We can deploy more projects on the same server by changing these port numbers.

server {
listen 80;
listen [::]:80;
server_name YOUR_URL(S);
server_name_in_redirect off;

access_log /var/log/nginx/reverse-access.log;
error_log /var/log/nginx/reverse-error.log;

location / {
proxy_set_header Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:80;
}
}

Lastly, we need to create a symbolic link for this file.

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
Image by Author

Finally

I was a bit nervous about these topics like how to create bridges between the containers, how to serve my static files, how to create a reverse proxy on my server etc. After I learned these topics, I started using them for every project. I wish this repo and this article will help you to develop, manage and deploy your projects. You can access the repo by using the link below.

Kind regards

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Baysan

Baysan

321 Followers

Lifelong learner & Freelancer. I use technology that helps me. I’m currently working as a Business Intelligence & Backend Developer. mebaysan.com