Django Gunicorn with Nginx in Docker

Adi Ramadhan
4 min readApr 9, 2023

--

Official docs of django and docker
Prerequisite: python and docker installed

Project Structure:

A. Setup Python Virtual Environment
create virtualenv: virtualenv venv
start virtualenv: venv/Scripts/activate
install Django and Gunicorn in virtualenv: pip install django gunicorn

B. Django Project with Gunicorn
Step 1: Create Django Project, Inital Migration
Create Django: django-admin startproject myproject
Go to myproject folder: cd myproject
Initial Migration: python manage.py migrate

Step 2: Create requirements.txt
create requirements.txt: pip freeze> requirements.

Step 3: Create Dockerfile in myproject

# syntax=docker/dockerfile:1
FROM python:3.8

# set environment variables
ENV APP_HOME=/app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# set work directory
WORKDIR $APP_HOME

# update pip, install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt $APP_HOME
RUN pip install -r requirements.txt

# copy app folder
COPY . $APP_HOME

# run python command
RUN python manage.py makemigrations
RUN python manage.py migrate
RUN python manage.py collectstatic --noinput --clear

C. Nginx Docker setup
Step 1: Preparation

Create nginx folder in main folder

Step 2: Create nginx.conf file

client_max_body_size 8M;

upstream django_app {
server myproject:8000;
}

server {

listen 80;

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
client_max_body_size 50M;

location / {
proxy_pass http://django_app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}

}

Step 3: Create Dockerfile in nginx folder

# Fetching the latest nginx image
FROM nginx

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

# Copying new conf.d into conf.d nginx image
COPY nginx.conf /etc/nginx/conf.d

D. Docker Compose Setup

Create docker-compose.yml in main folder

version: '1.0'

# Defining the compose version
services:

nginx:
build: ./nginx
ports:
- 8687:80
depends_on:
- myproject
restart: "always"

myproject:
build: ./myproject
command: sh -c "gunicorn myproject.wsgi:application --bind 0.0.0.0:8000"
expose:
- 8000
restart: "always"
folder structure and its content

Docker-compose command:

docker-compose down && docker-compose build && docker-compose up -d

down for remove existing apps (if exist), build for building into image, up for start the services

docker desktop

open in browser 127.0.0.1:8687

django is served in nginx server

OTHER CONCERN: Debug=False, STATIC and MEDIA
Debug False is crucial for production env, STATIC and MEDIA should be served by nginx

Step 1: Django Myproject > Update myproject/myproject/settings.py

Set Debug, Allowed Host, Templates, Static and Media in settings.py

...

Debug = False #update
ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] # update

...

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
Path(BASE_DIR, 'templates') # update
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

...

STATIC_URL = '/static/' #new
STATIC_ROOT = Path(BASE_DIR, 'staticfiles') #new
STATICFILES_DIRS = [Path(BASE_DIR,'static'),] #new

MEDIA_URL = "/media/" #new
MEDIA_ROOT = Path(BASE_DIR, 'mediafiles') #new

...

Step 2: Django Myproject > Set static, media and templates folder

Create static, media and templates folder
Add image in static file, example: cat.png

cat.png

Create index.html in templates folder

{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World</title>
</head>
<body>
<h1>Hello World!</h1>
<img src="{%static 'cat.png' %}" alt="not found">
</body>
</html>

Step 3: Django Myproject> Update myproject/urls for serving static file simulation

from django.urls import path
from django.shortcuts import render

def home(request):
return render(request, template_name='index.html')

urlpatterns = [
path('', home ),
]

Step 4: Nginx > Update nginx.conf Add staticfiles and mediafiles

client_max_body_size 8M;

upstream django_app {
server myproject:8000;
}

server {

listen 80;

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
client_max_body_size 50M;

location / {
proxy_pass http://django_app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}

# updated
location /static/ {
alias /app/staticfiles/;
}

# updated
location /media/ {
alias /app/mediafiles/;
}


}

Step 5: Docker Compose> Update docker-compose.yml add volumes

version: '1.0'

# Defining the compose version
services:

nginx:
build: ./nginx
ports:
- 8687:80
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/mediafiles
depends_on:
- myproject
restart: "always"

myproject:
build: ./myproject
command: sh -c "gunicorn myproject.wsgi:application --bind 0.0.0.0:8000"
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/mediafiles
expose:
- 8000
restart: "always"

volumes:
static_volume:
media_volume:

Final Folder Structure:

final project structure

Run with Docker-compose command:

docker-compose down && docker-compose build --no-cache && docker-compose up -d

open in browser 127.0.0.1:8687

--

--