Continuous Integration using GitHub Actions

Continuous integration is a DevOps software development practice where developers regularly merge their code changes into a central repository, after which automated builds and tests are run.

I will build a docker image using GitHub actions and publish it to an AWS ECR repository. A few prerequisites that will be helpful moving forward if you plan to follow along:

  • Basic NodeJS & Javascript skills
  • Docker and Docker Engine installed and running
  • AWS account with an ECR repo setup already

Create an API

const express = require("express");
const app = express();

app.get("/", (req, res) => res.send("Thabo Lebelo 🚀"));

app.get("/health", (req, res) => {
  res.status(200);
  res.send("healthy");
});

app.listen(1000, () => {
  console.log("App listening on port 1000!");
});
Basic NodeJS app with two endpoints

Docker file for our app

FROM alpine:latest
RUN apk add --no-cache nodejs npm

WORKDIR /app
COPY package.json /app
RUN npm install

COPY . /app

EXPOSE 1000
CMD ["npm", "start"]
We will expose port number same as our app

Build and test container locally

docker build -t api:latest .

docker run -d -p 1000:1000 --name api api:latest
Run in the same directory as our docker file

Create AWS IAM User, Policy, and Group

I need to give GitHub actions permissions to push docker image to AWS ECR. To do this we need an IAM policy, name it  AllowPushPullPolicy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GetAuthorizationToken",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetDownloadUrlForLayer",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ],
      "Resource": [
        "arn:aws:ecr:{REGION}:{ACCOUNT-ID}:repository/{REPO-NAME}"
      ]
    }
  ]
}
Attach this policy to the IAM user/group
  • {REGION} : replace with your AWS region
  • {ACCOUNT-ID} : replace with your  AWS account number/id
  • {REPO-NAME} : replace with AWS ECR repository name

Note: You need to add the IAM user Access key ID and Secret access key as secrets to your GitHub repo. Name the secrets AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY respectively.

Create GitHub Actions Workflow

  1. Create a .github folder in the root application folder
  2. Create a worflows folder in the .github folder
  3. Create a main.yml file in the workflows folder
name: Build and Push NodeJS Image to Docker Registry
on:
  push:
    branches: [main]
jobs:
  build-and-push:
    name: Publish to AWS ECR
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Configure 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: us-east-1
      - name: Login to AWS ECR
        id: login-ecr
        uses: aws-actions/amzon-ecr-login@v1
      - name: Build and Push docker image to AWS ECR
        id: build-Image
        env: 
          ECR_REGISTRY: $ {{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: create-a-ci-pipeline
          IMAGE_TAG: latest
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
  • {AWS_ACCESS_KEY_ID} : references your GitHub repo secret
  • {AWS_SECRET_ACCESS_KEY} : references your GitHub repo secret
  • ECR_REPOSITORY should have the value of your chosen ECR repo name (My ECR repo is named create-a-ci-pipeline)

See it in action

To test the pipeline, push/merge changes to the main branch, then head to GitHub to see the workflow run all defined steps:

To verify that the image was indeed successfully published, head over to AWS ECR to see the the changes.

Summary

There you have it, a continuous integration (CI) pipeline between GitHub and AWS. I hope to build on this example in a few other posts. In the meantime checkout the repo create-a-ci-pipeline.