How to containerise a web app (Django + React.js) using docker and make it production ready ? 🐋
Read my previous blog on what is docker? To know docker terminologies and how things actually work
This will make your project production-ready as the project will be served through Nginx servers
This post will guide you through containerising a full-stack application built using Django, React.js/Next.js, PostgreSQL and a web server NGINX. I am going to be using Linux throughout but steps should remain the same on Windows or macOS. This post assumes you already know Nginx, Django, React and you've read my previous post explaining what docker and docker terminologies are.
To install docker follow the official documentation. On Linux, you'd need to run an extra step after installation that is "sudo apt install docker-compose".
The architecture of the app
Initial steps:
Run them one by one
mkdir docker_tutorial
cd docker_tutorial
virtualenv venv
source venv/bin/activate
pip install django gunicorn psycopg2-binary gevent
django-admin startproject backend
npx create-react-app frontend
What do these steps exactly do?
- Create a new folder, navigate into it
- Set up a virtual environment, activate it (to install virtualenv library, run pip install virtualenv)
- Install Django and gunicorn (a mod python wsgi to be used with nginx reverse proxy) and pscopg2-binary for connecting with PostgreSQL
- Create a new Django Project
- Create a new React.js / Next.js Project (assuming you have node and npm installed)
- For Nginx and PostgreSQL we'll be using pre-built images from the docker hub to make our lives easier
If you want to learn more about Nginx and gunicorn check out digital ocean articles.
After this, your folder should contain three folders backend, frontend and venv. Check by running the "ls" command.
The next step is to write individual docker files inside backend and frontend. Dockerfiles tell the process which commands to run during build.
Navigate to the backend folder that is the Django app and create a requirement.txt file
cd backend
pip freeze > requirements.txt
Now the simple way to spin up the server is by running python manage.py runserver . If we have any migrations then python manage.py makemigrations and python manage.py migrate . We need to the same inside the container that we are going to spin up using Docker.
Create a new file called Dockerfile . This file has no extensions. Inside here we'll write the steps to copy our app inside the container and run it.
# docker_tutorial/backend/Dockerfile
FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
RUN python manage.py makemigrations
RUN python manage.py migrate
CMD gunicorn -b 0.0.0.0:8000 --worker-class=gevent --worker-connections=1000 --workers=5 backend.wsgi
This file simply uses the python image from docker hub, copies our app into the container in a folder named app, installs dependencies, runs migrations and spins up the gunicorn server.
Next, we have to do the same with our frontend react.js app. Navigate to the frontend folder and create a Dockerfile in it. Paste the following inside the Dockerfile.
# docker_tutorial/frontend/Dockerfile
FROM node:14-alpine AS builder
WORKDIR /opt/web
COPY . ./
RUN npm install
ENV PATH="./node_modules/.bin:$PATH"
RUN npm run build
FROM nginx:1.20.1-alpine
RUN apk --no-cache add curl
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst && \
chmod +x envsubst && \
mv envsubst /usr/local/bin
COPY ./nginx.config /etc/nginx/nginx.template
CMD ["/bin/sh", "-c", "envsubst < /etc/nginx/nginx.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]
COPY --from=builder /opt/web/build /usr/share/nginx/html
This will get the node:alpine image from docker hub which has npm preinstalled, copy our app inside it, builds the image. After this, it looks for nginx image and copies the nginx.config file inside the container.
Paste the following contents in ngin.config file
# docker_tutorial/frontend/nginx.config
server {
listen ${PORT:-80};
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $$uri /index.html;
}
}
Congratulations on making it here, we now have frontend and backend apps running in their own containers.
The next step is to configure the Nginx server to serve our Django and React app. Navigate to the docker_tutorial directory and create a new folder called nginx_backend_server and create a Dockerfile inside it. Paste the following contents inside of it.
# docker_tutorial/nginx_backend_server/Dockerfile
FROM nginx:1.20.1-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
This will get the nginx image from docker hub and copy our custom nginx.conf inside the container. Thus, we'll need to create a nginx.conf now in the same place and paste the following contents inside it.
# docker_tutorial/nginx_backend_server/nginx.conf
server {
listen 8000;
location / {
proxy_pass http://backend:8000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
}
This configuration is a reverse proxy to the backend container running gunicorn server and we can access the backend server simply using http://backend. This is how Docker containers communicate with each other. We'll also need to keep the same service name in the docker-compose file which we'll add later. The backend server will this way run on port 8000 of host machine.
The final step is configuring the database and putting the credentials in the settings.py of the Django project, which we'll be doing inside the docker-compose.yml file. Navigate to the docker_tutorial directory and create a new docker-compose.yml. Paste the following contents inside of it.
# docker_tutorial/docker-compose.yml
version: "3"
services:
database:
image: postgres:12.7-alpine
volumes:
- ./backup_data/db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
backend:
build: ./backend
volumes:
- ./backend:/app
depends_on:
- database
frontend:
build: ./frontend
volumes:
- ./frontend:/app
depends_on:
- backend
ports:
- 80:80
nginx_backend_server:
build: ./nginx_backend_server
ports:
- 8000:8000
depends_on:
- backend
Now run "docker-compose up" on the terminal at the docker_tutorial directory level. Images should start building and containers should start running. If this was successful go to the settings.py file in your Django project and change the database configuration the following
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'database',
'PORT': 5432,
}
}
The HOST should be the name of the service given in docker-compose for docker to know about the database. Watch my YouTube video on this to get a full in-depth understanding of everything written in this post.
The process should now reload and Tadaaaa !! You have a dockerized production-ready full-stack application. Go to http://localhost:80 to see the react.js app and localhost:8000 to see the Django app. Both are served using Nginx.
See you next time! Kudos!