After you developed your React website you want to set up a CI/CD pipeline that automates the delivery of the application to the end-users? This article briefly shows you how to do it!
Step 0 - Full Jenkinsfile Configuration
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
stage('Install npm and node js') {
steps {
script {
def npmVersion = sh(script: 'npm -v', returnStatus: true)
if (npmVersion != 0) {
echo 'Installing npm and node js'
sh '''
sudo apt update
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y
sudo apt install build-essential
'''
} else {
echo 'npm and node js already installed'
}
}
}
}
stage('Install Dependencies') {
steps {
sh 'npm i'
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage ('Move to Nginx') {
steps {
sh 'sudo mkdir -p /var/www/html/octopus'
sh 'sudo rm -rf /var/www/html/octopus/dist' // Safely remove the directory if exists
sh 'sudo mv dist /var/www/html/octopus'
}
}
stage ('Configure Nginx') {
steps {
script {
def configFile = '''
server {
listen 80;
location / {
return 301 https://85.120.206.53$request_uri;
}
}
server {
listen 443 ssl http2;
include /etc/nginx/ssl/ssl_all_sites.conf;
include /etc/nginx/ssl/ssl_codelighthouse.conf;
root /var/www/html/octopus/dist;
index index.html;
location / {
try_files $uri /index.html;
}
}
'''
writeFile file: '/etc/nginx/sites-available/octopus.conf', text: configFile
def file = new File('/etc/nginx/sites-enabled/octopus.conf')
if (!file.exists()) {
sh 'sudo ln -s /etc/nginx/sites-available/octopus.conf /etc/nginx/sites-enabled'
}
}
}
}
stage ('Restart Nginx') {
steps {
sh 'sudo systemctl reload nginx'
}
}
}
}
Step 1 - Add the Jenkinsfile
You are going to want to create a Jenkinsfile
inside your website folder. This will serve as the code that Jenkins will use to create your pipeline.
Step 2 - Set up the Jenkinsfile code
Open up a pipeline that can run on any agent (or any specific one if you have) and has a timeout of an hour to avoid infinite running.
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS')
}
Then define your stages as such:
Stage - Checkout code
First, checkout the code from the SCM e.g GitHub:
stages {
stage('Checkout Code') {
steps {
checkout scm
}
}
Stage - Install npm and node
Then make sure you have npm
and node
installed:
stage('Install npm and node js') {
steps {
script {
def npmVersion = sh(script: 'npm -v', returnStatus: true)
if (npmVersion != 0) {
echo 'Installing npm and node js'
sh '''
sudo apt update
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y
sudo apt install build-essential
'''
} else {
echo 'npm and node js already installed'
}
}
}
}
Let's explain each line:
def npmVersion = sh(script: 'npm -v', returnStatus: true)
defines a variable called npmVersion
that will either hold the version of the already installed npm package or 0 if npm
is missing.
sh '''
sudo apt update
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y
sudo apt install build-essential
'''
will first update apt
, add the latest version of node
because the default version that apt
will fetch is a very old one; install the lts version of node
that also comes with npm
; installs build-essentials
that is required by npm to compile source scripts.
Stage - Install Dependencies
stage('Install Dependencies') {
steps {
sh 'npm i'
}
}
Stage - Build React app
stage('Build') {
steps {
sh 'npm run build'
}
}
Stage - Move files to Nginx directory
stage ('Move to Nginx') {
steps {
sh 'sudo mkdir -p /var/www/html/octopus'
sh 'sudo rm -rf /var/www/html/octopus/dist' // Safely remove the directory if exists
sh 'sudo mv dist /var/www/html/octopus'
}
}
Let's explain each line:
sh 'sudo mkdir -p /var/www/html/octopus'
creates a folder that will hold the built web app inside the NginX serving folder.
sh 'sudo rm -rf /var/www/html/octopus/dist'
removes the folder dist
if it already exists e.g from previos builds of the pipeline.
sh 'sudo mv dist /var/www/html/octopus'
moves the built folder dist
inside the previously created folder.
Stage - Configure Nginx
stage ('Configure Nginx') {
steps {
script {
def configFile = '''
server {
listen 80;
root /var/www/html/octopus/dist;
index index.html;
location / {
try_files $uri /index.html;
}
}
'''
writeFile file: '/etc/nginx/sites-available/octopus.conf', text: configFile
def file = new File('/etc/nginx/sites-enabled/octopus.conf')
if (!file.exists()) {
sh 'sudo ln -s /etc/nginx/sites-available/octopus.conf /etc/nginx/sites-enabled'
}
}
}
}
Let's explain the above code:
listen 80;
listen on the unsecured http
port: 80
.
root /var/www/html/octopus/dist;
sets the root for easier access to files. All further references will be made with regards to this root when it comes to paths.
index index.html;
tells NginX to always return index.html
when a folder is requested with no file e.g http:/site.com/
or http:/site.com/folder/
.
location / {
try_files $uri /index.html;
}
tells NginX to try to serve the file requested i.e $uri
that can look like http://site.com/file.php
for example and if it can't find it, return the index.html
file. This is imperative for React builds as the router works by taking in requests for the index.html file. Failing to redirect requests like http://site.com/app
to the index.html
file will result in the routing not working.
Stage - Restart Nginx
stage ('Restart Nginx') {
steps {
sh 'sudo systemctl reload nginx'
}
}
Note: Always use reload
instead of restart
. If the configuration file is flawed and throws an error restart
will leave NginX down, whereas reload
will throw the error but will not load the flawed configuration file so NginX will continue to work with the old configuration.
Step 3 - Push Jenkinsfile to GitHub
You can now go ahead and push the configuration file to the GitHub repository.
Step 4 - Allow Jenkins to Run sudo
Commands
We used many sudo
commands in the pipeline code, but we have to grant Jenkins the rights to act as a super user with no password input requirements.
Step 4.1 - Jenkins User
Make sure the server running the Jenkins controller has a Jenkins
user created:
id jenkins
If the user does not exist, create it:
sudo useradd -m jenkins
Flags used:
-
-m
creates a home directory for the user
Step 4.2 - Make Jenkins
a Super User
Add the Jenkins
user to the super users group (sudo)
sudo usermod -aG sudo jenkins
Flags used:
-
-a
append a new group to the existing ones -
-G
specify a group to add the user to
Step 4.3 - Allow Jenkins
Super User to not Utilize Passwords
Open the visudo
file:
sudo visudo
and add Jenkins
at the end of the file:
jenkins ALL=(ALL) NOPASSWD: ALL
Step 5 - Create a Jenkins Multi-Branch Pipeline
Inside Jenkins you have to create a new Multi-Branch pipeline that will scan all the branches of your repository and create pipelines for them based on the found Jenkinsfile
.
This can be done by following this route inside Jenkins:
Dashboard -> New Item -> Enter a name -> Multibranch Pipeline
Then go ahead and set up the GitHub repo:
Repository HTTPS URL
Step 6 - Build the Pipeline
Build the pipeline by clicking on one of your branches and then Build Now
Step 7 - Set up GitHub Webhooks
If your pipeline has been successfully created, you just have to tell GitHub to let Jenkins know of any push that happens to the repository. This can be done via webhooks
:
Inside GitHub repo:
Settings -> Webhooks -> Add Webhook
The Payload URL
is http://<your_server_ip>:8080/github-webhook/
Conclusions
This article has briefly showed you how to set up a full CI/CD pipeline for delivering your React app using NginX and Jenkins. Everytime you push changes to your repository, you can expect to see them live in a few minutes.
Note:This is just a basic tutorial, many more security and performance steps must be completed in order to have an industrial-grade pipeline.
Top comments (0)