Creating a fully automated Multibranch CI/CD pipeline for React

Index
-
Step 1: Dockerizing the Application
- Step 2: Setting up Nexus.
- Step3: Setting up Jenkins.
- Step4: Creating the pipeline
-
Step 5: Ansible and Jenkinsfile Configuration
Step 1: Dockerizing the Application
In this pipeline, we will use Docker to Containerize our application. This makes our application portable. Create a file named 'Dockerfile' in the root of the project directory and paste the following code. This code is basically creating a multi-stage build where the first stage is the normal build process of a react application and the second stage involves copying the 'dist' folder from the 1st build (builder) to the nginx default html path to serve the html and copying 'nginx.conf' from the currect directory.
# Dockerfile
# Step 1: Build the application
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Step 2: Set up the production environment
FROM nginx:stable-alpine-perl
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 5000
CMD ["nginx", "-g", "daemon off;"]
nginx configuration file.
# nginx.conf
server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
Both the files will be saved in the root folder of the project.
Step 2: Setting up Nexus.
Nexus is an open source Artifact Repository Manager. We will use Nexus to Store our docker image versions and use these images for deployment for either staging or production environment based on which branch the changes are deployed. (This is explained later) To get up and running with nexus, you will require a VM running. You can use any cloud provoder you are familiar will the steps will remain more or less same. I will be using a DigitalOcean Droplet. You will also require a firewall setup to allow inbound traffic to the port at which nexus will be running.(default port is 8081).
- Create a Digital Ocean Droplet
- Add SSH key of you machine to later access the VM.
- Attach the Firewall rules
- SSH into the machine and install OpenJDK - 8. (Nexus uses Java version-8)
sudo apt install openjdk-8-jre-headless
sudo apt install openjdk-8-jdk-headless
java -version
- Download Nexus
cd /opt
wget https://download.sonatype.com/nexus/3/latest-unix.tar.gz
ls
tar -zxvf latest-unix.tar.gz
ls
# 2 folders named nexus-[version] and sonatype-work/nexus3 have been extracted
- Create new User called nexus
- Attach nexus user to the 2 folders
chown -R nexus:nexus [folder 1]
chown -R nexus:nexus [folder 2]
ls -l
- Set nexus configuration so Nexus will run as a nexus user.
vim nexus-[version]/bin/nexus.rc
# inside the editor
run_as_user="nexus"
- Run Nexus.
su - nexus
# Start Nexus
/opt/nexus-[version]/bin/nexus start
# Check if running
ps aux | grep nexus
# Install net-tools
exit
apt install net-tools
su - nexus
netstat -lnpt
You should now be able to run nexus in you browser at http://<DROPLET-IP>:8081.Further more you will require to create a Docker hosted repository in nexus, create a nx-docker user to access the repository, create a new role for the repository with suitable permissions. Attach the Role to the user. Refer official documentation to do these steps. Keep in mind after creating the repository add an http or https connector (based on what your jenkins server is running) port as 5000.
Step 3: Setting up Jenkins.
Jenkins is an open source automation server which allows us to build pipelines for our application, it comes with a number of built in pluggins which we will be using to build the pipeline for out React application along with some manual bash scripting. To get up and running with Jenkins, The Intial Process of Setting up the machine and Firewall will remain the same as for nexus. For the Installation we will be using a docker image of Jenkins.
- SSH into the machine.
- Update your system
-
Install Docker
- Pull the Jenkins Image jenkins/jenkins:lts
- Run The Jenkins Container.
Aditionally since we will be running many docker commands in our pipeline it is required that we install docker inside the Jenkins container. Refer this article to install Docker inside Docker. Once Docker is setup inside the container, create a daemon.json file at the destination /etc/docker and add the following code. Do the same outside of the container.
# daemon.json
{
"insecure-registries": ["<Nexus_Repo_Url>:5000"]
}
Assuming you now have Jenkins running, open Jenkins in you browser and login into Jenkins using initial admin password.
# find the initial adming password here inside the jenkins container
cat /var/lib/jenkins/secrets/initialAdminPassword
Once you are in, Install the following plugins.
- Multibranch Scan Webhook Trigger
- Gitlab Plugin
- Nodejs (Install manually inside container if the required version is not available on Jenkins)
- Ansible (same as Nodejs)
Restart Jenkins.
Step4: Creating the pipeline.
Once everyting is setup and all the plugins are Installed. We will First add Some Credentials that we will require. Manage Jenkins > Mange Credentials.
- Gitlab Credentials - Add you gitlab usename and password and give the id and description as 'gitlab-cred'.
- Nexus User - Create a new username and password and add the username and password of the nexus user which we created earlier.
Once all the credentials are set, we will create a new Multi-branch-pipeline. Name it as react-multibranch-pipeline.
- Create a New Multibranch pipeline.
- Under Branch Sources → Add source → Chose Git → and provide Gitlab URL and Credentials (Credentials are optional if it is public repo).
- Under Build Configuration → choose Jenkinsfile path. Most of the case it will be in the root directory of your repository.
- Apply and Save the job.
Now Jenkins automatically scans the repository and create a job for each branch wherever it finds a Jenkinsfile and initiate first build. To automate this process on every push event to our repository, enable the option "scan by webhook" under "Scan Multibranch Pipeline Triggers". Here we should give a token. I am giving it as "gitlabtoken".
- In Gitlab navigate to your project settings > Webhooks.
- Set the url to '<JENKINS_URL>/multibranch-webhook-trigger/invoke?token=gitlabtoken'.
- Disable SSL verification and click Add webhook.
Make changes to the code and check if the respective branch pipeline gets triggered.
Step 5: Ansible and Jenkinsfile Configuration
Thank you for sticking with me till now. Since our pipeline is setup you can now create seperate Jenkinsfile for every branch which will be used when a push event is triggered to any of the branches or create a same file and add a 'when' directive to specify steps for a specific branch in a single file. For this Example we will be creating a single Jenkinsfile.
# when directive in Jenkinsfile
when {
branch pattern:'feature/[a-z]*',comparator: "REGEXP"
}
steps {
// Some pipeline step for feature branch
}
Jenkinsfile
This file is written in Declarative Pipeline Syntax of Jenkins which follow the same rule as Groovy Syntax.
pipeline {
agent any
environment {
NEXUS_REPO_URL = 'http://206.189.129.73:5000/repository/docker-hosted/'
DOCKER_IMAGE_VERSION = "1.0.0"
DOCKER_IMAGE_TAG = "whiteboard"
DOCKER_IMAGE_TYPE = "release"
STAGING_SERVER_IP = ""
PROD_SERVER_IP = ""
SSH_PRIVATE_KEY_PATH="/var/jenkins_home/.ssh/id_rsa"
}
stages {
stage("Checkout from SCM"){
steps {
sh 'echo "SCM checkout of branch \'$GIT_BRANCH\'"'
git branch: env.GIT_BRANCH, credentialsId: 'gitlab-cred', url: 'https://gitlab.com/Kirtish_Patil/whiteboard.git'
}
}
stage("Testing the application"){
steps {
script {
sh '''
echo "Installing npm packages"
npm i
echo "Running tests"
npm run test
if [ $? -ne 0 ]; then
echo "Tests Failed"
exit 1
fi
echo "Tests Completed Successfully"
'''
}
}
stage("Building Docker Image") {
steps {
sh 'echo "Building Docker Image for branch \'$GIT_BRANCH\'" of tag \'${DOCKER_IMAGE_TAG}-${DOCKER_IMAGE_TYPE}:${DOCKER_IMAGE_VERSION}\''
script {
dockerImage = docker.build("${DOCKER_IMAGE_TAG}-${DOCKER_IMAGE_TYPE}:${DOCKER_IMAGE_VERSION}")
}
}
}
stage("Push Docker image to Nexus"){
steps {
script {
docker.withRegistry("${NEXUS_REPO_URL}", 'nexus-repo-cred'){
dockerImage.push()
}
}
}
}
stage("Deploying to staging environment"){
when {
branch 'master'
}
steps {
sh '''
echo "Deploying to master"
ansible-playbook -i ansible/hosts ansible/setup-docker.yml | tee ansible_setup_output.txt
ansible-playbook -i ansible/hosts ansible/deploy-app.yml | tee ansible_deploy_output.txt
echo "===== Ansible Setup Output ====="
cat ansible_setup_output.txt
echo "===== Ansible Deploy Output ====="
cat ansible_deploy_output.txt
'''
}
}
stage("Global stage for all branches") {
steps {
echo "Process was successful..."
}
}
}
post {
always {
script {
cleanWs()
sh 'docker image prune -f'
}
}
}
}
Ansible
Ansible is another automation open source tool that allows us to provide provisioning of nodes/vms. In this pipeline we will be dividing our ansible-playbook into 2 file, 1 for setup and 2nd for deployment, thus provisioning our machine on which our application will be hosted.
- Setup
# ansible/setup-docker.yaml
---
- name: Setup Docker on Staging Environment
hosts: whiteboard_staging
become: yes
tasks:
- name: Update apt package index
apt:
update_cache: yes
- name: Install Snap
apt:
name: snapd
state: present
- name: Ensure snapd is running
systemd:
name: snapd
state: started
enabled: yes
- name: Install Docker using snap
snap:
name: docker
state: present
- name: Create Docker daemon configuration directory
file:
path: /etc/docker
state: directory
mode: "0755"
- name: Configure Docker daemon for insecure registry
copy:
content: |
{
"insecure-registries": ["206.189.129.73:5000"]
}
dest: /var/snap/docker/current/config/daemon.json
owner: root
group: root
mode: "0644"
- name: Restart Docker service
systemd:
name: snap.docker.dockerd
state: restarted
enabled: yes
- name: Ensure Docker service is running
service:
name: snap.docker.dockerd
state: started
- Deployment
# ansible/deploy-app.yaml
- name: Deploy App on staging Environment
hosts: whiteboard_staging
become: yes
tasks:
- name: Ensure Docker daemon is running
become: yes
service:
name: snap.docker.dockerd
state: started
- name: Login to Nexus Docker Registry
command: sudo docker login -u nx-docker -p 123 206.189.129.73:5000
- name: Pull Docker Image
command: sudo docker pull 206.189.129.73:5000/whiteboard-feature:1.0.0
- name: Run Docker Container
command: sudo docker run -d -p 8080:8080 206.189.129.73:5000/whiteboard-feature:1.0.0
Also add a hosts file for specifying the ip address of the machine where our application will be hosted along with the path to the ssh private key file.
[whiteboard_staging]
68.183.83.29 ansible_ssh_private_key_file=~/.ssh/id_rsa ansible_user=root
Conclusion.
And we are done, there are n number of possibilities to where you can take this pipeline to. Setting up a CI/CD pipeline for your React application helps automate testing, building, and deployment, making your workflow faster and more reliable. It ensures consistent code quality and quicker releases, allowing you to deliver better applications with less effort.