Deploying a Node.js App with AWS CloudFormation: A Developer‘s Guide

As an application developer, you know that deploying your code involves a lot more than just copying files to a server. You need to provision infrastructure, install dependencies, configure networking and security, and automate the deployment process. That‘s where infrastructure-as-code tools like AWS CloudFormation come in.

In this guide, I‘ll introduce you to the core concepts of CloudFormation and walk you through a hands-on example of deploying a Node.js application with a CloudFormation template. By the end, you‘ll understand how to define your application‘s infrastructure requirements in code and automate the provisioning process for speed and reliability. Let‘s get started!

What is AWS CloudFormation?

CloudFormation is an AWS service that allows you to model and provision your cloud infrastructure resources using declarative templates. Instead of manually creating servers, databases, load balancers, etc. through the AWS Management Console, CLI, or SDKs, you define them in a JSON or YAML template that CloudFormation uses to deploy your application stack.

Some key benefits of using CloudFormation include:

  • Infrastructure-as-code – Your infrastructure is defined in the template, tracked in version control alongside your application code. This makes it easy to review changes, roll back to previous versions, and collaborate with your team.

  • Automation – CloudFormation handles the complexities of provisioning dependencies, waiting for resources to be ready, and rolling out updates. You can deploy your full application stack with a single command.

  • Consistency – Using a template ensures your development, staging, and production environments are consistent, reducing "works on my machine" issues. You can be sure that what you test will match what you deploy.

  • Reusability – CloudFormation templates are parameterized, so you can deploy the same architecture for multiple projects, regions, or accounts by simply changing the parameter values. Templates can also be shared within your organization or publicly on the web.

Now that we understand what CloudFormation is and why it‘s useful for deploying applications, let‘s look at how to structure a template for a Node.js app.

Anatomy of a CloudFormation Template

A CloudFormation template is a JSON or YAML formatted text file that defines the AWS resources that make up your application. The template contains several major sections:

  • Format Version – Specifies the template format version. Use "2010-09-09" as the format version unless you have a specific reason to use an older version.

  • Description – A text description of the template, used to communicate its purpose to other developers.

  • Metadata – Additional data about the template itself, such as the AWS SAM version it is compatible with, diagrams of the architecture, etc.

  • Parameters – Values passed in to the template at runtime to customize the deployment, such as environment name, instance type, etc. Parameters make templates reusable for different scenarios.

  • Mappings – A lookup table of key-value pairs that can be referenced elsewhere in the template, such as mapping AWS regions to AMI IDs.

  • Conditions – Logic expressions that determine whether certain resources are created or configured based on input parameter values.

  • Resources – The AWS resources that comprise your application stack and their configuration properties. This is the core of the template where you define servers, databases, networking, IAM, etc.

  • Outputs – Values returned by the deployment that you can import into other stacks, view through the AWS Console, or query using the AWS CLI. Outputs are useful for passing resource names, URLs, etc. between layers of your architecture.

Let‘s focus on the Resources section to define the components needed for a basic Node.js application.

Defining Resources for a Node.js App

To deploy a Node.js app, we need a few essential resources:

  • An EC2 instance to run the application code
  • A security group to allow inbound traffic on the necessary ports
  • Commands to install Node.js, pull the latest code, and start the app

Here‘s an example snippet of a CloudFormation template that defines these resources:

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: ami-0c94855ba95c71c99  # Amazon Linux 2 AMI 
      KeyName: my-ssh-key
      SecurityGroupIds: 
        - !Ref MySecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          yum install -y git
          curl -sL https://rpm.nodesource.com/setup_14.x | bash -
          yum install -y nodejs
          git clone https://github.com/myuser/myapp.git /opt/myapp
          cd /opt/myapp
          npm install
          npm start

  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup    
    Properties:
      GroupDescription: Enable HTTP and SSH access
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp 
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation

Let‘s break this down:

The MyInstance resource defines an EC2 instance with the following configuration:

  • InstanceType specifies the hardware specs of the VM. The t2.micro size is eligible for the free tier.
  • ImageId is the ID of the base OS image. The latest Amazon Linux 2 AMI is used here.
  • KeyName is an SSH key for logging into the instance. You need to create this separately and pass in the name as a parameter.
  • SecurityGroupIds attaches the security group created below to govern the instance‘s network access.
  • UserData contains shell commands that run when the instance first boots. This script updates the OS, installs the HTTP Apache web server, Node.js, clones the application source code from GitHub, and starts the Node app.

