Mon - Fri: 9:00 - 19:00 / Closed on Weekends

7- Docker Compose: Nginx Django Postgresql Web Application Part2

We created Django Project and App. Now we are ready to create configuration files for Docker Compose. The below image shows us the files and folder structure of our Django project. It might be helpful to see them upfront before we create them.

Create a requirements.txt file with the following content in website folder. We will use this file to tell pip what to install in our app container.

asgiref==3.6.0
Django==4.1.4
pytz==2022.7
sqlparse==0.4.3
psycopg2-binary>=2.8
gunicorn==20.1.0
tzdata==2022.7

 

Create a dockerfile again in the web folder. This docker file will create our app image according to the steps defined.

FROM python:alpine3.17
RUN pip install --upgrade pip
RUN apk update
RUN apk add postgresql-dev gcc python3-dev musl-dev libc-dev linux-headers
WORKDIR /web
COPY . .
RUN pip install -r requirements.txt
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONNUNBUFFERED 1
COPY ./entrypoint.sh ./entrypoint.sh
ENTRYPOINT [ "sh", "./entrypoint.sh" ]

First line tells that we are going to use python installed alpine version of linux. Alpine linux is small in size, reliable and secure.

Then we upgrade pip (python package installer)

Then install required libraries for postgresql

Then we change our working directory to /web and copy everything from website folder to container's web directory

Then install python packages that were defined in requirements.txt

ENV PYTHONUNBUFFERED=1 ensures that Python output is logged to the terminal, making it possible to monitor Django logs in realtime.

ENV PYTHONDONTWRITEBYTECODE=1 prevents Python from copying pyc files to the container.

Finally we copy entrypoint.sh from website folder to container's current working directory.

 

 

Create a file named "docker-compose.yml" in website folder. Indention is very important. We have 3 containers; app (django), db (postgresql) and nginx.

build: defines where the dockerfile resides to build our image

volumes: defines the folders that should be mounted (we are mounting app root directory to container's web directory. This allows when we edit our code on our local development environment, the same modifications will be available in our container.)

env_file: defines where the .env file is that stores our environmental variables like secretkeys, username, password etc

depends_on: if a service requires another service to be up and ready, this is where we define it

 

version: '3.8'
services:
 app:
   build: .
   volumes:
     - .:/web
     - static:/static
   ports:
     - 8000:8000
   env_file:
     - .env
   depends_on:
     db:
       condition: service_healthy
   links:
     - db
   networks:
     - mynet
 db:
   image: postgres:15.1-alpine
   volumes:
     - ./postgres_data:/var/lib/postgresql/data 
   ports:
     - 5432:5432
   environment:
     - POSTGRES_DB=webdb
     - POSTGRES_USER=dbuser
     - POSTGRES_PASSWORD=dbpass
   healthcheck:
     test: ["CMD-SHELL", "pg_isready -U dbuser"]
     interval: 5s
     timeout: 5s
     retries: 5
   networks:
     - mynet


 nginx:
   build: ./nginx
   volumes:
     - static:/static
   ports:
     - 80:80
   depends_on:
     - app
   networks:
     - mynet


networks:
 mynet:
   driver: bridge



volumes:
 static:
 postgres_data:

 

 

Create another file and name it ".env" in website folder

You should copy and paste SECRET_KEY from settings.py which resides in your django project and then delete the SECRET_KEY from settings.py for security.

DJANGO_ALLOW_HOST defines which host can run your application. When our jenkins server and kubernetes cluster are ready we should add them also here. The rest is sql related parameters, I choose simple names and password for simplicity, you should use your own values.

DEBUG=TRUE
SECRET_KEY = 'django-insecure-!0zoeq-c)ygyw@l_l+w&d&7g9=somu2&qqbg6ybk84hp76l3ib'
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::] X270 https://djangoweb.configland.com
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=webdb
SQL_USER=dbuser
SQL_PASSWORD=dbpass
SQL_HOST=db
SQL_PORT=5432

 

Edit settings.py in your django project

We should change the parameters like this to tell settings.py to use values that defined in .env file. Again when we have kubernetes and jenkins ready we will add their ip address or domain address to CSRF_TRUSTED_ORIGINS

import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG= os.getenv('DEBUG')
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS').split(" ")
CSRF_TRUSTED_ORIGINS = ["http://localhost/*", "https://djangoweb.configland.com" , "http://127.0.0.1"]

