Angular app deployment to AWS EC2 instance with GitHub actions

Niranjan Gawali
Globant
Published in
7 min readSep 5, 2022

In the earlier blog, we successfully deployed the angular application on the EC2 instance and configured it with the NGINX web server. but it was more of a manual way now in this section we will automate the complete process using GitHub actions. We are going to use the GitHub actions to build and deploy the angular application to the EC2 instance. It's assumed that you have an EC2 instance of Ubuntu OS with NGINX web server configuration and access to the PEM key of the instance with you.

Creating the workflow file:

GitHub provides predefined workflows for many of the most popular platforms. But for this, we’ll be creating the YAML file manually with workflow commands.

At the root of the project create a folder, .github and Create a subfolder workflows in it. This is where our workflow YAML files will reside.

A project can have multiple workflows like build, release etc. For simplicity purposes, we will be keeping the workflows in a single file. We are creating a file named ci.yaml in the workflows folder.

GitHub folder, workflow file added

Once the file is created we will be adding CICD steps to the file.

name: CIon:
push:
branches: [main]
pull_request:
branches: [main]

With the name tag, we are providing the name to the workflow, after that, we are defining when the workflow should be triggered, and our workflow will be triggered when the code is pushed to the main branch or any pull request is merged with the main branch.

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./

after that, we are defining the build angular job, which runs on the ubuntu-latest. Each job runs in the fresh instance of the virtual environment. A job may contain one or more steps defined in it.

steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14.x"
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

With the steps, we are defining the steps that should be executed. The first step in the workflow is to take the pull of code from the repository using the checkout command. Below that we are setting up the node environment, we have also defined the node version that should be used. The step cache is used to improve the performance of our workflow. It is defined to re-use the node packages. This process helps to speed up the build process. To understand the details of the cache command please click here.

- name: NPM Install
run: npm install
- name: NPM Install Angular
run: npm install -g @angular/cli > /dev/null
- name: NPM build Angular Production
run: npm run build:prod

In the above-mentioned steps, we are installing the npm packages required for the project, Then we install the latest angular CLI and create the angular production build.

- name: Deploy to my EC2 instance
uses: easingthemes/ssh-deploy@v2.1.5
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY}}
SOURCE: "dist/angular-demo/"
REMOTE_HOST: "ec2-remote-host"
REMOTE_USER: "ubuntu"
TARGET: "/var/www/html/angular-demo/"

Once angular build creation is done the remaining part is deploying it to the EC2 instance. We are using the easingthemes/ssh-deploy@v2.1.5 to deploy this code. EC2 instance configuration is required with this step. The first thing that we need to provide is the SSH_PRIVATE_KEY, it is nothing but a PEM file of the EC2 instance, we need to add it in the secrets section of GitHub so it can be utilised in the configuration. Please refer to the following image for a better understanding.

GitHub repository secrets

For SOURCE can define the location from where we can get the code repository, for REMOTE_HOST we need to add the remote host path, we can get that from the AWS console, after going to the instance details we can look for Public IPv4 DNS, the same path should be added there. REMOTE_USER is the user of the EC2 instance, in our case it's ubuntu. TARGET is the defined path where angular build should be copied. It should be kept in mind the path that we define for the TARGET should be the same that we have configured for the NGINX server.

In the above configuration optionally, we can add additional steps to the format command to beautify the code and the lint command to maintain the quality of the code. Configuration for mentioned commands should be added in the angular application and commands should be configured in the package.json file.

To add the lint commands we just need to type ng lint in the project root terminal and afterwards select yes when asked about installation configuration. In the script section of the package.json file, the lint command will be added automatically. Then we can add the lint command to the ci.yaml file.

- name: Lint
run: npm run lint

For the formatter there are different formatter available, I have used the prettier formatter, To use the prettier, we need to install its dependency from the NPM, and use the following command to install the formatter.

npm install --save-dev --save-exact prettier

After prettier is installed then we can add it to the scripts section of the package.json file. In the command we have defined what types of files should be modified, we can modify them as per the requirement.

"format": "npx prettier 'src/**/*.{js,jsx,ts,tsx,html}' --write"

Once the format command is available then we can add the same in the ci.yaml file.

- name: Format
run: npm run format

We can also add a step to check if all tests are correctly executing or not.
In the package.json file, we need to add the test command. It will look as follows.

"test:ci": "ng test --no-watch --browsers ChromeHeadless

We also need to add the same command in the step of the ci.yaml file.

- name: Test
run: npm run test:ci

We can also add the step to check the coverage of the test cases for we can simply run the following command in the angular root folder and our coverage reports will be generated. As the chrome browser is not required so ChromeHeadless part is added.

ng test --no-watch --code-coverage --browsers ChromeHeadless

We will add the test coverage command in the script section of the package.json file.

"test:ci:cov": "ng test --no-watch --code-coverage --browsers ChromeHeadless"

for additional information on code coverage please refer to the following URL.

https://angular.io/guide/testing-code-coverage

The script section of the package.json file will look as follows

"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build:prod": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"test:ci": "ng test --no-watch --browsers ChromeHeadless"
"lint": "ng lint",
"format": "npx prettier 'src/**/*.{js,jsx,ts,tsx,html}' --write",
"test:ci:cov": "ng test --no-watch --code-coverage --browsers ChromeHeadless"
}

After adding the above-mentioned configuration in the YAML file will look like the below, again I liked to reiterate that lint, format, test and test coverage are optional its not mandatory to implement CICD but it's good practice.

name: CIon:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
# using Ubuntu
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14.x"
- uses: actions/cache@v3 # this allows for re-using node_modules caching, making builds a bit faster.
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: NPM Install
run: npm install
- name: NPM Install Angular
run: npm install -g @angular/cli > /dev/null
- name: Lint
run: npm run lint
- name: Format
run: npm run format
- name: NPM build Angular Production
run: npm run build:prod
- name: Test
run: npm run test:ci
- name: Test coverage
run: npm run test:ci:cov
- name: Deploy to my EC2 instance
uses: easingthemes/ssh-deploy@v2.1.5
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY}}
SOURCE: "dist/angular-demo/"
REMOTE_HOST: "ec2-remote-host"
REMOTE_USER: "ubuntu"
TARGET: "/var/www/html/angular-demo/"

Now we can perform any modification in the angular code, for testing purposes, we will modify the angular code and observe the changes. Now check out to the main branch using the following command.

git checkout main

While you’re on the main branch add the following line in the app.component.html file.

<h3>CICD implementation using GitHub actions is added</h3>

Now we can commit the code and push the code to the main branch using the following commands.

git add .
git commit -m "CICD implementation added"
git push origin main

We can check the status of the workflow by moving into the GitHub actions section of the git repository.

GitHub workflow status
GitHub Actions result

After Github actions are completed successfully, we simply go to the URL http://public-ip-address to observe the added modification.

We can see in the below image that the modification that we did is reflected correctly.

Angular page after update

Conclusion:

By following the procedure documented above we have successfully converted the static angular application deployment procedure to the CICD procedure with GitHub actions.

If you have any comments or queries about this, please feel free to mention them in the comments section below.

--

--