The MySecurityGroup resource defines the inbound ports to open for the EC2 instance:

  • Port 80 is opened to all IPs to allow access to the web application over HTTP
  • Port 22 is opened to a restricted IP range (SSHLocation parameter) to enable SSH access for administration

The !Ref intrinsic function is used to reference the value of the security group within the EC2 instance configuration. The !Sub function is used to insert the SSHLocation parameter into the security group ingress rule.

Deploying the CloudFormation Stack

With the template defined, we‘re ready to launch the CloudFormation stack that will provision the actual resources in AWS. You can do this through the AWS Management Console, CLI, or SDKs:

Using the AWS Console

  1. Open the CloudFormation console and click "Create stack"
  2. Choose "Template is ready" and "Upload a template file", then click "Choose file" and select your template
  3. Click Next and give the stack a name like "node-app-stack"
  4. Fill in any Parameters defined in the template, like the SSH key name and allowed IP range
  5. Click Next, add any tags if desired, then Next again
  6. Review the stack configuration and click "Create stack"

CloudFormation will now begin provisioning the resources defined in the template. You can monitor the progress in the stack‘s "Events" tab and view the "Resources" tab to see each resource being created.

Once the status reaches "CREATE_COMPLETE", your Node.js app is deployed and accessible! Find the public DNS name of the EC2 instance in the stack‘s Outputs tab and open it in your web browser.

Updating the Application

One of the big benefits CloudFormation provides is making updates to your application stack fast and reliable. To modify your infrastructure or application code, simply change the template and update the existing stack with the new version.

For example, to deploy a new version of your Node.js code:

  1. Push your code changes to the git repository referenced in the UserData script
  2. Update the CloudFormation stack using the same template file
  3. Choose "Update current template" and click "Next" with the same parameter values

CloudFormation will update only the modified EC2 instance, rather than recreating the entire infrastructure from scratch. The UserData script will run again, pulling down the latest code commits and restarting your application.

You can also use CloudFormation change sets to preview the impact of a template change before applying it. This is a safe way to catch any resource additions, deletions, or modifications that could cause data loss, downtime, or unexpected bills.

Best Practices for CloudFormation

To get the most value out of CloudFormation, keep these best practices in mind:

  • Use separate templates for shared/reusable resources (like VPCs) and application-specific resources. This allows updates to happen independently.

  • Leverage CloudFormation parameters to make templates generic and reusable across regions, accounts, and teams.

  • Validate template changes on a dev/test stack before updating production. Make use of change sets to catch unintended effects.

  • Use source control for your templates as you do with application code. Consider tools like CloudFormation linter and CFN NAG to catch common security/IAM misconfigurations in templates.

  • Seek opportunities to automate higher-level processes with CloudFormation StackSets. For example, auto-enrolling new AWS accounts in your organization into standard CloudFormation stacks.

Conclusion and Further Resources

Whew – we covered a lot! I hope this guide gave you a solid foundation for defining and deploying your Node.js application with CloudFormation. Once you have your application code and infrastructure co-located in templates, a whole world of automation opens up to you.

Some ideas to explore next:

  • Add a CloudFront distribution in front of your EC2 instance for global caching and SSL termination
  • Put the application behind an Application Load Balancer for horizontal scaling, using an auto-scaling group to adjust the number of EC2 instances
  • Swap the Node.js EC2 instance for AWS Lambda nodejs function to go fully serverless
  • Use the AWS CDK to generate CloudFormation templates from familiar programming languages

Here are some of my favorite resources for diving deeper into CloudFormation:

To stay up-to-date on the latest CloudFormation capabilities, I recommend following the AWS Developer blog, What‘s New with AWS, and AWS This Week on YouTube. You can also find me on Twitter @ArunachalamB where I post about AWS and serverless.

Do you have experience deploying Node.js apps with CloudFormation? What best practices and tips can you share? Let me know in the comments!

Similar Posts