Rolling Your Own Continuous Deployment with Node.js, Git, PM2, and Linux

Rolling Your Own Continuous Deployment with Node.js, Git, PM2, and Linux

Continuous deployment involves frequently integrating code changes into a shared repository. This allows for your production app to be updated with the latest code changes in a continuous release format easily. Several turn-key solutions exist from many different providers. However, in this article, we’ll explore how to roll your own continuous integration system using Node.js, Git, PM2, and Linux.

Prerequisites

  • A Node.js app that has its source code hosted in a bare git repository on the server. This is different than just cloning the repository. The server is actually hosting the repo. I wrote a guide for this.

  • The repository has been cloned to another location on the server. This is the production code that PM2 will manage.

  • The production code for the app is being run and managed by PM2. I also wrote a guide for this.

Set up CI

In the bare repository (not the cloned repository that PM2 is running from), create a post-receive git hook by creating a file named [repo name]/hooks/post-receive.

#!/bin/bash

while read oldrev newrev refname
do
  branch=$(git rev-parse --symbolic --abbrev-ref $refname)
  if [ "master" = "$branch" ]; then
    cd /path/to/cloned/app
    unset GIT_DIR
    git pull
    npm ci
    npm run build
    pm2 reload [app name]
  fi
done

On the line if [ "master" = "$branch" ]; then, master must be the name of the branch which should trigger CI when it's pushed to. If you use some other name for your master branch, just change it on that line.

On the line cd /path/to/cloned/app, make sure that path points to the actual cloned production code that PM2 is managing.

On the line pm2 reload [app name], the [app name] must be the name you set in ecosystem.config.js.

Make the hook executable.

chmod +x [repo name]/hooks/post-receive

The setup is complete. Here is the process this continuous integration solution follows:

  1. A developer pushes changes to the bare repository

  2. The post-receive hook in the bare repository is automatically executed

  3. The hook changes into the cloned production repository and pulls the changes

  4. Any missing dependencies are installed

  5. The production app is rebuilt

  6. The processes are reloaded by PM2, one at a time for zero downtime

All of this happens automatically when changes are pushed to the repository.

Reduce Downtime on a Next.js App

With the current configuration, there will still be some downtime when Next.js builds your app. During the build, it removes everything from the .next directory, then repopulates it. Since your running processes are accessing files in the .next directory, the app becomes temporarily unavailable.

You can solve this by having Next.js build into a temporary directory, then moving the built files over once the build is complete.

Edit next.config.js. Add the distDir line below.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  distDir: process.env.BUILD_DIR || ".next",
};

This allows us to set a build directory at build time.

Now edit the post-receive hook again. The changes are...

  1. Modify the build command to set a temporary build directory .next_temp

  2. Add a command to remove the old .next directory

  3. Add a command to rename .next_temp to .next.

#!/bin/bash

while read oldrev newrev refname
do
  branch=$(git rev-parse --symbolic --abbrev-ref $refname)
  if [ "master" = "$branch" ]; then
    cd /path/to/cloned/app
    unset GIT_DIR
    git pull
    npm ci
    BUILD_DIR=.next_temp npm run build
    rm -rf .next
    mv .next_temp .next
    pm2 reload [app name]
  fi
done

Now you have a running app and a bare git repository hosted on the server. When a developer pushes to that repository, the changes are automatically applied and pushed to your production environment.

In this article, we’ve explored how to set up a continuous integration system using Node.js, Git, PM2, and Linux. By leveraging git hooks and PM2’s process management capabilities, we can enable continuous releases of our production app without much hassle.

Cover photo by Omid Armin on Unsplash.