Amazon Web Services (AWS) provides a vast ecosystem of products that make DevOps an absolute dream. Products like AWS Elastic Beanstalk have ready-made services for autoscaling, deployment, and logging (to name a few). However, teams may prefer to take a barebones approach and build incrementally - in which case AWS Elastic Compute Cloud (EC2) would be the preferred option.

Regardless of the path chosen, CircleCI provides a robust platform that you can use to automate the deployment process and reduce bottlenecks in your software development lifecycle. In this article, I will show you how to deploy a Node application on an AWS EC2 Linux instance and automate the deployment of further updates by pushing to GitHub.

Prerequisites

In addition to a basic understanding of JavaScript, you will need the following to get the most from this tutorial:

Setting up an EC2 server

Launch new EC2 instance from your AWS dashboard. Provide a descriptive name for your instance and select Ubuntu from the Quick Start section. A sample selection is shown below.

New EC2 instance

Next, you need to create a new key pair. If you already have an existing key pair, you can select it from the dropdown.

Warning: Do not proceed without create a key pair as you will not be able to connect to your instance.

Create a new key pair

If you created a new key pair, a .pem or .ppk file will be generated and made available for you to download. Make sure you store it somewhere memorable.

For the next steps, you need to move down to the Network section. Update the firewall security groups as per the image shown below.

Update firewall security

From this image, you can infer that a new Security group is getting created which will allow all HTTP, HTTPs, and SSH connections to you EC2 instance.

Finally, click Launch Instance in order to complete the creation process.

Once this is completed, you will be redirected to view your list of Instances. Select your newly created instance and look out for the Public IPv4 DNS, which will be used in connecting to your instance

View instance

For the sake of brevity, this tutorial will not be covering how to set up a reverse proxy using Nginx. Instead, we will open port 8000 on the EC2 instance that the application is running on.

To do this, select the Security tab from the bottom menu bar. Next, Ctrl+Click on the name of the security group mentioned under the Security groups sub-heading. This will open your security group in a new tab.

Security Tab

In the newly opened tab, click the Edit Inbound rules button at the bottom right hand of the Inbound Rules section to see the inbound rules that control the incoming traffic to the instance:

Edit inbound rules

Click Add rule and add the following.

Add custom rule

Save the new rule to allow connections to port 8000 of your EC2 instance.

With that, you have successfully initiated the EC2 instance and allowed all traffic to the ports that you will need to run the NodeJS application.

Setting up the NodeJS application on EC2

You can SSH into your EC2 instance using the following command. Go to the directory where you have downloaded or have the AWS Key file.

First, you need to set the certificate permissions to 600 without which you will not be able to connect to your instance. To modify the permissions, execute the following command:

chmod 600 CircleCIServer.pem

Then, run the following command:

ssh -i <key-pair-name>.pem ubuntu@<instance-public-dns-name>

Substitute the <key-pair-name> with the name of the Key file and replace <instance-public-dns-name> with your public DNS name. This can be found in the Details tab under the Public IPv4 DNS section.

Note: For the first time, you will encounter a confirmation message for which you will need to type yes and proceed.

Once you have connected to your instance, you will need to install Node and npm.

sudo apt update

sudo apt install nodejs -y

sudo apt install npm -y

The above commands will update Ubuntu in the instance and install NodeJS and NPM packages in the instance.

Clone the sample project from GitHub using the following command:

git clone https://github.com/CIRCLECI-GWP/node-aws-sample

This command will create a new directory and download all the files hosted in the repository into the folder.

Finally, you can run the application using the following command:

node node-aws-sample/server.js

To view the application, open http://<instance-public-ipv4-address>:8000 in your browser.

view application on AWS

Configuring NodeJS server for production

To allow the server to run as an independent process in production, we’ll use pm2. It is a process manager for Node.js applications. It allows you to keep your applications running smoothly and to automatically restart them in the event of a crash or system restart.

Stop the application and install pm2 using the following commands:

mkdir ~/.npm-global

npm config set prefix '~/.npm-global'

export PATH=~/.npm-global/bin:$PATH

source ~/.profile

sudo npm install -g pm2

This will install the pm2 package in your system.

Next, restart the application using the following command, from outside the project folder.

pm2 start node-aws-sample/server.js --name "node_app"

You will get the following output:

[PM2] Starting /home/ubuntu/node-aws-sample/server.js in fork_mode (1 instance)
[PM2] Done.

With this command, the application will still be running even if the SSH connection is terminated.

