feat(ci): Use new docker build #290
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |