Own Your Dependencies: Customizing GitHub Actions with Container Images
Published on July 17, 2024 | Written by Andreas
Own your dependencies. Do you have full control over all the tools used to build and ship your software? A CI/CD pipeline relies on many tools. In my projects, that’s Node.js, npm, eslint, OpenTofu, tflint, and AWS CLI, for example. It’s crucial, to take full control over those dependencies. Don’t rely on pre-installed software on a GitHub-hosted or self-hosted runner, there is a high risk that changes to the pre-installed tools will break your jobs. Also, do not use GitHub Actions to install dependencies, there is a high risk for malicious code being injected into your deployment pipeline.
Build a container image including all the tools needed to build and ship a project. Then, configure your GitHub workflow to run the jobs in a container from the image.
In the following you will learn how to use GitHub Actions to build a customized container image with all the tools required for your CI/CD jobs pre-installed. After that, you will learn how to configure your GitHub workflow to use the customized image for executing jobs.
The example assumes, that you installed HyperEnv before, as GitHub-hosted runners do not support fetching container images from ECR.
Building a container image for CI/CD
First, create a Dockerfile
to install all tools needed to build and ship your project.
For example, the following Dockerfile
uses Amazon Linux 2023 as the base image and installs Node.js, OpenTofu, and the AWS CLI.
FROM amazonlinux:2023
RUN curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
RUN yum install nodejs -y
RUN yum update -y
RUN yum install -y awscli
RUN if [ "$(uname -m)" == "x86_64" ]; then CPU_ARCH="amd64"; else CPU_ARCH="arm64"; fi && \
cd /tmp && \
curl -L -o tofu.zip "https://github.com/opentofu/opentofu/releases/download/v1.6.2/tofu_1.6.2_linux_${CPU_ARCH}.zip" && unzip tofu.zip && chmod +x tofu && mv tofu /usr/local/bin/ && rm -f *
Next, create a private ECR repository named cicd-tools
.
Then, create a GitHub repository and add the Dockerfile
as well as the following GitHub Workflow .github/workflows/build-cicd-image.yml
. Don’t forget to replace <AWS_ACCOUNT_ID>
and <AWS_REGION>
with your AWS account ID and region.
- Checkout the repository, to fetch the
Dockerfile
. - Assume an IAM role with write-access to the ECR repository.
- Login to ECR.
- Enable Docker multi-arch.
- Build a multi-arch image by using the
Dockerfile
and push it to ECR.
Why building a multi-arch image? Doing so allows you to run your jobs on runners executed on AMD64 or ARM64 machines.
The following example uses an OpenID Connect to authenticate with AWS. Check out our CloudFormation template to enable OpenID Connect for GitHub in your AWS account as well.
---
name: build-cicd-image
on:
push:
tags: ['*']
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ['hyperenv']
steps:
- uses: actions/checkout@v3
- name: 'Assuming IAM role'
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::<AWS_ACCOUNT_ID>:role/github-oidc
role-session-name: deploy
aws-region: <AWS_REGION>
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: 'Enable Multi Arch'
run: docker buildx create --use
- name: 'Build container image'
run: docker buildx build --platform linux/arm64,linux/amd64 --push -t <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/cicd-tools:${{github.ref_name}} .
working-directory: build-cicd-image
The GitHub workflow will build and push a new version of the CI/CD container image to ECR whenever you push a new tag to the GitHub repository.
Executing CI/CD jobs by using customized container image
Now modify your exsisting GitHub workflow. For each job, define the container
(see Running jobs in a container) that should be used to execute each step.
The following listing demonstrates how to execute tofu apply
by using a customized container image. Don’t forget to replace <AWS_ACCOUNT_ID>
and <AWS_REGION>
with your AWS account ID and region.
---
name: build-deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ['hyperenv']
container:
image: <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/cicd-tools:main
concurrency:
group: 'build-deploy'
cancel-in-progress: false
steps:
- uses: actions/checkout@v3
- name: 'Assuming IAM role'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<AWS_ACCOUNT_ID>:role/github-oidc
role-session-name: build-deploy
aws-region: us-east-1
- name: 'Deploy'
run: 'tofu apply'
working-directory: build-cicd-image
Do you want to dive deeper? Check out the full code example on GitHub [widdix/hyperenv-examples)(https://github.com/widdix/hyperenv-examples).
Summary
In conclusion, owning your dependencies through custom container images in GitHub Actions offers a powerful way to enhance the reliability and security of your CI/CD pipeline. By building and using your own containers with pre-installed tools, you gain full control over your build and deployment environment, reducing risks associated with external dependencies. This approach not only streamlines your workflow but also ensures consistency across different runners and architectures. Remember, in the world of CI/CD, independence and control over your tools are key to maintaining a robust and efficient development process.