Set Up A CI/CD Pipeline with NodeJS, Jenkins and Google Cloud

Żimuzo Obiechina
8 min readSep 27, 2021

--

This article describes how to set up a CI/CD pipeline in Jenkins that builds and deploys a NodeJS application to the Google Cloud Platform(GCP).

Prior knowledge of how to manage users in Linux and how to configure an Nginx server is required.

We will cover the following in this article:

  • Create Compute Engine virtual machine(VM) instances on Google Cloud.
  • Install and configure necessary dependencies on VM.
  • Set up SSH credentials with GitHub.
  • Add GitHub webhook.
  • Create Jenkinsfile.
  • Set up Publish Over SSH in Jenkins.
  • Create CI/CD pipeline.

Prerequisites:

  • A Google Cloud account — if you do not already have an account, create one here.
  • Jenkins (v2.312)
  • Node (v16.9.1)
  • npm (v7.24.0)

Create virtual machine instances

We will need to have the Jenkins application running on its server and the NodeJS application running on another.

Let’s create two VM instances on GCP — a Jenkins server and an application server, on the default VPC network. Use the gcloud command-line tool to run the following commands:

# Create jenkins server
$ gcloud compute --project=<project-name> instances create <instance-name> --zone=us-central1-f --machine-type=e2-small --subnet=default --tags=<add-tags>,http-server
# Create application server
$ gcloud compute --project=<project-name> instances create <instance-name> --zone=us-central1-a --machine-type=e2-small --subnet=default --tags=<add-tag>,http-server

Create firewall rules to expose port 8080 for Jenkins and port 3000 for your NodeJS application:

# for app server
$ gcloud compute firewall create <name> --network --action allow --target-tags <instance-tag> --source-ranges 0.0.0.0/0 --rules tcp:3000
# for jenkins server
$ gcloud compute firewall create <name> --network --action allow --target-tags <instance-tag> --source-ranges 0.0.0.0/0 --rules tcp:8080

Confirm that the VMs are up and running:

$ gcloud compute instances list

Confirm the firewall rules are created:

$ gcloud compute firewall-rules list

Connect to instances through SSH and create a jenkins user in both instances:

# Connect via SSH
$ gcloud compute ssh --zone=us-central1-a <jenkins-server>
# Create user
$ adduser jenkins
# Perform the same operations in the application server

Now that we have our server instances up and running, let’s install the necessary dependencies required to run the NodeJS application.

Install and configure dependencies on the Jenkins server

Before we can begin to use the Jenkins application to set up a CI/CD pipeline for the NodeJS application, we need to do the following:

  • Install Jenkins on the server.
  • Install node and npm on the Jenkins server to enable Jenkins to run dependencies for the NodeJS application.
  • Install mocha to run tests — use the command npm install -g mocha

Enter the public IP address of the Jenkins server into your browser to view the Jenkins application — remember to include the port number. Follow the instructions to complete the installation.

Once Jenkins installation is complete, search for and install the NodeJS plugin — Manage Jenkins > Manage Plugins.

And then, enable NodeJS as a tool to use within Jenkins. Navigate to Manage Jenkins > Global Tool Configuration > NodeJS installations > Add NodeJS.

Add NodeJS installation

Now, let’s configure the application server.

Install and configure dependencies on the application server

We will serve our NodeJS application on an Nginx web server. But first, let’s install node and npm on the application server.

Then, install Nginx:

$ sudo apt install nginx

Configure Nginx as a reverse proxy — add the following server configuration to your configuration file in the /etc/nginx/sites-available directory( you may also use the default file /etc/nginx/sites-available/default ):

server {
listen 80;
listen [::]:80;
server_name _;
root /var/www/<your-directory-name-containing-app-files>;
index index.html app.js;

location / {
try_files $uri $uri/ =404;
proxy_pass http://<app-server-external-ip>:3000
}
}

The proxy_pass directive declares that the Nginx should serve as a proxy and send requests that target the / root url to whatever IP address or domain name specified — in this case, it will be the application server url with the default port for the NodeJS application.

We also need to install a tool that will monitor the application and keep it online once it is deployed to the application server— the pm2 tool.
Install pm2 using the following command:

$ npm install pm2 -g

In your code editor, create a pm2 config file following this template.

Set up SSH credentials with GitHub

We need to enable the Jenkins application to securely connect with GitHub via SSH. In your Jenkins server, create an SSH key pair. Run the following command:

$ ssh-keygen

At the prompt, add the file location(or leave as is). You may also add a passphrase at the prompt. Press Enter to create the keys.

