Continuous Deployment with GitHub Actions and AWS App Runner

Continuous Deployment with GitHub Actions and AWS App Runner

DevOps Project

In this blog, I will simplify the process of creating a CI/CD build pipeline using GitHub actions, AWS ECR and AWS App Runner. I will provide a step-by-step guide to creating a workflow and automating workflow to deploy the app whenever we commit in the main(master) branch.

Before starting, let's look over all the prerequisites for this project:

  • A GitHub account.

  • An AWS IAM account with proper permissions.

  • Basic knowledge of AWS Console, Git, Docker, and YAML.

Now let's start with our CI/CD pipeline project


  • Start with a basic Vue.js repository

    To keep the demo simple, I will not create a fresh Vue.js app and go into detail about Vue.js. Rather than that, let's find a popular Vue.js repository and start from there.

    We will use the "Vue Paper Dashboard", which can be found here: https://github.com/creativetimofficial/vue-paper-dashboard

    Let's clone this repo and start locally first, after running the below commands, you should be able to see the dashboard UI as an image.

      $ git clone git@github.com:creativetimofficial/vue-paper-dashboard.git
      $ cd vue-paper-dashboard/
      $ npm install
      $ npm run dev
    

    The Vue.js Paper Dashboard is up & running at localhost:8080.

  • Creating a Dockerfile

As the repository doesn't come with native Docker support, we need to quickly add a Docker file for this repository, so that we can run it on AWS infrastructure as containerized application. Let's create a Dockerfile in the root directory of the project (same place as the package.json file), and populate the below contents.

# build stage
FROM node:fermium-alpine3.14 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

To understand more about how to dockerize a Vue.js app, please refer official document from Vue.js here: https://v2.vuejs.org/v2/cookbook/dockerize-vuejs-app.html?redirect=true.

Now let’s build the Docker image of our Vue.js app:

docker build -t vue-paper-dashboard .

Finally, let’s run our Vue.js app in a Docker container:

docker run -dt --name vue-paper-dashboard -p 8080:80 vue-paper-dashboard:latest

After running the application container successfully, you should be able to access the dashboard via the same address as the "npm run dev" command. Next, let's push the whole source code to our GitHub repository.

First remove existing .github folder
$ rm -r .github
$ git init
$ git remote add origin git@github.com:Harsh-Daga/Github-actions-pipeline-devops-project.git
$ git add .
$ git commit -m 'initial commit'
$ git push --set-upstream origin master
  • Provisioning AWS resources

    We will use GitHub Actions to continuously deploy our Vue.js application to AWS, so we need to create an IAM account and a user-managed role on AWS, this will be used in the next steps.

    Create an IAM account (will be used by GitHub Actions agents):

    Access the IAM console at https://us-east-1.console.aws.amazon.com/iamv2/home#/home, and create a new IAM account, remember to download the credential and save it somewhere, we will need to use it soon.

Now, let's create an app runner role, this will be used by AWS App Runner services to access ECR (where we manage our Docker image).

Create a new role named "app-runner-service-role".

To create the service role, click Create role button, select the Custom trusted policy option and enter the below JSON:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "build.apprunner.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

and attach the "AWSAppRunnerServicePolicyForECRAccess" policy to this role.

Please note down the ARN of the newly created role, we will need this in the next step.

  • Provisioning permission to IAM User

    Navigate to the github-vue-paper-dashboard IAM permission and attach the below inline policy, this will grant permission to GitHub Actions (via IAM role) to work with ECR and App Runner.

      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Sid": "VisualEditor0",
                  "Effect": "Allow",
                  "Action": "apprunner:*",
                  "Resource": "*"
              },
              {
                  "Sid": "VisualEditor1",
                  "Effect": "Allow",
                  "Action": [
                      "iam:PassRole",
                      "iam:CreateServiceLinkedRole"
                  ],
                  "Resource": "*"
              },
              {
                  "Sid": "VisualEditor2",
                  "Effect": "Allow",
                  "Action": "sts:AssumeRole",
                  "Resource": "{ENTER_YOUR_SERVICE_ROLE_ARN_HERE}"
              },
              {
                  "Sid": "VisualEditor3",
                  "Effect": "Allow",
                  "Action": [
                      "ecr:GetDownloadUrlForLayer",
                      "ecr:BatchGetImage",
                      "ecr:BatchCheckLayerAvailability",
                      "ecr:PutImage",
                      "ecr:InitiateLayerUpload",
                      "ecr:UploadLayerPart",
                      "ecr:CompleteLayerUpload",
                      "ecr:GetAuthorizationToken"
                  ],
                  "Resource": "*"
              }
          ]
      }
    

    We're almost done with AWS Configuration, one final step is to create a private repository on ECR to manage our Docker images.

  • Creating a Private AWS ECR repository

    In this step, we will create a private AWS ECR repository to manage our Docker images

  • Configuring GitHub Actions

    Go to Settings/Secrets and Variables/Actions on your GitHub repository and add all necessary secret variables:

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: From the .csv file when we create github-vue-paper-dashboard IAM user.
AWS_REGION: us-east-1
ROLE_ARN: Arn of app-runner-service-role which we had obtained above.

Next, back to Actions and select "set up a workflow yourself ->", on the new file editor (main.yml), populate below YAML file:

name: PRODUCTION - Deploy container to AWS App Runner # Name of the workflow
on:
  push:
    branches:
      - master
  workflow_dispatch: # Allow manual invocation of the workflow
env:
  ENVIRONMENT_NAME: production
  ECR_REPOSITORY_NAME: vue-paper-dashboard
jobs:  
  deploy:
    runs-on: ubuntu-latest

    steps:      
      - name: Checkout
        uses: actions/checkout@v2
        with:
          persist-credentials: false

      - name: Configure AWS credentials
        id: aws-credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}     

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1        

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ env.ECR_REPOSITORY_NAME }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"  

      - name: Deploy to App Runner
        id: deploy-apprunner
        uses: awslabs/amazon-app-runner-deploy@main        
        with:
          service: erp-app-${{ env.ENVIRONMENT_NAME }}
          image: ${{ steps.build-image.outputs.image }}          
          access-role-arn: ${{ secrets.ROLE_ARN }}         
          region: ${{ secrets.AWS_REGION }}
          cpu : 1
          memory : 2
          port: 80
          wait-for-service-stability: false

      - name: App Runner output
        run: echo "App runner output ${{ steps.deploy-apprunner.outputs.service-id }}"

Within the above GitHub Actions file, we go through several steps. First, it will check out the latest code from the master branch, then login to AWS ECR, build, tag, and deploy a new Docker image to our repository. And finally, it will deploy our web application to the AWS App Runner service.

  • Testing the Workflow

    Now, let's make some changes to our GitHub repository to see if the workflow gets automatically triggered or not and if it is successful or not.

GitHub Actions workflow automatically triggered on master changes.

GitHub Actions workflow runs successfully after a few minutes.

Let's navigate to the AWS App Runner service console, there will be a service named erp-app-production created (note that we programmatically named it via this command: service: erp-app-${{ env.ENVIRONMENT_NAME }} in the main.yml file, you can change the YAML file to match your naming preferences).

Now, if we visit the Default domain, we will be able to access our vue paper dashboard.

  • Conclusion

    Congrats, we have successfully implemented the CI/CD build pipeline for a simple Vue.js web application using Docker, GitHub Actions, AWS ECR, and AWS App Runner. Here are some notes after finishing the demo:

  • Remove unused AWS resources to save cost.

  • I used a Vue.js app as an input, but it is not mandatory, as long as you can dockerize your application, it doesn't matter which platform you're going to use.

  • I used AWS App Runner to host my containerized web app, in real-world scenarios, it can be ECS Fargate, EKS, EC2 ... This post is just a way to go.

  • I will remove all services after publishing the post so some URLs will not work.

Thanks for reading my Blog, see you in the next Blog.