From db7156c89bee69c526433ada99927f1c9f3bcfdc Mon Sep 17 00:00:00 2001 From: Filipe Azevedo Date: Mon, 12 Oct 2026 12:16:28 +0100 Subject: [PATCH 1/2] ci: Publish binaries and Docker images on release Releases attach x86_64 and aarch64 Linux binaries (publish.yml) and push the graph-node and graph-node-debug images (docker.yml), which also build nightly from master. Images go to ghcr.io/graphprotocol and, when DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are set, to Docker Hub with the same tags. This replaces the Google Cloud Build setup (docker/cloudbuild.yaml, docker/tag.sh). --- .github/workflows/docker.yml | 130 +++++++++++++++++++++++++ .github/workflows/publish.yml | 178 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 30 +++++- docker/build.sh | 2 +- docker/cloudbuild.yaml | 53 ---------- docker/tag.sh | 31 ------ 6 files changed, 336 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 docker/cloudbuild.yaml delete mode 100644 docker/tag.sh diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000000..1d9c701c737 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,130 @@ +name: Docker images + +on: + push: + branches: [master] + workflow_dispatch: + workflow_call: + inputs: + tag_name: + type: string + default: "" + secrets: + DOCKERHUB_USERNAME: + required: false + DOCKERHUB_TOKEN: + required: false + +permissions: + contents: read + +jobs: + docker: + name: Build and push images + # The Dockerfile compiles graph-node in release mode, which needs the + # cores and disk of a large runner. + runs-on: nscloud-ubuntu-22.04-amd64-16x32 + timeout-minutes: 60 + permissions: + contents: read + packages: write + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ inputs.tag_name }} + + # A release is told apart by tag_name, not the event: called from + # release.yml, the event is the caller's push to master. Same tags as the + # Cloud Build setup: the short SHA always, plus the release tag and + # `latest` for a release, or `nightly` for a master build. Docker Hub + # gets the same tags, and is skipped when its secrets are absent (forks). + - name: Compute build args and tags + id: meta + env: + TAG_NAME: ${{ inputs.tag_name }} + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + run: | + set -euo pipefail + registries=(ghcr.io/graphprotocol) + if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then + registries+=(docker.io/graphprotocol) + echo "dockerhub=true" >> "$GITHUB_OUTPUT" + else + echo "::notice title=Docker Hub::Docker Hub push skipped, DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set" + echo "dockerhub=false" >> "$GITHUB_OUTPUT" + fi + tags=("$(git rev-parse --short=7 HEAD)") + branch="" + if [ -n "$TAG_NAME" ]; then + tags+=("$TAG_NAME" latest) + else + branch="$GITHUB_REF_NAME" + [ "$GITHUB_REF" = refs/heads/master ] && tags+=(nightly) + fi + { + echo "sha=$(git rev-parse HEAD)" + echo "branch=$branch" + for image in graph-node graph-node-debug; do + echo "${image}<> "$GITHUB_OUTPUT" + + - name: Log in to ghcr.io + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: steps.meta.outputs.dockerhub == 'true' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Both images are built by one builder, so the graph-node-debug build + # reuses the graph-node-build stage from its cache. + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4 + + # context is set because the action's default is a git context of + # github.sha, which is not the checked-out release tag. linux/amd64 only, + # as Cloud Build built. + - name: Build and push graph-node + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 + with: + context: . + file: docker/Dockerfile + target: graph-node + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.graph-node }} + labels: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + build-args: | + COMMIT_SHA=${{ steps.meta.outputs.sha }} + REPO_NAME=${{ github.event.repository.name }} + BRANCH_NAME=${{ steps.meta.outputs.branch }} + TAG_NAME=${{ inputs.tag_name }} + + - name: Build and push graph-node-debug + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 + with: + context: . + file: docker/Dockerfile + target: graph-node-debug + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.graph-node-debug }} + labels: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + build-args: | + COMMIT_SHA=${{ steps.meta.outputs.sha }} + REPO_NAME=${{ github.event.repository.name }} + BRANCH_NAME=${{ steps.meta.outputs.branch }} + TAG_NAME=${{ inputs.tag_name }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000000..5a87e6a91b0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,178 @@ +name: Publish + +on: + workflow_call: + inputs: + tag_name: + required: true + type: string + dry_run: + type: boolean + default: false + workflow_dispatch: + inputs: + tag_name: + description: "Tag to publish (e.g. v0.46.0); with dry_run, any ref" + required: true + type: string + dry_run: + description: "Build and upload workflow artifacts only; leave the release untouched" + type: boolean + default: false + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + +jobs: + build: + name: Build ${{ matrix.asset_suffix }} + runs-on: nscloud-ubuntu-22.04-amd64-16x32 + timeout-minutes: 60 + strategy: + fail-fast: true + matrix: + # run_check marks the entries whose target is the runner's own architecture. + include: + - target: x86_64-unknown-linux-gnu + asset_suffix: linux-x86_64 + run_check: true + file_type: "^ELF 64-bit LSB .*x86-64" + - target: aarch64-unknown-linux-gnu + asset_suffix: linux-aarch64 + run_check: false + file_type: "^ELF 64-bit LSB .*ARM aarch64" + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ inputs.tag_name }} + + # rustflags is cleared because the action otherwise sets RUSTFLAGS to + # "-D warnings", which would let a new warning fail a release build. + - name: Setup rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1 + with: + target: ${{ matrix.target }} + cache: false + rustflags: "" + + # perl and make build the vendored OpenSSL; libprotobuf-dev carries the + # well-known protos the firehose build script imports. + - name: Install system packages + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + file make perl pkg-config protobuf-compiler libprotobuf-dev + if [ "${{ matrix.target }}" = aarch64-unknown-linux-gnu ]; then + sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV" + fi + + # gnd is built alongside only for its pq-sys `bundled` feature, which + # links libpq statically into graph-node too, so the binary needs no + # libpq on the host. gnd itself is released by gnd-binary-build.yml. + - name: Build + run: cargo build --release --locked --target ${{ matrix.target }} --bin graph-node --bin gnd + + # A cross-built binary cannot run here, so its architecture is asserted instead. + - name: Stage binary + env: + FILE_TYPE: ${{ matrix.file_type }} + RUN_CHECK: ${{ matrix.run_check }} + run: | + set -euo pipefail + mkdir -p release + binary="release/graph-node-${{ matrix.asset_suffix }}" + cp "target/${{ matrix.target }}/release/graph-node" "$binary" + chmod +x "$binary" + kind=$(file -b "$binary") + echo "$kind" + grep -qE "$FILE_TYPE" <<<"$kind" || { echo "unexpected binary type: $kind" >&2; exit 1; } + if [ "$RUN_CHECK" = true ]; then + "$binary" --help > /dev/null + fi + gzip "$binary" + + - name: Upload artifact + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: binaries-${{ matrix.asset_suffix }} + path: release/*.gz + if-no-files-found: error + + publish: + name: Publish + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + concurrency: + group: publish + steps: + - name: Resolve tag + id: v + env: + TAG: ${{ inputs.tag_name }} + DRY_RUN: ${{ inputs.dry_run || false }} + run: | + set -euo pipefail + if [ "$DRY_RUN" != true ]; then + [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "tag_name must be vX.Y.Z, got: $TAG" >&2; exit 1; } + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Download binaries + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + path: dist + pattern: binaries-* + merge-multiple: true + + # graph-node for both Linux targets. + - name: Checksum binaries + working-directory: dist + run: | + set -euo pipefail + shopt -s nullglob + archives=(*.gz) + [ "${#archives[@]}" -eq 2 ] || { echo "expected 2 archives, found ${#archives[@]}" >&2; exit 1; } + sha256sum -- "${archives[@]}" > SHA256SUMS + cat SHA256SUMS + + - name: Upload release bundle + if: inputs.dry_run + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: release-bundle + path: dist/* + + - name: Attach to release + if: ${{ !inputs.dry_run }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + gh release upload "${{ steps.v.outputs.tag }}" --repo "$GITHUB_REPOSITORY" --clobber \ + dist/*.gz dist/SHA256SUMS + + - name: Summary + env: + DRY_RUN: ${{ inputs.dry_run || false }} + run: | + set -euo pipefail + { + if [ "$DRY_RUN" = true ]; then + echo "### Dry run for ${{ steps.v.outputs.tag }}: release left untouched" + else + echo "### graph-node ${{ steps.v.outputs.tag }}" + echo "https://github.com/${GITHUB_REPOSITORY}/releases/tag/${{ steps.v.outputs.tag }}" + fi + echo '```' + cat dist/SHA256SUMS + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7528cfb99e6..fe9adaeaeab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,14 +8,16 @@ on: permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - jobs: release-please: name: Release Please runs-on: ubuntu-latest timeout-minutes: 5 + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: # A pull request opened with GITHUB_TOKEN does not trigger workflows, so # the release PR is opened as the app instead. @@ -47,3 +49,25 @@ jobs: else echo "Nothing to release" >> "$GITHUB_STEP_SUMMARY" fi + + publish: + name: Publish binaries + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + permissions: + contents: write + uses: ./.github/workflows/publish.yml + with: + tag_name: ${{ needs.release-please.outputs.tag_name }} + + docker: + name: Docker images + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + permissions: + contents: read + packages: write + uses: ./.github/workflows/docker.yml + secrets: inherit + with: + tag_name: ${{ needs.release-please.outputs.tag_name }} diff --git a/docker/build.sh b/docker/build.sh index 5dd67ea99d5..8ccf5f63770 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -1,7 +1,7 @@ #! /bin/bash # This file is only here to ease testing/development. Official images are -# built using the 'cloudbuild.yaml' file +# built by the .github/workflows/docker.yml workflow type -p podman > /dev/null && docker=podman || docker=docker diff --git a/docker/cloudbuild.yaml b/docker/cloudbuild.yaml deleted file mode 100644 index 0bf800cddad..00000000000 --- a/docker/cloudbuild.yaml +++ /dev/null @@ -1,53 +0,0 @@ -options: - machineType: "E2_HIGHCPU_32" -timeout: 1800s -steps: -- name: 'gcr.io/cloud-builders/docker' - args: ['build', '--target', 'graph-node-build', - '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', - '--build-arg', 'REPO_NAME=$REPO_NAME', - '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', - '--build-arg', 'TAG_NAME=$TAG_NAME', - '-t', 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA', - '-f', 'docker/Dockerfile', '.'] -- name: 'gcr.io/cloud-builders/docker' - args: ['build', '--target', 'graph-node', - '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', - '--build-arg', 'REPO_NAME=$REPO_NAME', - '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', - '--build-arg', 'TAG_NAME=$TAG_NAME', - '-t', 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', - '-f', 'docker/Dockerfile', '.'] -- name: 'gcr.io/cloud-builders/docker' - args: ['build', '--target', 'graph-node-debug', - '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', - '--build-arg', 'REPO_NAME=$REPO_NAME', - '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', - '--build-arg', 'TAG_NAME=$TAG_NAME', - '-t', 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA', - '-f', 'docker/Dockerfile', '.'] -- name: 'gcr.io/cloud-builders/docker' - args: ['tag', - 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', - 'lutter/graph-node:$SHORT_SHA'] -- name: 'gcr.io/cloud-builders/docker' - entrypoint: 'bash' - args: ['docker/tag.sh'] - secretEnv: ['PASSWORD'] - env: - - 'SHORT_SHA=$SHORT_SHA' - - 'TAG_NAME=$TAG_NAME' - - 'PROJECT_ID=$PROJECT_ID' - - 'DOCKER_HUB_USER=$_DOCKER_HUB_USER' - - 'BRANCH_NAME=$BRANCH_NAME' -images: - - 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA' - - 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA' - - 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA' -substitutions: - # The owner of the access token whose encrypted value is in PASSWORD - _DOCKER_HUB_USER: "lutter" -secrets: - - kmsKeyName: projects/the-graph-staging/locations/global/keyRings/docker/cryptoKeys/docker-hub-push - secretEnv: - PASSWORD: 'CiQAdfFldbmUiHgGP1lPq6bAOfd+VQ/dFwyohB1IQwiwQg03ZE8STQDvWKpv6eJHVUN1YoFC5FcooJrH+Stvx9oMD7jBjgxEH5ngIiAysWP3E4Pgxt/73xnaanbM1EQ94eVFKCiY0GaEKFNu0BJx22vCYmU4' diff --git a/docker/tag.sh b/docker/tag.sh deleted file mode 100644 index 1abafa95afa..00000000000 --- a/docker/tag.sh +++ /dev/null @@ -1,31 +0,0 @@ -#! /bin/bash - -# This script is used by cloud build to push Docker images into Docker hub - -tag_and_push() { - tag=$1 - docker tag gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA \ - graphprotocol/graph-node:$tag - docker push graphprotocol/graph-node:$tag - - docker tag gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA \ - graphprotocol/graph-node-debug:$tag - docker push graphprotocol/graph-node-debug:$tag -} - -echo "Logging into Docker Hub" -echo $PASSWORD | docker login --username="$DOCKER_HUB_USER" --password-stdin - -set -ex - -tag_and_push "$SHORT_SHA" - -# Builds of tags set the tag in Docker Hub, too -[ -n "$TAG_NAME" ] && tag_and_push "$TAG_NAME" -# Builds for tags vN.N.N become the 'latest' -[[ "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] && tag_and_push latest - -# If the build is from the master branch, tag it as 'nightly' -[ "$BRANCH_NAME" = "master" ] && tag_and_push nightly - -exit 0 From 941c88cdc1b90f84c2d7626bd6cd0e1de2dc9d5e Mon Sep 17 00:00:00 2001 From: Filipe Azevedo Date: Mon, 12 Oct 2026 12:53:51 +0100 Subject: [PATCH 2/2] ci: Tag images with metadata-action and skip Docker Hub login without a token --- .github/workflows/docker.yml | 91 ++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1d9c701c737..352def54080 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,52 +28,43 @@ jobs: permissions: contents: read packages: write + # Docker Hub is pushed to only when its token is set; `if:` can't read + # secrets directly. + env: + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: ref: ${{ inputs.tag_name }} - # A release is told apart by tag_name, not the event: called from - # release.yml, the event is the caller's push to master. Same tags as the - # Cloud Build setup: the short SHA always, plus the release tag and - # `latest` for a release, or `nightly` for a master build. Docker Hub - # gets the same tags, and is skipped when its secrets are absent (forks). - - name: Compute build args and tags + # The same tags as the Cloud Build setup: the short SHA always, plus + # `nightly` for master, or the release tag and `latest` for a release. A + # release is told apart by tag_name, not the event: called from + # release.yml, the event is the caller's push to master. + - name: Image metadata (graph-node) id: meta - env: - TAG_NAME: ${{ inputs.tag_name }} - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - run: | - set -euo pipefail - registries=(ghcr.io/graphprotocol) - if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then - registries+=(docker.io/graphprotocol) - echo "dockerhub=true" >> "$GITHUB_OUTPUT" - else - echo "::notice title=Docker Hub::Docker Hub push skipped, DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set" - echo "dockerhub=false" >> "$GITHUB_OUTPUT" - fi - tags=("$(git rev-parse --short=7 HEAD)") - branch="" - if [ -n "$TAG_NAME" ]; then - tags+=("$TAG_NAME" latest) - else - branch="$GITHUB_REF_NAME" - [ "$GITHUB_REF" = refs/heads/master ] && tags+=(nightly) - fi - { - echo "sha=$(git rev-parse HEAD)" - echo "branch=$branch" - for image in graph-node graph-node-debug; do - echo "${image}<> "$GITHUB_OUTPUT" + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 + with: + images: | + ghcr.io/graphprotocol/graph-node + name=docker.io/graphprotocol/graph-node,enable=${{ env.DOCKERHUB_TOKEN != '' }} + flavor: latest=false + tags: &tags | + type=sha,prefix= + type=raw,value=nightly,enable=${{ inputs.tag_name == '' && github.ref == 'refs/heads/master' }} + type=raw,value=${{ inputs.tag_name }},enable=${{ inputs.tag_name != '' }} + type=raw,value=latest,enable=${{ inputs.tag_name != '' }} + + - name: Image metadata (graph-node-debug) + id: meta-debug + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 + with: + images: | + ghcr.io/graphprotocol/graph-node-debug + name=docker.io/graphprotocol/graph-node-debug,enable=${{ env.DOCKERHUB_TOKEN != '' }} + flavor: latest=false + tags: *tags - name: Log in to ghcr.io uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 @@ -83,7 +74,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Log in to Docker Hub - if: steps.meta.outputs.dockerhub == 'true' + if: env.DOCKERHUB_TOKEN != '' uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} @@ -105,12 +96,12 @@ jobs: target: graph-node platforms: linux/amd64 push: true - tags: ${{ steps.meta.outputs.graph-node }} - labels: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - build-args: | - COMMIT_SHA=${{ steps.meta.outputs.sha }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: &build-args | + COMMIT_SHA=${{ github.sha }} REPO_NAME=${{ github.event.repository.name }} - BRANCH_NAME=${{ steps.meta.outputs.branch }} + BRANCH_NAME=${{ inputs.tag_name == '' && github.ref_name || '' }} TAG_NAME=${{ inputs.tag_name }} - name: Build and push graph-node-debug @@ -121,10 +112,6 @@ jobs: target: graph-node-debug platforms: linux/amd64 push: true - tags: ${{ steps.meta.outputs.graph-node-debug }} - labels: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - build-args: | - COMMIT_SHA=${{ steps.meta.outputs.sha }} - REPO_NAME=${{ github.event.repository.name }} - BRANCH_NAME=${{ steps.meta.outputs.branch }} - TAG_NAME=${{ inputs.tag_name }} + tags: ${{ steps.meta-debug.outputs.tags }} + labels: ${{ steps.meta-debug.outputs.labels }} + build-args: *build-args