Deploying a Node.js application to AWS Elastic Beanstalk using Github actions
Tags: aws, actionshackathon, elasticbeanstalk, github
Published: 2022-02-12 21:07:31

In this note, I'm jotting down how to deploy a Node.js application to AWS Elastic beanstalk using Github actions. Part of my assumption is that you have an existing project on Github, and you also have a running EBS environment.
Before we take a deep dive, let's taste the depth using one foot. GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository or deploy merged pull requests to production. An alternative to Github actions can be Circle CI, Travis CI, or Jenkins.

Automating your deployment comes with so many advantages. Part of it has been faster time to market, reduced risks, and better code quality. Now that we're tasted the waters let's take a deep dive.

We first need to create a .github folder at the root of our project directory. after that, let's navigate inside the .github folder and create another folder called workflows. Inside the workflow folder, create a config file with the extension .yml. The file's name can be anything, but in my case, I made it prod.yml.

We can break down the process into several jobs and steps in the config file. We're trying to keep it simple to have a single appointment with several steps.
In the first part of the config file, we'll define the name of the action and the branch that triggers our action.

```
name: production

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
```
As seen above, the action is name production; the action is triggered with a push to the main branch, or a pull request is merged to the main branch. You can add multiple branches as well.

Next, we define our job and the environment(OS and node version) on which it will run. We do this using the following code;

```
jobs:
  ci:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [14]
    env:
      NODE_ENV: "development"
```
After that, the next step involves the actual steps involved in building, uploading, and deploying our application.

```
    steps:
      - name: Checkout 🛎
        uses: actions/checkout@master

      - name: Setup node env 🏗
        uses: actions/setup-node@v2.1.2
        with:
          node-version: ${{ matrix.node }}

      - name: Cache node_modules 📦
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Installing NPM
        run: npm install

      - name: Linting check
        run: npm run lint

      - name: Building application
        run: npm run build

      - name: Generate deployment package
        run: zip -r deploy.zip * .[^.]* -x "**node_modules**"

      - name: Get timestamp
        uses: gerred/actions/current-time@master
        id: current-time

      - name: Run string replace
        uses: frabert/replace-string-action@master
        id: format-time
        with:
          pattern: '[:\.]+'
          string: "${{ steps.current-time.outputs.time }}"
          replace-with: '-'
          flags: 'g'

      - name: Beanstalk Deploy for app
        uses: einaregilsson/beanstalk-deploy@v16
        with:
          aws_access_key: ${{secrets.ACCESS_KEY_ID}}
          aws_secret_key: ${{secrets.SECRET_ACCESS_KEY}}
          application_name: <application name>
          environment_name: <Replace with your environment name>
          region: us-east-2
          version_label: "version-label-${{ steps.format-time.outputs.replaced }}"
          deployment_package: deploy.zip

      - name: Deployed!
        run: echo App deployed to ELB
```
From the above code, some steps to note are:

**Step 7: Generate deployment package.**
During my trials, I discovered that each project has some individual files required to run. By default, running the zip -r * command zips the files but excludes the hidden files (files and folders starting with a dot .), so feel free to modify the zip command to include the required files.

**Step 10: Beanstalk Deploy for app**
We're using einaregilsson/beanstalk-deploy@v16 action for beanstalk deployment. At the point of writing, we're using version 16 of the package. Feel free to check for updates and modify the parameters as required. The full code is shown below.

```
name: production

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  ci:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [14]
    env:
      NODE_ENV: "development"


    steps:
      - name: Checkout 🛎
        uses: actions/checkout@master

      - name: Setup node env 🏗
        uses: actions/setup-node@v2.1.2
        with:
          node-version: ${{ matrix.node }}

      - name: Cache node_modules 📦
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Installing NPM
        run: npm install

      - name: Linting check
        run: npm run lint

      - name: Building application
        run: npm run build

      - name: Generate deployment package
        run: zip -r deploy.zip * .[^.]* -x "**node_modules**"

      - name: Get timestamp
        uses: gerred/actions/current-time@master
        id: current-time

      - name: Run string replace
        uses: frabert/replace-string-action@master
        id: format-time
        with:
          pattern: '[:\.]+'
          string: "${{ steps.current-time.outputs.time }}"
          replace-with: '-'
          flags: 'g'

      - name: Beanstalk Deploy for app
        uses: einaregilsson/beanstalk-deploy@v16
        with:
          aws_access_key: ${{secrets.ACCESS_KEY_ID}}
          aws_secret_key: ${{secrets.SECRET_ACCESS_KEY}}
          application_name: application_name
          environment_name: environment_name
          region: us-east-2
          version_label: "e-learn-${{ steps.format-time.outputs.replaced }}"
          deployment_package: deploy.zip

      - name: Deployed!
        run: echo App deployed to ELB
```

Also, the AWS secrets are stored using Github secrets. This is found in the settings tab of the repository. Click on settings, then scroll down till you find secrets in the sidebar. In the secrets dropdown, click on actions. Add the access key and the secret key to the secrets.

![Github secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1a4ympzma5eef8x9zny.png)

Change the application_name, environment_name, and the region to your AWS setup.

After that, you can save the file and push it to Github. In the actions tab of the repository, you'll see your CI running.

**NOTE**. AWS will look for a start script that will run automatically to start your application. You can specify this in a Procfile in the root of your project directory and add the following command web: npm run start.

You might need to tweak one or two in the config. Feel free to share your experience in the comment section.