Next, you need to create a bash script that will be called to handle the deploy process on the EC2 instance. Make sure the file is created outside the project directory.

nano deploy.sh

Add the following to deploy.sh.

#!/bin/bash

cd ~/node-aws-sample

git pull origin main

mkdir ~/.npm-global

npm config set prefix '~/.npm-global'

export PATH=~/.npm-global/bin:$PATH

source ~/.profile

pm2 restart node_app

This command pulls and navigates into the project directory, pulls the latest code, and then restarts the node application using pm2.

Make the script executable by running the following command.

chmod u+x deploy.sh

Next, in your local terminal create a new SSH key which will be used in your CircleCI pipeline to connect to your EC2 instance. Do it using the following command.

ssh-keygen -m PEM -t rsa -f .ssh/circleci

Since CircleCI cannot decrypt SSH keys, every new key must have an empty passphrase. Press Enter twice to create a key without a passphrase. Print the new public key with the following command.

cat ~/.ssh/circleci.pub

Copy the public key and append it to the existing authorized keys in your EC2 instance. You can find the list of existing authorized keys using the following command:

sudo nano .ssh/authorized_keys

With that in place, our EC2 instance is ready to accept SSH connections and also runs a bash script to pull the latest changes in our application.

Next, we’ll set up CircleCI to initiate the deployment process once a new push is made to the repository.

Adding the CircleCI configuration file

The downloaded demo project already contains the configuration file required to successfully set up the deployment pipeline for our project. Locate the config.yml file within the .circleci folder and ensure that its content is similar to the following:

version: 2.1

jobs:
  deploy:
    docker:
      - image: arvindr226/alpine-ssh
    steps:
      - run: ssh -oStrictHostKeyChecking=no -v $USER@$DNS "./deploy.sh"

workflows:
  deploy-to-ec2:
    jobs:
      - deploy

This config has one job named deploy. This job runs Alpine-ssh, a minimal Docker image that enables SSH. It runs a single command which is to SSH into the EC2 instance and runs the [deploy.sh](http://deploy.sh) script.

Connecting the application to CircleCI

The next step is to set up a repository on GitHub and link the project to CircleCI. Review Pushing a project to GitHub for instructions.

Log in to your CircleCI account. If you signed up with your GitHub account, all your repositories will be available on your project’s dashboard.

Click Set Up Project next to your node-aws-sample project.

Select project

You will be prompted to enter the name of the branch where your code is housed on GitHub. Click Set Up Project once you are done.

Select config

Your first workflow will start running.

The deploy job will fail because we are yet to add an SSH key to the project and set the required environment variables.

To fix that, start by adding two environment variables for USER and DNS. Click Project Settings.

Click the Environment Variables button on the left sidebar and create these variables:

  • The USER variable is the username to be provided in the ssh command. In this case it is ubuntu.
  • The DNS variable is the IPV4 DNS of your EC2 instance.

Add Env Vars

Next, click SSH Keys on the sidebar. Scroll down and click Add SSH Key

Add SSH Key

For the hostname, use the IPV4 DNS for your EC2 instance. To get the private key, run the following command in your local terminal.

cat ~/.ssh/circleci

Copy the output (starting from *** ----BEGIN RSA PRIVATE KEY---— *** and ending with ----END RSA PRIVATE KEY---— **) and paste in the Private Key section. Click Add SSH Key to save the new key.

Add hostname and SSH Key

Go back to the dashboard. Click Rerun Workflow from Start.

Your workflow will run successfully.

Successful Build

To make sure that everything works properly, let’s make an update to the application. For example, you can modify the name of a user within the users.js file.

Commit and push the changes to your GitHub repository and then head back to your CircleCI dashboard. Once the workflow completes running, try out the new route in your browser.

Updated app

Conclusion

In this article, we looked at how to implement a deployment pipeline using CircleCI for an Amazon EC2 instance. To keep things simple, we took an approach that focused solely on the deployment pipeline. There are two key things to note when replicating this approach in a production environment:

  1. Exposing ports on your server over the internet could have serious security implications. A more secure approach would be to use a reverse proxy server (such as Nginx) to handle incoming requests to the DNS and redirect as required.
  2. To SSH into our EC2 instance, we used the ubuntu user. This user has sudo privileges and should not be used for the SSH command in CircleCI. A safer approach would be to create a non-sudo user and use that in connecting with the instance.

The entire codebase for this tutorial is available on GitHub.


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem solving skills at building software for both web and mobile. A full stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech savvy, his hobbies include trying out new programming languages and frameworks.

Read more posts by Olususi Oluyemi