DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Using Newman to Run Postman Collections
  • Stop Writing Dialect-Specific SQL: A Unified Query Builder for Node.js
  • Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
  • Building MCP Hub for DevOps and CI/CD Pipelines

Trending

  • The Invisible OOMKill: Why Your Java Pod Keeps Restarting in Kubernetes
  • Pragmatica Aether: Let Java Be Java
  • Your AI Agent Tests Are Passing, But Your Agent Is Still Broken
  • Every Cache Miss Is a Tiny Tax on Your Performance
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Automating Node.js Deployments With a Custom CI/CD Server

Automating Node.js Deployments With a Custom CI/CD Server

Learn to automate Node.js deployments with a custom CI/CD server using GitHub webhooks, GitHub Actions, PM2, and shell scripting for seamless updates.

By 
Kolawole Yusuf user avatar
Kolawole Yusuf
·
Aug. 06, 25 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
3.3K Views

Join the DZone community and get the full member experience.

Join For Free

It is possible that managing and deploying Node.js applications can become a bottleneck as projects grow. Having a properly designed Continuous Integration and Continuous Deployment (CI/CD) pipeline can help reduce the burden of frequent updates, simplify dependency management, and eliminate the need for manual restart processes, thereby avoiding these bottlenecks.

In this tutorial, we will create a custom CI/CD server that listens to GitHub webhook events and performs deployments using GitHub Actions, PM2, and shell scripting. This enables us to:

  1. Get the latest changes from the GitHub repository when updates are made.
  2. Identify changes in dependencies and install npm only if there are changes.
  3. Stop and restart the application using PM2 when there is an update.
  4. Manage different projects conveniently from a single CI/CD server. 

Once you've completed this tutorial, you will have a fully functional self-hosted deployment pipeline that can be used across different projects on AlmaLinux (or any Linux server). This approach is particularly helpful for developers who want more control over their deployment processes than what they can achieve with GitHub Actions or similar third-party CI/CD services. 

What We are Building

Our goal is to develop a lightweight Node.js CI/CD server that:

  1. Listens to the GitHub webhook events based on the commit being pushed to a particular branch.
  2. Validates the payload to confirm that the request is coming from GitHub.
  3. Executes a shell script (deploy.sh) that:
  • Changes the directory to the right project
  • Pulls the repository
  • Checks for and installs any missing dependencies
  • Restarts the project using PM2
  1. Sends the deployment status to GitHub for logging.

This tutorial will walk you through the basics of Node.js, GitHub Actions, and Linux shell scripting. If you are new to PM2 or GitHub webhooks, do not worry, we will explain each in detail.

Let's Set Up Our CI/CD Server

Let's configure a CI\CD server based on Node.js that will automate deployments by monitoring GitHub webhooks and initiating the deployment process using PM2 and shell scripting.

Prerequisites

Make sure you have the following installed on your server before we get started; 

1. Node.js (version 16 or newer): You can download it using vim or install it directly from the package manager. 

2. PM2: To manage Node.js applications effectively, you can globally install PM2 by following these steps; 

npm install -g pm2

3. Git: Required for pulling the latest code. Install via:

sudo dnf install git -y

4. NGINX or Apache (optional): If you want to expose the CI/CD server through a domain.

5. GitHub Setup: Make sure your project is hosted on GitHub and that you have the necessary permissions to configure webhooks.

Creating the CI/CD Server

Step 1: Initialize a Node.js project

First, create a new directory for the CI/CD server and initialize a Node.js project:

mkdir ~/cicd-server && cd ~/cicd-server 

npm init -y

Install the required dependencies:

npm install express body-parser crypto


Step 2: Build the webhook listener

Create a file named index.js and populate it with the following code:

JavaScript
 
const express = require("express");

const bodyParser = require("body-parser");

const crypto = require("crypto");

const { exec } = require("child_process");



const app = express();

const PORT = 4000;

const GITHUB_SECRET = process.env.GITHUB_SECRET || "your-secret-key";



app.use(bodyParser.json());



