Part II: Setting Up AWS CodeDeploy/CodePipeline to Automate the Deployment of a Node Web Service

By Bobby Gill on November 24, 2021

This Part 2 of a 3-part series that walks through the steps of using Amazon CodePipeline and CodeDeploy to automate the deployment of a Node based web service to an EC2 environment. Before continuing on this guide, please make sure to review Part I here.

Like any great trilogy, it’s the middle part the makes or break the story, its the proverbial meat between the buns. This axiom holds true for this middle chapter in our 3 part series on using AWS CD/CI tools to automate the deployment of a Node web service. In this blog post, I will walk through the scripts and configuration files that need to be modified within the source code repository to make allow AWS CodeDeploy to automate its deployment.

Prepping the Node Source Code Repository for CodeDeploy

There are essentially 4 files that need to be created, 1 YAML file to control orchestrate the deployment (appspec.yml) and 3 shell scripts that will execute at various points of the deployment that will handle different phases of the app lifecycle.

Create the AppSpec.yml file

Open up the root of the repository in your source repo and create a new file named “appspec.yml”. Within it, paste the following:

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html/<project name>
hooks:
  AfterInstall:
    - location: scripts/after_install
      timeout: 300
      runas: root
  ApplicationStart:
    - location: scripts/start_server
      timeout: 300
      runas: root
  ApplicationStop:
    - location: scripts/stop_server
      timeout: 300
      runas: root

This YAML file tells CodeDeploy which scripts to run at various phases of the lifecycle of a CodeDeploy deployment. In our example, we are only going to be using 3 of the phases, and for each of them, we provide the path to the shell script that CodeDeploy is to run for that phase along with the user context under which to run it.

You will notice that we will be using the root user to install and run all of these scripts, this is why it is critical that, as indicated in Part I, that nvm and pm2 be installed under the root user.

Within the root of the project file, create a new folder called “scripts”. Within the scripts folder create new files named: after_install, start_server, stop_server.

Configure the after_install script

The AfterInstall phase runs after the files themselves have been dropped onto the EC2 machine by AWS CodeDeploy. In our YAML file, we have hooked up the AfterInstall phase to execute a script called after_install. Within this script we need to do the following:

  • Copy over the .env.sandbox file to be named .env (This example assumes we are setting up a sandbox environment, however if this is a production deploy its likely you will need to copy the .env.production file to .env)
  • Install all NPM dependencies.
  • Run any outstanding migrations using the Sequelize CLI tool.

The script should look something like this:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
cd /var/www/html/<project name>

if [ "$APPLICATION_NAME" == "<name of CodeDeploy application used for production deploys>" ]
then
   cp .env.production .env
else
   cp .env.sandbox .env
fi

npm install
sequelize db:migrate

Note we add the if condition to test the environment variable $APPLICATION_NAME so that this same script can be used eventually in both the sandbox and production pipelines. By default CodeDeploy populates the name of the environment variable APPLICATION_NAME with the name of the CodeDeploy application running. (Don’t worry, we haven’t yet setup the CodeDeploy application yet, but we can envisiage what the name of the production CodeDeploy project might be) For more information on standard environment variables created by CodeDeploy see this article.

Configure the start_server script

As the name implies, this script is going to be run after the after_install phase and its purpose is to launch the application. In this script we simply need to create and add a new application to pm2 and instruct it on how to launch the Node service.

For our sample project, the normal npm command to run the Node service in sandbox/development mode is “npm run dev”. This translates to the following command passed to pm2:

pm2 start npm –name “<project name>” — run dev

Check out this excellent StackOverflow thread with some more examples of how to pm2’ize your NPM start command.

Our completed start_script thus looks like:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

cd /var/www/html/<project name>
pm2 start npm --name "<project name>" -- run dev

Configure the stop_server script

This script is called during the ApplicationStop phase when CodeDeploy needs to stop the application. This generally happens as the first phase of the CodeDeploy pipeline, because on each deployment the first thing CodeDeploy needs to do is stop the running service before copying and starting it back up. Note, the ApplicationStop phase is notorious for throwing errors if you are running a deploy right after a failed deployment. In the event of a failed deployment, then its likely the subsequent deployment you run is going to fail on the ApplicationStop phase because CodeDeploy runs this script which attempts to stop a pm2 service that isn’t there.  To overcome this issue either modify the shell script to be smarter about testing for the presence of the pm2 process before stopping it, or follow the steps here:

Our stop_server script is very simple, it just instructs pm2 to stop the running Node service and delete it from pm2. It should look something like this:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
cd /var/www/html/<project name>
pm2 stop "<project name>"
pm2 delete "<project name>"

Once done, now commit all of these changes to your source repo. For this example, we are setting up a sandbox environment so we are going to commit our code to a branch called “sandbox” which is going to be used to trigger the CodePipeline.At this point, we now have a AWS environment and Ec2 instance configured for CodeDeploy and a source code repository containing the necessary script and configuration files to instruct CodeDeploy on how to deploy this application. Like Gandalf coming down the mountain at the head of a muster of raging Rohirrim, we are now ready for the final chapter in this trilogy where will actually configure the CodePipeline and CodeDeploy application to automate the deployment.

This Part 2 of a 3-part series that walks through the steps of using Amazon CodePipeline and CodeDeploy to automate the deployment of a Node based web service to an EC2 environment. After reading this post, please continue on to Part III.