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!");
});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"]Build and test container locally
docker build -t api:latest .
docker run -d -p 1000:1000 --name api api:latest
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}"
      ]
    }
  ]
}- {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
- Create a .github folder in the root application folder
- Create a worflows folder in the .github folder
- 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.