This document contains an outline of the deployment process of the PacMail application using Flask, React.js and Docker. This project is part of the final project of pacmann's DevOps Fundamentals course.
In this project there are several tools used, including:
- Docker.
- GitHub Action.
- Amazon EC2 Instance.
- DNS Server.
There are five stages that need to be done to deploy an application to the server and make it accessible.
- Server Preparation.
- Compose the App.
- Create CI/CD Pipeline.
- Configure web server using Nginx.
- Create domain name.
In this project, I am using the Ubuntu Operating System running on an EC2 Instance of Amazon Web Services. The instance specifications used are t3.small (2 vCPU, 2 GiB Memory) with a root volume of 8 Gb. There are several stages in preparing the server to be used:
sudo apt update && sudo apt upgrade
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install the Docker packages:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Source: Docker documentation
sudo apt install nginx -y
After installing NGINX, configure the firewall by setting which ports will be used. We can configure the firewall on the Security Groups used by our server.
Next we need to create a configuration so that our application can run in the container we are using.
We need to create a Dockerfile so that the application can run inside a Docker Container.
FROM python:3.10
WORKDIR /backend
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD [ "python", "api.py" ]
Next, we need to create docker compose to make it easier for us to configure the services that will be run.
version: "3.8"
services:
my-frontend-prod:
image: node:latest
container_name: my-frontend-prod
restart: always
depends_on:
- my-backend-prod
- my-database
volumes:
- ./frontend:/frontend
working_dir: /frontend
command: ["npm", "start"]
network_mode: host
my-backend-prod:
image: ${APP_IMAGE}:${APP_TAG}
container_name: my-backend-prod
restart: always
depends_on:
- my-database
volumes:
- ./backend:/backend
network_mode: host
my-frontend:
image: node:latest
container_name: my-frontend
restart: always
depends_on:
- my-backend
- my-database
volumes:
- ./frontend:/frontend
working_dir: /frontend
command: ["npm", "start"]
network_mode: host
my-backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: my-backend
restart: always
depends_on:
- my-database
volumes:
- ./backend:/backend
network_mode: host
my-database:
image: postgres:13
container_name: my-database
hostname: database
restart: always
env_file:
- .env
volumes:
- postgres_data:/var/lib/postgresql/data
network_mode: host
volumes:
postgres_data:
After we have prepared the server that will be used to deploy the application, the next step we will create a CI/CD Pipeline using GitHub Actions to automate.
First, we need to protect the main
branch of our repository. This is done so that the merge process can be carried out if a pull request has been approved by the collaborator. Then we can create a CI/CD Pipeline inside the .github/workflows
directory. In this directory there is a .yaml file that will be used to run the CI/CD process.
The CI process will be run if there is a pull request to the main
branch. In this process, testing is also carried out to check whether the application being developed is as desired.
This process will be carried out if the main branch is pushed/updated where later the application will be deployed to the staging server. This process is also useful if a QA team wants to try testing the application to be developed before deploying it in production.
After the testing process on the staging server is complete and the application is declared ready to be released, the next step is to save the docker image used in the docker registry. In this project I use Docker Hub to store the docker images used. This process will be executed if there is a new tag saved on the main
branch.
This process will be carried out if a tag is released so that the application will be deployed to the production server. This application on the production server will later be used by the user.
In this process we need to configure NGINX which acts as a Reverse Proxy which will direct requests from port 80 (HTTP) to the application running on the server
First we need to remove the default NGINX configuration.
sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
After we delete the default configuration from NGINX, then we will create the configuration that we will use.
cd /etc/nginx/sites-available
sudo nano pacmail.putra988.my.id
In the configuration file, we will specify which ports NGINX listens on and the destination port to which requests will be redirected.
server {
listen 80;
server_name pacmail.putra988.my.id;
location / {
proxy_pass http://:public-ip:3000;
}
}
server {
listen 80;
server_name pacmail.api.putra988.my.id;
location / {
proxy_pass http://public-ip:5000;
}
}
After successfully creating the configuration file, then we activate the configuration file by creating a symlink to the sites-enabled
directory.
sudo ln -s /etc/nginx/sites-available/pacmail.putra988.my.id /etc/nginx/sites-enabled
Next, check the syntax and test to ensure there are no incorrect configurations.
sudo nginx -t
If there are no errors, now the application can be accessed directly without needing to specify a port.
The final process of this project is creating a domain name to make it easier for users to access the application without needing to remember and write the public IP used by the application. This process will also show how to install an SSL certificate to ensure the connection used is secure via HTTPS. In this project, I used jagoanhosting.com to generate the domain name and Certbot to generate the SSL certificate.
After we create a domain name, next we need to generate an SSL certificate and install the certificate on the server used. Before generating SSL, we need to install Certbot first.
sudo apt install certbot python3-certbot-nginx
Then, using the NGINX configuration file that has been created, we will generate an SSL certificate using Certbot.
sudo certbot --nginx -d "pacmail.putra988.my.id"
After we have generated and installed the SSL certificate, now the connection is secure using HTTPS. You can access the application using pacmail.putra988.my.id
In this project the deployment process has been successfully carried out. Overall, both the server setup process and the application deployment process to the server were successfully carried out and the application can be accessed via the domain used with an HTTPS connection. You can see the video demonstration at the link here. The next plans for this project are adding an email reply feature and carrying out stress testing.