DEV Community

Pakawat (Tle) Teerawattanasuk for AWS Community ASEAN

Posted on • Updated on

Tag-based monorepo docker image build on AWS

Monorepo to AWS ECR
My team adopted monorepo for our backend microservices and we need a simple solution to build docker images so we can use it for test or deploy anywhere. Because we use Amazon Elastic Container Repository (ECR) to store docker images, I decided to use git tag to trigger docker image build on AWS CodeBuild and push it to ECR.

The condition to trigger build is adding tag service-name/version-number and push it to GitHub or Bitbucket. AWS CodeBuild will receive webhook and start building new docker image using version-number provided in tag and push it to ECR. And lastly.... store build artifact in S3.

The REPO

Firstly, we need a monorepo on GitHub or Bitbucket, I prepared a sample repo with 2 projects at https://github.com/teer823/monorepo-aws-docker.

Each projects is just a simple express service with Dockerfile ready to build docker image and include buildspec.yml for AWS CodeBuild to use, however, I also added 2 buildspec file inside buildspec folder to demonstrate that it's possible to separate buildspec.yml file per project as needed.

The buildspec.yml file is an instruction for AWS CodeBuild and separated to phases. Here's what I do at each phase.

  • pre_build extract TAG_VERSION_NUMBER from tag and create version.txt file to store as artifact then login to ECR
  • build as the name say... build and tag docker image. Please note that I also add latest tag here as well, doing so prevent me from using ECR's tag immutability feature.
  • post_build push docker image to ECR
version: 0.2

phases:
  pre_build:
    commands:
      - echo Pre Build Phase...
      - export TAG_VERSION_NUMBER=$(echo $CODEBUILD_WEBHOOK_TRIGGER | sed 's/.*\///')
      - echo $TAG_VERSION_NUMBER
      - >
        PACKAGE_VERSION=$(cat $REPO_FOLDER/package.json | grep version | head -1 | awk -F: '{print $2}' | sed 's/[",[:space:]]//g')
      - echo $PACKAGE_VERSION > version.txt
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Build Phase...
      - cd $REPO_FOLDER
      - docker build -t $IMAGE_REPO_NAME:$TAG_VERSION_NUMBER -t $IMAGE_REPO_NAME:latest .
      - docker tag $IMAGE_REPO_NAME:$TAG_VERSION_NUMBER $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$TAG_VERSION_NUMBER
      - docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest 
  post_build:
    commands:
      - echo Post Build Phase...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$TAG_VERSION_NUMBER
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
artifacts:
  files:
    - '$REPO_FOLDER/**/*'
    - 'version.txt'
  name: '$IMAGE_REPO_NAME-$TAG_VERSION_NUMBER'
  discard-paths: no
Enter fullscreen mode Exit fullscreen mode

For more info about buildspec.yml please refer to HERE

AWS ECR

As I want to push docker image to ECR, I need to pre-create container repositories for each service beforehand. Just go and create a private repository and please don't enable Tag immutability or build will failed as latest tag cannot be overwritten. (Or just change the build spec and remove latest tag)

AWS ECR Create Repo

AWS S3

We want CodeBuild to store build artifact on AWS S3, so we need to prepare a bucket in advance. Let's create a bucket! (Leave the rest of configuration default or up to you.., i recommend turn on server-side encryption for BEST PRACTICE reason)

S3 create bucket

AWS CODEBUILD

Now we have Code Repo , Container Repo, Artifact bucket.. let's create AWS CodeBuild project.

  • Project configuration your choices.. Config Project
  • Source connect to your GitHub or Bitbucket and select repository in my GitHub account and select our monorepo repository. Please note that we need to rely on webhook so public repo won't do. Config Sourdce
  • Primary source webhook events Here's what important. Enable webhook by checking the box. And config build condition
    • Build type - Single build
    • Event type - PUSH and PULL_REQUEST_MERGED
    • Condition - Start build when receiving tag, in HEAD_REF add a regular expression ^refs/tags/service-name/([\w\.-]+)$, don't forget to replace service-name accordingly. Config Webhook

If you want to use other method to trigger build, here's the place to try and modify build condition according to your usecases.

  • Environment I personally prefer Ubuntu, so it's up to you how you want to config your build machine, however what's important for me here are the following

    • Privileged Enable the flag because we want to build docker images Priviledge
    • Service role Select your preferred name, I personally like to reuse the role in same project so I name it with project instead of service and next time I can just select it from existing service role. However, please remember the Role name because we will need it later. Service Role
    • Environment Variable If you noticed in buildspec.yml earlier, I used some environment variable. you will need to set it here

      • AWS_DEFAULT_REGION - your ECR region
      • AWS_ACCOUNT_ID - your ECR account Id
      • IMAGE_REPO_NAME - name of ECR remove created above
      • REPO_FOLDER - folder name for service we're building

      Environment Variable

  • Buildspec when using root's buildspec.yml just select Use a buildspec file will do.
    Default buildspec

    In case you want to try using individual buildspec file, add path to project-buildspec file in Buildspec name field
    Custom buildspec file

  • Artifacts Select Amazon S3 and bucket created above, use Zip packaging and use service-name as path.
    Build Artifact

Hit Create build project and we're done...then add another one for 2nd service.

PERMISSION

One last step before start tagging and building. our service role don't have permission to push docker image to ECR yet. so we need to add it in IAM. Go to your Role created above and Attach Managed Policy name AmazonEC2ContainerRegistryPowerUser to our role and Done!
IAM Role Allow ECR

TAGGING TIME!

Now it's time to add tag to monorepo and push it to origin

❯ git tag ingest-service/0.1.0
❯ git push origin ingest-service/0.1.0
Enter fullscreen mode Exit fullscreen mode

Go back to CodeBuild project and see it work!
Build in progress

When building complete, find your new docker image in ECR!
New Docker Image

Conclusion

That's all about it. I hope this is useful for anyone looking to make a simple build pipeline like my team :)

Discussion (1)

Collapse
chatchaikomrangded profile image
Chatchai Komrangded (Bas)

Great article :)