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.
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_networknetworks:
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).
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
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
Also, you can read the first part of this article by following the link below.