Add the public key to GitHub — run cat <file path to .pub file> and copy the public key.

Go to the GitHub repository for your NodeJS application. Navigate to Settings > Deploy Keys > Add Deploy Key. Paste in the public key in the text box.

Add public SSH key

Add the private key to the Jenkins application — go back to your Jenkins server; run cat <file path to private key> and copy the private key.

In the Jenkins GUI, go to Manage Jenkins >Manage Credentials. Click on 'global' and select Add Credentials. Select Enter directly and paste in the private key.

Navigate to Manage Jenkins >Manage Credentials. Click on ‘global’
Add credentials form

Next, install git on your Jenkins server:

$ sudo apt install git

Confirm that the git plugin is enabled on the Jenkins application.

Ensure git plugin is enabled

Add GitHub webhook

A webhook listens for any specified events on a target application. When there is an event, it sends data(requests) that trigger processes in a specified endpoint.

We want to automatically trigger the CI/CD pipeline to run anytime there is a push commit to the repository.

First, let’s generate an access token. This token is a form of authentication to ensure that the request received is from GitHub only.

In Jenkins, click on the user icon on the top right, select Configure.

Go to Add New Token. Generate an API token and copy it. Save changes.

Go to GitHub. Navigate to Settings > Webhooks > Add Webhook.

The payload URL will be <your-jenkins-instance-URL>/github-webhook/ . Paste in the API token in the Secret field. You may leave the other options as is. Click on the Add webhook button.

Set up Publish Over SSH

We will automate the deployment process to the application server. To do this, we need to establish SSH authentication for a secure connection.

Install the Publish Over SSH plugin — Manage Jenkins > Manage Plugins.

In the Jenkins GUI, navigate to Manage Jenkins > Configure System > Publish Over SSH.

Add the required details:

  1. Enter your Passphrase — if you have one set up with SSH.
  2. Enter the path to the private key of your Jenkins in the “Path to Key” field.
  3. In the SSH Servers section, enter a logical name in “Name”.
  4. Enter the IP address of the application server in the “Hostname” field.
  5. Enter the username to log in within the “Username” field.
  6. Enter the directory that the build artifact should be stored in the remote server, in the “Remote Directory” field.
  7. Click “Test Configuration”.
  8. Click “Save”.

Learn more about Publish Over SSH in the Jenkins official documentation here.

Create a Jenkinsfile

A Jenkinsfile is a declarative document that contains your pipeline script — it contains the commands and instructions that you would like the Jenkins application to carry out for you at different stages of the pipeline. You can apply this template to create your Jenkinsfile.

Below is a sample Jenkinsfile for a NodeJS application:

The Build stage installs the node dependencies and creates a compressed file containing only files relevant to the application— a tar.gz file: this is the build artifact.

The Test stage runs the tests written in Mocha.

The Deploy stage connects with the remote server via SSH and then publishes the application by running the commands specified within the steps block.

Learn more about the Pipeline syntax here.

If you are unsure of the code to include in your pipeline script, navigate to the Pipeline Syntax Generator to generate code snippets for various actions you would like to declare within your script.

Create CI/CD pipeline

Now that we have all installations and configurations done, let’s go ahead and create a simple pipeline in Jenkins. On your Jenkins Dashboard, select New Item.

Enter a name, select Pipeline and click OK.

Under Build Triggers, select GitHub hook trigger for GITScm polling

Under Advanced Project Options, select Pipeline Script From SCM

Go to your GitHub repository, copy the SSH remote URL.

Under SCM, add the SSH remote URL.

Select the SSH credentials you created above — for SSH authentication with Jenkins.

Under Branches To Build, enter the branch name. Click “Save”.

Your pipeline is all set up! Now, any commit pushed to the specified branch of the GitHub repository automatically triggers the CI/CD pipeline.

Putting it all together

Generally, when you’re deploying a NodeJS application, you should have a frontend part of the application — usually in React. In order to connect these two parts, follow these steps described in this article to deploy a React application.

The only modification you will need to make is to add the following snippet to the Nginx configuration file — within the location directive:

proxy_pass <node server ip>

Conclusion

In a nutshell, setting up SSH credentials with GitHub and adding a webhook to listen for events on the repository constitutes the continuous integration (CI) part of the pipeline — while Publish over SSH takes care of the continuous deployment (CD) phase.

You can implement the process described above using this project here — to see how everything works together. Ideally, it should run smoothly; however, if you run into any issues, feel free to reach out to me—we’ll work through it together.

I hope you find this article useful. Thank you for reading!

--

--