Web-App Deployment using PM2 in AWS

· 4 min read ·
AWSAutomationDevOps

Deploying web applications on AWS EC2 instances is a common task for cloud engineers. However, ensuring that the deployment is seamless, automated, and manageable requires careful consideration of the tools and configurations used. This guide walks through the process of deploying a web application using PM2 on an AWS EC2 instance, leveraging a Launch Template for consistency and efficiency, and implementing a semi CI/CD pipeline using S3.

Setting Up the EC2 Launch Template

To streamline the creation of EC2 instances, an AWS Launch Template is used. This template specifies the instance type, AMI, security group, and user data script that configures the instance upon launch.

Here’s the configuration for the Launch Template:

LaunchTemplate:
  Type: "AWS::EC2::LaunchTemplate"
  Properties:
    LaunchTemplateData:
      InstanceType: "c5a.large" # Compute optimized instance with 2 vCPU and 4 GiB memory
      ImageId: "ami-060e277c0d4cce553" # Ubuntu 22.04 LTS image
      KeyName: "<keypair-name>"
      IamInstanceProfile:
        Name: "<IAM-instance-profile-name>"
      SecurityGroupIds:
        - "<security-group-id>"
      UserData:
        Fn::Base64: |
          #!/bin/bash -l

          # Update package index
          apt update

          # Install necessary packages
          apt install -y nginx unzip curl

          # Install NVM (Node Version Manager)
          curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
          export NVM_DIR="$HOME/.nvm"
          [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
          [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
          nvm install --lts
          nvm use --lts

          # Install PM2 and Bun globally
          npm install -g pm2 bun
          pm2 startup systemd -u $USER --hp $HOME

          # Install AWS-CLI
          curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
          unzip awscliv2.zip
          ./aws/install

          # Variables
          S3_PATH="s3://<bucket-name>"
          LOCAL_PATH="/home/ubuntu/<bucket-name>"
          FILE_TO_CHECK="<src-code-file>.zip"
          APP_NAME="<app-name>"

          # Wait for the source code to be available in S3
          while true; do
            aws s3 ls $S3_PATH/$FILE_TO_CHECK
            && break
            || echo "Waiting for source code to be available in S3..."
            && sleep 300
          done

          # Sync source code from S3
          mkdir -p $LOCAL_PATH
          aws s3 sync $S3_PATH $LOCAL_PATH
          unzip $LOCAL_PATH/$FILE_TO_CHECK -d $LOCAL_PATH/
          rm $LOCAL_PATH/$FILE_TO_CHECK
          echo "Source code pulled from S3 to $LOCAL_PATH"

          # Move source code to web directory
          mkdir -p /var/www/$APP_NAME
          mv $LOCAL_PATH/* /var/www/$APP_NAME/
          cd /var/www/$APP_NAME

          # Install dependencies and build the application
          bun install --frozen-lockfile
          bun run build

          # Start the application with PM2
          pm2 start ecosystem.config.cjs
          pm2 save

          # Configure Nginx
          cat > /etc/nginx/sites-available/$APP_NAME <<- EOF
          server {
              listen 80;
              server_name <domain.com>;
              
              location / {
                  proxy_pass http://localhost:3000;
                  proxy_http_version 1.1;
                  proxy_set_header Upgrade \$http_upgrade;
                  proxy_set_header Connection 'upgrade';
                  proxy_set_header Host \$host;
                  proxy_cache_bypass \$http_upgrade;
              }
          }
          EOF
          ln -s /etc/nginx/sites-available/$APP_NAME /etc/nginx/sites-enabled/
          rm /etc/nginx/sites-enabled/default

          # Restarting server
          systemctl restart nginx
          pm2 restart all

Explanation and pre-requisite

Semi CI/CD Implementation using S3

For a semi CI/CD approach, the application code is uploaded to an S3 bucket. The EC2 instance continuously checks the S3 bucket for the presence of the deployment package and deploys it once available. But to be safe, upload the compressed source-code in the S3 bucket first before launching an EC2 using this template. This method is useful for environments where a full CI/CD pipeline isn’t feasible or required.

Disclaimer

The original article was authored by Aerrata and sourced from here.