Deploying a Node app on Elastic Beanstalk with Gitlab CI
By: Rodolfo Costa
Tags: Elastic Beanstalk, Gitlab, AWS, Nodejs, Continuous Integration

Elastic Beanstalk

Creating an environment on Elastic Beanstalk can be done programatically by using other AWS services such as CloudFormation or manually in the AWS console. Here we will create one directly in the AWS console using an already created VPC and subnet configuration.

First, we hit the "Create a new environment" button, choose "Web server environment" and, then, set the "Application name".

Application name must be unique among account’s EB application names

Next, we define "Environment name" as well as a "Domain" for it.

Setting environment name and domain. Click “Check availability” to check if desired domain can be used

After that, we define what platform will be used. We chose "Managed platform" since we want to use Docker with an Amazon Linux 2 in an attempt to make the instance’s infrastructure as lightweight and up-to-date as possible.

Setting Docker with Amazon Linux 2 platform

The upcoming step is setting up the application code to be deployed to the environment. Three options are given: "Sample application", "Version label" or "Upload your code". Knowing that Elastic Beanstalk uses S3 to store future application versions, we chose to create a S3 Bucket and upload an initial version to it and share its "Public S3 URL". One thing to note: this "application code" is a "Source Bundle", meaning it must be a ZIP or WAR file which, in our case, needs to have the source code and a Dockerfile to get Docker running.

Files to build Source Bundle

The content of the files are:

app.js

const express = require('express')

const app = express()

const APP_PORT = process.env.APP_PORT || 3000; app.get('/', (req, res) => {

res.send('Hello World!')

}) app.listen(APP_PORT, () => {

console.log(`Running app at port:${APP_PORT}`)

})

Dockerfile

FROM node:10

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

ENV APP_PORT 8080

EXPOSE 8080

CMD [ "node", "app.js" ]

dockerignore

node_modules

npm-debug.log

package.json

{

"name": "my-awesome-app",

"version": "1.0.0",

"description": "Hello World in Node with Express",

"main": "app.js",

"scripts": {

"start": "node app.js"

},

"author": "",

"license": "ISC",

"dependencies": {

"express": "^4.17.1"

}

}

Select the ZIP file and copy the “Object URL” to be used as “Public S3 URL”

Defining “Public S3 URL”

Considering we will be using custom VPC and subnet configuration, we must define which one we want to use by going to "Configure more options" before finishing the creation process.

For this, go to "Network" card and edit it.

Edit Network card

Select desired VPC and subnet(s).

Selecting VPC

Selecting subnet

Then, we need to set a security group to our EC2 instance. Go edit "Instances" card and choose an EC2 security group.

Selecting security group

Now hit "Create environment"...

... and that's it! Finally!

After waiting AWS building process you should see your environment like this:

Environment preview

With a created Elastic Beanstalk environment, the next step is to update it using Gitlab CI.

Gitlab CI

To run a Gitlab CI pipeline to deploy our app to Elastic Beanstalk, first we need to have a Runner installed and, afterwards, place the following ".gitlab-ci.yml" in our repository root directory:

The pipeline created for this deployment consists of two steps/jobs. First, we create an AWS application version with our files, that is, we create a Source Bundle, upload it to our S3 Bucket and, afterwards, create an application version. Then, we deploy our created application version to our Elastic Beanstalk environment.

A side note: To create the zip file in the CI context, we use a simple python script that help us generate it, as follows:

zip.py (Python 3.x)

import sys, shutil

shutil.make_archive(sys.argv[1], 'zip', '.')

By using this CI file, you can define the "Variables" values directly in the file or as Gitlab Environment Variables under "Settings" > "CI/CD" > "Variables" menu. Detailing the variables values:

APP_NAME: Gets the name from repository. It must be the same name defined for the Elastic Beanstalk application that holds our created environment.

APP_VERSION: Defines current version of our application. It is used to help managing application versions in S3 and on the environment preview screen.

S3_BUCKET: Name of the bucket that will store the application version.

AWS_ID: ID of the AWS account hosting the Elastic Beanstalk environment

AWS_ACCESS_KEY_ID: Represents the "login" or "username" of your AWS account.

AWS_SECRET_ACCESS_KEY: Represents the password of your AWS account.

AWS_REGION: It is the region where the Elastic Beanstalk application was created. In our case, us-east-1

AWS_PLATFORM: The platform chosen to run the application.

By setting these values and pushing the file to the repository, the pipeline will be executed and, when both jobs finish successfully, you should see this:

Successful pipeline execution

Now go to your Elastic Beanstalk environment. It should be updated with your application version generated by the pipeline.

Updated app

In the image above, under "Running version" you can see that our app was updated. The pattern chosen to name the "Source Bundle" was: {APP_NAME}-{APP_VERSION}-{CI_PIPELINE_NUMBER}. This way, it becomes easier to trace the version and its deployment by checking the pipeline number and the respective commit that triggered it.

The exciting part: to see the app working, copy the URL defined for the Elastic Beanstalk environment and paste it to your browser or an app to call your endpoint.

As shown above, you should see a super duper "Hello World" response (well, it is a Node app anyways 😜)

Conclusion

Thanks to Elastic Beanstalk and Gitlab, we can have a simple and automated structure to host and maintain our application (in this case, a Node app, but it could be in other languages). This really helps in making the deployment process less painful and scary and it gives developers more time to focus on the application itself.

As a result, we get our app running in a secure, controlled and scalable environment that can be updated according to the project characteristics and, at the same time, it represents a great cost-effective solution.

References: