build: refactor container build process #3585
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: Containers | |
# Build containers when pushed to master or if a pull request has been labeled with "container" | |
on: | |
push: | |
branches: | |
- master | |
pull_request: | |
branches: | |
- master | |
types: [labeled, synchronize, opened] | |
jobs: | |
setup: | |
# Determine if any containers need to be built and what tags they will result in | |
name: Setup container tags | |
runs-on: ubuntu-latest | |
outputs: | |
# Version information | |
# v7.0.0 | |
version: ${{ steps.version.outputs.version }} | |
# v7 | |
version_major: ${{ steps.version.outputs.version_major }} | |
# v7.1 | |
version_major_minor: ${{ steps.version.outputs.version_major_minor }} | |
# Tagging information as a JSON object | |
# eg { cli: ["ghcr.io/linz/basemaps/cli:latest", "ghcr.io/linz/basemaps/cli:v7"], needs_containers: true } | |
tags: ${{ steps.tags.outputs.result }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Setup tags | |
id: version | |
run: | | |
GIT_VERSION=$(git describe --tags --always --match 'v*') | |
GIT_VERSION_MAJOR=$(echo $GIT_VERSION | cut -d. -f1) | |
GIT_VERSION_MAJOR_MINOR=$(echo $GIT_VERSION | cut -d. -f1,2) | |
echo "version=${GIT_VERSION}" >> $GITHUB_OUTPUT | |
echo "version_major=${GIT_VERSION_MAJOR}" >> $GITHUB_OUTPUT | |
echo "version_major_minor=${GIT_VERSION_MAJOR_MINOR}" >> $GITHUB_OUTPUT | |
- name: Create Image Tags | |
id: tags | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
// Images to create tags for | |
const images = ['cli', 'server']; | |
// Mapping of images to their tags | |
// cli => "ghcr.io/linz/basemaps/cli:latest,ghcr.io/linz/basemaps/cli:v7" | |
const output = {}; | |
// List of tags to apply to images, eg "v7" or "latest" | |
const tags = []; | |
// If on master ensure that the "latest" and a specific tag version is used | |
if ('${{ github.ref }}' == 'refs/heads/master' ){ | |
tags.push('latest'); | |
// If on a release commit add `v7`, `v7.1` and `v7.1` | |
if (`${{ toJson(github.event.head_commit.message) }}`.startsWith('release:')) { | |
tags.push('${{ steps.version.outputs.version_major }}'); | |
tags.push('${{ steps.version.outputs.version_major_minor }}'); | |
} | |
tags.push('${{ steps.version.outputs.version }}'); | |
} | |
// If a pull request is labeled as "container", ensure a pull request tag is created | |
// "ghcr.io/linz/basemaps/cli:pr-1124" | |
const labels = ${{ toJson(github.event.pull_request.labels.*.name) }} | |
if (labels.includes('container')) { | |
tags.push('pr-${{ github.event.number }}') | |
} | |
for (const img of images) { | |
const repo = `ghcr.io/${{ github.repository }}/${img}` | |
output[img] = JSON.stringify(tags.map(t => `${repo}:${t}`)) | |
} | |
// Have any tags been created | |
output.needs_containers = tags.length > 0; | |
return output; | |
- name: List tags | |
run: | | |
echo ${{ toJson(steps.tags.outputs.result) }} | jq | |
build-containers: | |
needs: setup | |
if: ${{ fromJson(needs.setup.outputs.tags).needs_containers == true }} | |
runs-on: ${{ matrix.os }} | |
strategy: | |
matrix: | |
include: | |
- os: ubuntu-latest | |
arch: amd64 | |
platform: linux/amd64 | |
- os: ubuntu-24.04-arm | |
arch: arm64 | |
platform: linux/arm64 | |
permissions: | |
id-token: write | |
contents: read | |
packages: write | |
steps: | |
- uses: linz/action-typescript@v3 | |
- name: (Prod) Ensure tag exists | |
if: github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'release:') | |
run: | | |
git fetch --depth=1 origin +refs/tags/*:refs/tags/* # see https://stackoverflow.com/a/60184319/9285308 | |
git config user.name "github-actions[bot]" | |
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
CURRENT_VERSION=$(node -p "require('./lerna.json').version") | |
git tag v${CURRENT_VERSION} -m v${CURRENT_VERSION} || true # Only create the tag if it doesn't exist | |
- name: Bundle & Package all files | |
run: | | |
npx lerna run bundle --stream | |
npm pack --workspaces | |
env: | |
NODE_ENV: 'production' | |
- name: Set up Docker Buildx | |
id: buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Copy packages and files | |
run: | | |
# Files are packed into the base directory | |
cp *.tgz packages/server/ | |
cp *.tgz packages/cli/ | |
cp -r packages/lambda-tiler/static/ packages/server/ | |
cp -r packages/lambda-tiler/static/ packages/cli/ | |
- name: Create docker metadata | |
id: meta | |
uses: docker/metadata-action@v5 | |
with: | |
labels: | |
org.opencontainers.image.version=${{ needs.setup.outputs.version }} | |
org.opencontainers.image.licenses=MIT | |
tags: | | |
type=sha | |
- name: Login to GitHub Container Registry | |
uses: docker/login-action@v3 # v3 | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: 'Build Container - @basemaps/cli' | |
uses: docker/build-push-action@v6 | |
id: 'build_cli' | |
with: | |
context: packages/cli | |
labels: ${{ steps.meta.outputs.labels }} | |
outputs: type=image,"name=ghcr.io/${{ github.repository }}/cli",push-by-digest=true,push=true | |
build-args: | | |
GIT_HASH=${{ github.sha }} | |
GIT_VERSION=${{ needs.setup.outputs.version }} | |
GITHUB_RUN_ID=${{ github.run_id }} | |
- name: 'Build Container - @basemaps/server' | |
uses: docker/build-push-action@v6 | |
id: 'build_server' | |
with: | |
context: packages/server | |
labels: ${{ steps.meta.outputs.labels }} | |
outputs: type=image,"name=ghcr.io/${{ github.repository }}/server",push-by-digest=true,push=true | |
build-args: | | |
GIT_HASH=${{ github.sha }} | |
GIT_VERSION=${{ needs.setup.outputs.version }} | |
GITHUB_RUN_ID=${{ github.run_id }} | |
- name: Export digests | |
run: | | |
mkdir -p ${{ runner.temp }}/digests/cli ${{ runner.temp }}/digests/server | |
digest="${{ steps.build_cli.outputs.digest }}" | |
touch "${{ runner.temp }}/digests/cli/${digest#sha256:}" | |
digest="${{ steps.build_server.outputs.digest }}" | |
touch "${{ runner.temp }}/digests/server/${digest#sha256:}" | |
- name: Upload digest | |
uses: actions/upload-artifact@v4 | |
with: | |
name: digests-${{ matrix.arch }} | |
path: ${{ runner.temp }}/digests/* | |
if-no-files-found: error | |
retention-days: 1 | |
merge: | |
# Find all the containers built from the matrix jobs then tag and publish them | |
name: Merge and publish containers | |
permissions: | |
id-token: write | |
contents: read | |
packages: write | |
runs-on: ubuntu-latest | |
needs: | |
- build-containers | |
- setup | |
steps: | |
- name: Download digests | |
uses: actions/download-artifact@v4 | |
with: | |
path: ${{ runner.temp }}/digests | |
pattern: digests-* | |
merge-multiple: true | |
- name: Show digests | |
working-directory: ${{ runner.temp }}/digests | |
run: | |
ls -R . | |
- name: Login to GHCR | |
uses: docker/login-action@v3 | |
with: | |
registry: ghcr.io | |
username: ${{ github.repository_owner }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Create manifest list and push | |
working-directory: ${{ runner.temp }}/digests | |
run: | | |
cd cli | |
docker buildx imagetools create $(jq -cr '. | map("-t " + .) | join(" ")' <<< '${{ fromJson(needs.setup.outputs.tags).cli }}') \ | |
$(printf 'ghcr.io/${{ github.repository }}/cli@sha256:%s ' *) | |
cd ../server/ | |
docker buildx imagetools create $(jq -cr '. | map("-t " + .) | join(" ")' <<< '${{ fromJson(needs.setup.outputs.tags).server }}') \ | |
$(printf 'ghcr.io/${{ github.repository }}/server@sha256:%s ' *) |