Skip to content

feat(ci): Use new docker build #290

feat(ci): Use new docker build

feat(ci): Use new docker build #290

Workflow file for this run

name: Docker Build
# This workflow builds Docker images for projects in the repository.
# It can be triggered by:
# - Labeling a pull request with "ci debug"
# - Being called from another workflow via `workflow_call`
# - Manual dispatch using `workflow_dispatch`
# The workflow uses matrix builds to parallelize the process and leverages caching
# to speed up dependency and final image builds.
on:
pull_request:
types:
- labeled
- synchronize
workflow_call:
inputs:
projects:
description: 'Comma-separated list of project names to build.'
type: string
required: true
build-args:
description: 'Additional build-args (newline separated)'
type: string
required: false
version:
description: 'The version to tag the image with (previously DOCKER_TAG)'
type: string
required: false
workflow_dispatch:
inputs:
projects:
description: 'Comma-separated list of project names to build.'
type: string
required: true
build-args:
description: 'Additional build-args (newline separated)'
type: string
required: false
version:
description: 'The version to tag the image with (previously DOCKER_TAG)'
type: string
required: false
concurrency:
group: container-build-${{ github.head_ref || github.run_id }}-${{ github.event_name }}
cancel-in-progress: true
defaults:
run:
shell: bash -euo pipefail {0}
env:
# Regsitry to for build images and cache
IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'localhost' }}
AWS_ECR_REPO_BASE: ${{ vars.AWS_ECR_REPO_BASE || 'docker.io' }}
NODE_IMAGE_VERSION: 20
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
NX_TASKS_RUNNER: ci
# Registry without trailing `/docker`
ECR_PULLTHROUGH_CACHE: ${{ vars.ECR_PULLTHROUGH_CACHE || 'public.ecr.aws' }}
jobs:
prepare:
name: Prepare
if: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci debug') }}
runs-on: ${{ inputs.runners || 'arc-runners' }}
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
node-image-version: ${{ steps.args-prep.outputs.node-image-version }}
playwright-image-tag: ${{ steps.args-prep.outputs.playwright-image-tag }}
container-registry: ${{ steps.args-prep.outputs.container-registry }}
steps:
- uses: actions/checkout@v4
- name: Build arg prep
id: args-prep
run: |
- name: Create matrix from input
id: set-matrix
env:
localrun: ${{ !!github.event.localrun }}
projects: ${{ inputs.projects }}
is_debug: ${{ contains(github.event.pull_request.labels.*.name, 'ci debug') }}
run: |
if [[ "$is_debug" == true ]] && [[ -z "$projects" ]]; then
echo "Using small subset for testing docker build (on ${{ github.event.pull_request.base.ref }})"
# A representative sample of various docker build targets
export projects="web,air-discount-scheme-backend,license-api"
fi
# Create a list of objects of the form:
# [
# {
# "name": "services-my-service",
# "docker": "next|nest|mytype"
# },
# ...
# ]
echo "matrix=$(git ls-files '**/project.json' |
xargs cat |
jq -s -c --arg projects "$projects" '{ include: [
.[]
| select( .name | IN($projects | split(",") | .[]) )
| {
project: .name,
docker: (.targets | keys | map(select(startswith("docker-") and . != "docker-native")) | map(sub("^docker-"; "")) | .[])
}
]
}')" | tee -a "$GITHUB_OUTPUT"
deps:
name: Build dependencies
runs-on: arc-runners
needs: prepare
permissions:
id-token: write # This is required for requesting the JWT for AWS/ECR login
contents: read # This is required for actions/checkout
outputs:
creds: ${{ toJson(steps.creds.outputs) }}
meta: ${{ toJson(steps.meta.outputs) }}
common: ${{ toJson(steps.common.outputs) }}
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Prepare arguments
id: args
env:
image_repository: ${{ env.IMAGE_REGISTRY }}/docker-cache
localrun: ${{ github.event.localrun }}
run: |
echo image-repository="$image_repository" | tee -a "$GITHUB_OUTPUT"
echo build-cache-local="type=local,src=/tmp,dest=/tmp,mode=max,enabled=${{ !!github.event.localrun }}" | tee -a "$GITHUB_OUTPUT"
echo build-cache="type=registry,image-manifest=true,oci-mediatypes=true,ref=$image_repository,enabled=${{ !github.event.localrun }}" | tee -a "$GITHUB_OUTPUT"
- name: Configure AWS Credentials
if: ${{ !github.event.localrun }}
id: aws-creds
uses: aws-actions/configure-aws-credentials@v4
with:
role-session-name: container-build
aws-region: ${{ vars.AWS_REGION }}
# Identical to usage in push.yml
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Log in to Amazon ECR
if: ${{ steps.aws-creds.conclusion == 'success' }}
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
# We get rate-limited if using docker.io's image for buildkit.
driver-opts: |
image=${{ env.AWS_ECR_REPO_BASE }}/moby/buildkit:buildx-stable-1
install: true
use: true
- name: Prepare creds
id: creds
run: |
cat <<EOF | tee -a "$GITHUB_OUTPUT"
registry=${{ steps.ecr-login.outputs.registry }}
username=${{ steps.ecr-login.outputs[ format('docker_username_{0}',env.IMAGE_REGISTRY) ] }}
password=${{ steps.ecr-login.outputs[ format('docker_password_{0}',env.IMAGE_REGISTRY) ] }}
EOF
- name: Log in Docker
if: ${{ steps.ecr-login.conclusion == 'success' }}
id: docker-login
uses: docker/login-action@v3
with:
registry: ${{ steps.creds.outputs.registry }}
username: ${{ steps.creds.outputs.username }}
password: ${{ steps.creds.outputs.password }}
- name: Common config
id: common
env:
build_args: |
NODE_IMAGE_VERSION=${{ needs.prepare.outputs.node-image-version }}
PLAYWRIGHT_IMAGE_TAG=${{ needs.prepare.outputs.playwright-image-tag }}
DOCKER_IMAGE_REGISTRY=${{ env.ECR_PULLTHROUGH_CACHE }}
image_repository: ${{ steps.args.outputs.image-repository }}
cache_from: |
${{ steps.args.outputs.build-cache-local }}
${{ steps.args.outputs.build-cache }}
cache_to: |
${{ steps.args.outputs.build-cache-local }},mode=max
${{ steps.args.outputs.build-cache }},mode=max
run: |
cat <<EOF | tee -a "$GITHUB_OUTPUT"
file=scripts/ci/Dockerfile
push=false
build-args="$build_args"
image-repository="$image_repository"
cache-from="$cache_from"
cache-to="$cache_to"
EOF
# Build stable layers; only difference between these steps should be the `target`
- name: Docker build/cache dependencies
uses: docker/build-push-action@v6
with:
target: deps
context: .
file: ${{ steps.common.outputs.file }}
push: ${{ steps.common.outputs.push }}
build-args: ${{ steps.common.outputs.build-args }}
cache-from: ${{ steps.common.outputs.cache-from }}
cache-to: ${{ steps.common.outputs.cache-to }}
- name: Docker build/cache dependencies
uses: docker/build-push-action@v6
with:
target: output-base
context: .
file: ${{ steps.common.outputs.file }}
push: ${{ steps.common.outputs.push }}
build-args: ${{ steps.common.outputs.build-args }}
cache-from: ${{ steps.common.outputs.cache-from }}
cache-to: ${{ steps.common.outputs.cache-to }}
- name: Docker build/cache dependencies
uses: docker/build-push-action@v6
with:
target: base-node-with-pg
context: .
file: ${{ steps.common.outputs.file }}
push: ${{ steps.common.outputs.push }}
build-args: ${{ steps.common.outputs.build-args }}
cache-from: ${{ steps.common.outputs.cache-from }}
cache-to: ${{ steps.common.outputs.cache-to }}
build:
name: Build ${{ matrix.project }}
runs-on: arc-runners
needs:
- prepare
- deps
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
max-parallel: 1
permissions:
id-token: write # This is required for requesting the JWT for AWS/ECR login
contents: read # This is required for actions/checkout
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Log in Docker
if: ${{ steps.ecr-login.conclusion == 'success' }}
id: docker-login
uses: docker/login-action@v3
with:
registry: ${{ fromJson(needs.deps.outputs.creds).registry }}
username: ${{ fromJson(needs.deps.outputs.creds).username }}
password: ${{ fromJson(needs.deps.outputs.creds).password }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
# We get rate-limited if using docker.io's image for buildkit.
driver-opts: |
image=${{ env.AWS_ECR_REPO_BASE }}/moby/buildkit:buildx-stable-1
install: true
use: true
- name: Generate image metadata
id: meta
# This step requires a valid GitHub token to query the API for e.g. description of repository
if: ${{ !github.event.localrun }}
uses: docker/metadata-action@v5
with:
images: |
${{ steps.args.outputs.image-repository }}
tags: |
# Edge without prefix should only be used on `main`, so as not to conflict with type=sha
# type=edge
type=edge,prefix=edge-
# SemVer by tag (e.g. v1.2.3)
type=semver,pattern={{version}}
type=pep440,pattern={{version}}
# PR branch/name
# type=ref,event=<branch|tag|pr> Defaults are good
# Git SHA
type=sha,format=short,prefix=
type=sha,format=long,prefix=
# The final sha for the merge commit
# mcgh: Merge Commit GitHub
type=raw,value=${{ inputs.version }},enable=${{ !!inputs.version }}
type=raw,value=mcgh-${{ github.event.pull_request.merge_commit_sha }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
target: output-${{ matrix.docker }}
context: .
file: ${{ fromJson(needs.deps.outputs.common).file }}
push: ${{ !github.event.localrun }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
secrets: |
nx_cloud_access_token=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
build-args: |
${{ fromJson(needs.deps.outputs.common).build-args }}
${{ inputs.build-args }}
# Caching final images doesn't make sense when this should only run if the project is affected (changed)
cache-from: ${{ steps.deps.outputs.common.cache-from }}