app.post("/webhook", (req, res) => {

  const signature = `sha256=${crypto

    .createHmac("sha256", GITHUB_SECRET)

    .update(JSON.stringify(req.body))

    .digest("hex")}`;



  if (req.headers["x-hub-signature-256"] !== signature) {

    return res.status(401).json({ message: "Invalid signature" });

  }



  const repoName = req.body.repository.name;

  console.log(`Received update for: ${repoName}`);



  exec(`bash ./deploy.sh ${repoName}`, (error, stdout, stderr) => {

    if (error) {

      console.error(`Deployment failed: ${stderr}`);

      return res.status(500).json({ message: "Deployment failed", error: stderr });

    }

    console.log(`Deployment successful: ${stdout}`);

    res.json({ message: "Deployment successful", output: stdout });

  });

});



app.listen(PORT, () => console.log(`CI/CD server running on port ${PORT}`));


Step 3: Write the deployment script

Now create a deploy.sh file in the same directory:

Shell
 
REPO_NAME=$1

BASE_DIR="/var/www/projects"

PROJECT_DIR="$BASE_DIR/$REPO_NAME"


echo "Starting deployment for $REPO_NAME..."


if [ ! -d "$PROJECT_DIR" ]; then

  echo "Error: Directory $PROJECT_DIR does not exist."

  exit 1

fi


cd "$PROJECT_DIR"

echo "Pulling latest changes..."

git pull origin main


CHANGES=$(git diff --name-only HEAD@{1} HEAD)


if [[ $CHANGES == *"package.json"* ]]; then

  echo "Detected dependency changes. Running npm install..."

  npm install

fi


echo "Restarting application..."

pm2 restart $REPO_NAME


echo "Deployment completed."


Make it executable:

chmod +x deploy.sh


3. Configure GitHub webhooks

  • Go to GitHub → Your Repository → Settings → Webhooks.
  • Click "Add webhook".
  • Set Payload URL to: http://server-ip:4000/webhook
    (If you are using NGINX or Apache, replace it with your domain. as in example.com)
  • Set Content type to "application/json".
  • Add a secret with the same value used for GITHUB_SECRET in your index.js file.
  • Choose the push event and click "Add webhook".

4. Run the CI/CD server

Start the CI/CD server with PM2:

pm2 start index.js --name cicd-server 

pm2 save


5. Add NGINX or Apache Reverse Proxy

If you want to expose the CI/CD server through a domain, set up an NGINX or Apache reverse proxy.

NGINX configuration

Create an NGINX config file:

sudo nano /etc/nginx/conf.d/cicd.conf

content:

Shell
 
server {

    listen 80;

    server_name example.com;



    location / {

        proxy_pass http://localhost:4000;

        proxy_set_header Host $host;

        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    }

}


Reload NGINX:

sudo nginx -t 

sudo systemctl reload nginx


Apache configuration

Create a virtual host config:

sudo nano /etc/httpd/conf.d/cicd.conf

content:

Shell
 
<VirtualHost *:80>

    ServerName example.com

    ProxyPass / http://localhost:4000/

    ProxyPassReverse / http://localhost:4000/

    ErrorLog /var/log/httpd/cicd-error.log

    CustomLog /var/log/httpd/cicd-access.log combined

</VirtualHost>


Restart Apache:
sudo systemctl restart httpd

6. Test the deployment

Push an update to your repository and check if:

  • The webhook triggers the CI/CD server.
  • The script pulls changes and installs dependencies if needed.
  • PM2 restarts the application.

Wrapping Up

We have successfully created a self-hosted CI/CD pipeline for Node.js deployments using GitHub Actions, PM2, and shell scripting. This solution is lightweight, cost-effective, and can be easily scaled up to accommodate multiple projects.


Node.js Webhook Continuous Integration/Deployment

Opinions expressed by DZone contributors are their own.

Related

  • Using Newman to Run Postman Collections
  • Stop Writing Dialect-Specific SQL: A Unified Query Builder for Node.js
  • Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
  • Building MCP Hub for DevOps and CI/CD Pipelines

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook