Deploy a Node app on AWS EC2 Linux
Fullstack Developer and Tech Author
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, for teams that prefer to take a barebones approach and build incrementally, AWS Elastic Compute Cloud (EC2) may be a better 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:
- A GitHub account
- A CircleCI account
- An AWS account
- NodeJS installed on your computer
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.
Scroll down to create a new key pair for authentication. If you already have an existing key pair, you can select it from the dropdown.
Warning: Do not proceed without creating or selecting a key pair as you will not be able to connect to your instance.
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.
Next, move down to the Network section. Update the firewall security groups section to create a new security group that allows SSH traffic from anywhere, allows HTTPS traffic from the internet, and allows HTTP traffic from the internet.
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
For the sake of brevity, this tutorial will not be covering how to set up a reverse proxy using Nginx. Instead, we will use the less secure option of opening 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, CMD+Click on the hyperlinked name of the security group mentioned under the Security groups sub-heading. This will open your security group in a new 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:
Click Add rule and add the following new inbound rule for port 8000.
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
Next, go to the directory where you have downloaded or have the AWS Key file.
You first need to set the certificate permissions to 600. Without doing this, you will not be able to connect to your instance. To modify the permissions, execute the following command:
chmod 600 <name-of-your-keypair>.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 regarding the SSH key. Type yes
to proceed.
Once you have connected to your instance, install Node and npm on it.
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 onto the intstance 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.
Configuring NodeJS server for production
To allow the server to run as an independent process in production, we’ll use pm2, 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 on the instance 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 and configure 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"
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.
vim 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
Finally, save this file.
This script 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 so that you append it to the existing authorized keys in your EC2 instance. You can edit the list of existing authorized keys using the following command:
vim .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. Clone the repository to your local machine and push it to your own GitHub repository.
Log in to your CircleCI account. If you signed up with your GitHub account, all your repositories will be available from the Organization Homepage after you click Set Up Project.
Click Set Up Project next to your node-aws-sample
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.
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 isubuntu
. - The
DNS
variable is the public IPV4 DNS of your EC2 instance.
Next, click SSH Keys on the sidebar. Scroll down and click Add SSH Key
For the hostname, use the public 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.
Go back to the dashboard. Click Rerun Workflow from Start inside the actions row on the failed pipeline run.
Your workflow will run successfully.
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, you can try out the new route in your browser to validate that your change deployed automatically.
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:
- 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.
- 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.