STATIC_URL = '/static/'
STATIC_ROOT = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")

 

Change DATABASES parameters in your settings.py like this, so enviromental variables will be fetched from .env file we created earlier

DATABASES = {
 'default': {
     'ENGINE': os.getenv('SQL_ENGINE'),
     'NAME': os.getenv('SQL_DATABASE'),
     'USER': os.getenv('SQL_USER'),
     'PASSWORD': os.getenv('SQL_PASSWORD'),
     'HOST': os.getenv('SQL_HOST'),
     'PORT': os.getenv('SQL_PORT'),
 }
}

 

 

Create a folder named “nginx” in the Project root folder (website folder)

Create two files named default.conf and dockerfile

Modify dockerfile in nginx folder like this

FROM nginx:1.23.3-alpine
#we are replacing the default.conf with ours
COPY ./default.conf /etc/nginx/conf.d/default.conf

 

Modify default.conf file in nginx folder like this

#x270 is my host machine, when you use kubernetes you can change you replace this with your actual domain name 
upstream x270{
#this is the service from docker-compose.yml file
#Simply this is the server which nginx will be a proxy for
server app:8000;
}
server{
#nginx will listen port 80
listen 80;
location / {
    proxy_pass http://x270;
}
location /static/ {
    alias /static/;
}
 location /media/ {
    alias /media/;
}
}

 

Create a file named entrypoint.sh in the Project root folder (website folder) and modify like below. Each time you start or create the app container, entrypoint.sh commands are executed. 

#Entrypoint runs eachtime you boot or create the container
python3 manage.py makemigrations
python manage.py migrate
python manage.py collectstatic --no-input
gunicorn PrjWeb.wsgi:application --bind 0.0.0.0:8000

 

entrypoint.sh file must be executable. When dockerfile copies files, it copies their permisson too. So run the command below on your terminal to make entrypoint.sh to be executable.

chmod +x ./entrypoint.sh

 

Create a file named ".gitignore" and modify like this

postgres_data/
migrations
*.log
temp/
.vscode/*

 

We created all files that we need. We can build our images and run containers now.

To build images, run this command in your root folder (website folder)

docker compose build

Images are created with no errors

 

Let's try to run our containers. when we run this command postgresql image will be pulled from dockerhub and all the configuration from our yaml files are applied such as contaner network, volumes etc. nginx container will wait for app container to be up, app container will wait for db health verification and then all containers will be up and running in the correct order.

docker compose up -d

 

We can use docker ps command to make sure containers are running on our host machine

 

Nginx forwards request to django successfully

 

To make sure static files copied to static folder I browse 127.0.0.1/admin url.It looks fine. 

You can check nginx logs with the command: 

docker logs <container-id>

 

 

When we run docker compose up -d command, docker compose created a local folder named  "postgres_data" because in our yml file we defined a volume to be mounted with container's /var/lib/postgresql/data folder. This makes sure our data will not deleted even if containers are deleted.

Now we can create a super user for Django. This command lets us to run terminal commands without entering the containers interactive CLI. 

docker compose exec app python manage.py createsuperuser

 

We can check container network and volumes

 

I am adding some docker compose commands that you might find useful.

#start and run an entire app that contains multiple services according to the docker-compose.yml file (in detached mode)
docker compose up -d   

#If you need to delete everything that you build using docker compose you need to run these
docker compose down          #Stops containers and removes containers, networks, volumes, and images created by up 
sudo rm -r postgres_data		#deletes the data folder that is created for postgres
docker image rm $(docker images -aq) #deletes docker images
docker compose down -v 		#deletes the active container volumes 
docker volume prune		#deletes unused container volumes
docker network rm <network-id> # deletes container network

 

I just tested and used docker compose down and docker compose up -d commands and I could successfully access Django's admin panel. I also confirmed that user class is extended as I wanted.

You local development environment is ready. You can use docker compose up and down commands anytime without losing your data. As I said before, when you change your code, it will be written in the container immediately because app root folder and container's web folder are mounted. However you need to restart app container and reload nginx service to see your modifications.

docker restart <app-container-name>

docker compose exec <nginx-service-name-in-dockercomposefile>  nginx -s reload

docker restart website-app-1
docker compose exec nginx  nginx -s reload

Thanks for reading.

 

  • Hits: 272