How to Deploy a Full Stack Webapp on Cloud using best DevOps Practices

The tutorial will include a Video chatting application with react and nodejs as frontend and backend respectively , throughout the tutorial I will explain how to deploy the application to civo going through unit and intigration testing , CI/CD pipeline , scanning through some tools like datree , Trivy and Kubescape and in the end I will show how to deploy the application manually and using a IAC tool like Terraform to civo cluster.
The frontend and backend setup
Creating yaml files to deploy to kubernetes
Scanning and testing files through tools like datree and kubescape
manual and automatic(automation through IAC) deployment of cluster
Do not forget to give a star to the project used for this tutorial : https://github.com/Mahesh-Kasabe/Lets-Meet-Devops
Now let's start by creating a project and the folder structure which will look something like this :

1. The frontend and backend setup
The frontend part is created using the reactJS framework which includes a webpage which lets u enter a meeting id and code and after invoking the backend processes which is largely depends upon socketio and nodejs the application lets u connect with the other person.
CodeBases :
Backend Logic :
io.on("connection", (socket) => {
socket.emit("me", socket.id);
console.log(`User Connected with socket id ${socket.id}`);
socket.on("join_room", (data) => {
socket.join(data);
console.log(`User with ID: ${socket.id} joined room: ${data}`);
});
socket.on("send_message", (data) => {
socket.to(data.room).emit("receive_message", data);
});
socket.on("disconnect", () => {
socket.broadcast.emit("callEnded")
});
socket.on("callUser", ({ userToCall, signalData, from, name }) => {
socket.to(userToCall).emit("callUser", { signal: signalData, from, name });
});
socket.on("answerCall", (data) => {
socket.to(data.to).emit("callAccepted", data.signal)
});
});
Now that we have our backend and frontend setup together now let's write some unit test cases for them, just so that our production server would not go down after deploying the applications
The frontend testcase file : code
The backend testcase file : code
and after testing the web applications we will dockerize both the application in order to deploy to Kubernetes :
Frontend
FROM node:alpine as prod Run mkdir -p /home/src/frontend COPY . /home/src/frontend WORKDIR /home/src/frontend RUN yarn install RUN yarn build CMD ["yarn","serve","build"] FROM node:alpine as test Run mkdir -p /home/src/frontend COPY . /home/src/frontend WORKDIR /home/src/frontend RUN yarn install RUN yarn run test a --clearCacheBackend
From node:17-alpine as prod Run mkdir -p /home/src/backend COPY . /home/src/backend WORKDIR /home/src/backend RUN npm install CMD ["npm","start"] From node:17-alpine as test Run mkdir -p /home/src/backend COPY . /home/src/backend WORKDIR /home/src/backend RUN npm install RUN npm run test
As of now we have created chunk of files to dockerize the application with production and test tag now it's time to automate the deployment of docker containers to dockerhub using github actions .
before creating the workflows we will need some token to secure our workflows , let's add your github token, docker username and password inside secrets as PAT , DOCKER_USERNAME , DOCKER_TOKEN
for that matter now we will create a .github/workflows/ folder inside our main project folder so we could write some workflows for our CI/CD pipeline , now once we have our folder set up now let's create a cd-frontend file which will eventually deploy our frontend docker container on dockerhub , the content inside our workflow will be like this ,

similarly the backend image will get deployed to dockerhub ..

Now once we have our github actions pipeline setup now we can move on to deploying it to our kubernetes cluster
2. Creating YAML files to deploy to Kubernetes
The very first file we need to create in order to deploy our dockerize containers to Kubernetes is to create a deployment and service file for both the applications so we could download the applications to our clusters.
frontend deployment and service YAML
apiVersion: apps/v1 kind: Deployment metadata: labels: app: frontend name: frontend-deployment spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - image: maheshkasbe/frontend:v0.0.3 name: frontend-container ports: - containerPort: 3000 --------- apiVersion: v1 kind: Service metadata: name: frontend-service spec: type: NodePort selector: app: frontend ports: - port: 3000 targetPort: 3000 nodePort: 3000backend deployment and service yaml
apiVersion: apps/v1 kind: Deployment metadata: labels: app: backend name: backend-deployment spec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - image: maheshkasbe/backend:v0.0.3 name: backend-container ports: - containerPort: 3001 -------- apiVersion: v1 kind: Service metadata: name: backend-service labels: app: backend spec: type: ClusterIP selector: app: backend ports: - port: 3001 targetPort: 3001
**For one click argocd deployment for our project with self heal and automatic sync property **
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-backend
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
path: Deployments/K8s/backend
targetRevision: HEAD
destination:
server: 'https://kubernetes.default.svc'
namespace: app
syncPolicy:
syncOptions:
- CreateNamespace=true # to create the namaspace if not exsists
automated:
prune: true
selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-frontend
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
path: Deployments/K8s/frontend
targetRevision: HEAD
destination:
server: 'https://kubernetes.default.svc'
namespace: app
syncPolicy:
syncOptions:
- CreateNamespace=true # to create the namaspace if not exsists
automated:
prune: true
selfHeal: trueapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-backend
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
path: Deployments/K8s/backend
targetRevision: HEAD
destination:
server: 'https://kubernetes.default.svc'
namespace: app
syncPolicy:
syncOptions:
- CreateNamespace=true # to create the namaspace if not exsists
automated:
prune: true
selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-frontend
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
path: Deployments/K8s/frontend
targetRevision: HEAD
destination:
server: 'https://kubernetes.default.svc'
namespace: app
syncPolicy:
syncOptions:
- CreateNamespace=true # to create the namaspace if not exsists
automated:
prune: true
selfHeal: true
Now let's Create a docker-compose file just to run both the applications on local-system or any virtual system instances like EC2 (AWS)
docker-compose yaml
version: '3' networks: lets-meet: services: backend: image: docker.io/maheshkasbe/backend:v0.0.4 container_name: backend ports: - "3001:3001" networks: - lets-meet frontend: depends_on: - backend image: docker.io/maheshkasbe/frontend:v0.0.4 container_name: frontend ports: - "80:3000" networks: - lets-meet
3. Setting up datreeio, kubescape in order to scan yaml files for vulnerabilities , security and hygeine
Now in order to validate, scan and secure our argocd-test.yaml file we need to have our argocd account token , once we have our argocd account token we can start by creating **datree-test.yaml ** , by adding the following argument we can easily scan our argocd file .


```plaintext name: Datree-policy-Checks
on: push: branches: [ master ] pull_request: branches: [ master ]
env: DATREE_TOKEN: ${{ secrets.DATREE_TOKEN }}
jobs: ArgoCDPolicyCheck: runs-on: ubuntu-latest
steps: - name: Checkout uses: actions/checkout@v2
- name: Run Datree Policy Check uses: datreeio/action-datree@main with: path: 'Deployments/K8s/argocd.yaml' cliArguments: '--only-k8s-files -p Argo' ``` Now we need to scan all the deployment files with kubescape , ( Kubescape is the Open Source Kubernetes Security Platform - from development to production, configuration to runtime )
The workflow file will be something like this
name: K8s Manifest Sec checks Kubescape
on:
pull_request:
branches: [ master ]
push:
branches: [ master ]
jobs:
kubescape:
runs-on: ubuntu-latest
environment: testing
steps:
- uses: actions/checkout@v3
- name: 🧪 Install Kubescape
run: 'curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash'
- name: 🧪 Kubescape version
run: kubescape version
- name: Check the backend
run: |
cd Deployments/K8s
kubescape scan -v backend/*.yaml
- name: Check the frontend
run: |
cd Deployments/K8s/
kubescape scan -v frontend/*.yaml
Here the kubescape scan file will be scanning all the Deployments file particularly written for frontend and backend deployment clusters
4. Now Setting up Trivy and CircleCi for Unit and Integration testing of Docker containers
For best practices we will include Trivy Vulnerability scanner which is nothing butit is the default scanner of choice for DevOps and security teams across many popular projects and companies. Users benefit from regular, quality contributions and innovative feature requests. Aqua Trivy is the default scanner for GitLab’s Container Scanning functionality, Artifact Hub and Harbor. Aqua Trivy is also a RedHat certified scanner.
Here we will add another yaml file so we could scan our backend and frontend docker containers for vulnerabilities
name: ImageScan [Aqua Trivy]
on:
pull_request:
branches: [ master ]
push:
branches: [ master ]
permissions:
contents: read
jobs:
build:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
name: Build
runs-on: "ubuntu-18.04"
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build an image from Dockerfile
run: |
cd src/backend
docker build -t docker.io/my-organization/my-appb:${{ github.sha }} .
cd ../frontend
docker build -t docker.io/my-organization/my-appf:${{ github.sha }} .
- name: Run Trivy vulnerability scanner(backend)
uses: aquasecurity/trivy-action@cb606dfdb0d2b3698ace62192088ef4f5360b24f
with:
image-ref: 'docker.io/my-organization/my-appb:${{ github.sha }}'
format: 'template'
template: '@/contrib/sarif.tpl'
output: 'trivy-results.sarif'
severity: 'CRITICAL'
- name: Run Trivy vulnerability scanner(frontend)
uses: aquasecurity/trivy-action@cb606dfdb0d2b3698ace62192088ef4f5360b24f
with:
image-ref: 'docker.io/my-organization/my-appf:${{ github.sha }}'
format: 'template'
template: '@/contrib/sarif.tpl'
output: 'trivy-results.sarif'
severity: 'CRITICAL'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
once we are done our folder structure should look something like this

CircleCI is the software delivery engine for teams who want to ship software faster, using code that’s reusable and easy to maintain.You can login using your github account in order to use your github projects directly
in order to create a circle ci pipeline we need to add .circleci folder inside our main project folder so we could easily write our artefacts and deploy them, now let's create a config.yaml file so we could configure our pipeline

Once the pipeline get's executed we get our interface on circleci like this

5. Adding dependabot for security ,version and vulnerability scanning throughout the code
# Basic dependabot.yml file
# REF: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot
version: 2
updates:
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Look for `.github/workflows` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
interval: "daily"
The above file is about depebdabot which daily scans the entire repository for vulnurabilty scanning and keep your packages uptodat on daily basis , which eventually automatically pull new request on you github project on weekly

6. Manual and automatic(automation through IAC) deployment on Civo cluster
for civo
terraform { required_providers { civo = { source = "civo/civo" version = "0.10.3" } } } provider "civo" { token = "token" } resource "civo_kubernetes_cluster" "my-cluster" { name = "sammy" region = "LON1" applications = "Argo-cd" num_target_nodes = 3 target_nodes_size = "g3.k3s.xsmall" }Deployments/IAC/CIVO/main.tf
Now at the end we want to deploy our terraform IAC structure to cloud for that matter we will scan and check format of our terraform IAC using github actions as follows
name: Continuous-Staging (Terraform)
on:
push:
branches:
- "master"
pull_request:
permissions:
contents: read
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
# Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
defaults:
run:
shell: bash
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
# Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
- name: Terraform Init
run: |
cd Deployments/IAC/AWS
terraform init
# Checks that all Terraform configuration files adhere to a canonical format
- name: Terraform Format
run: |
cd Deployments/IAC/AWS
terraform fmt -check
Finally we are done with our project , thanks for reading folks !




