From 5be1246be17e098db7f1e43714b0c25276720a5f Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:25:25 +0100 Subject: [PATCH 1/6] build: refactor inputs Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test.yml | 181 +++++++++++++++++---- .github/workflows/build.yml | 294 +++++++++++++++++++---------------- .github/workflows/verify.yml | 15 +- README.md | 71 ++++++++- 4 files changed, 394 insertions(+), 167 deletions(-) diff --git a/.github/workflows/.test.yml b/.github/workflows/.test.yml index b62a0cd..f1c2666 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test.yml @@ -21,15 +21,16 @@ jobs: contents: read id-token: write with: + cache: true + file: test/hello.Dockerfile output: image push: ${{ github.event_name != 'pull_request' }} - cache: true + sbom: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=build-ghbuilder-single-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true + secrets: registry-auths: | - registry: public.ecr.aws @@ -70,17 +71,17 @@ jobs: contents: read id-token: write with: - output: image - push: ${{ github.event_name != 'pull_request' }} cache: true cache-scope: build-aws + file: test/hello.Dockerfile + output: image + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=build-ghbuilder-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: public.ecr.aws @@ -115,6 +116,58 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + build-aws-nosign: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + cache: true + cache-scope: build-aws + file: test/hello.Dockerfile + output: image + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + sbom: true + sign: false + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=build-ghbuilder-nosign--${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + build-aws-nosign-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - build-aws-nosign + with: + builder-outputs: ${{ toJSON(needs.build-aws-nosign.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + build-aws-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - build-aws-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-aws-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + build-ghcr: uses: ./.github/workflows/build.yml permissions: @@ -122,14 +175,14 @@ jobs: id-token: write packages: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: ghcr.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: ghcr.io @@ -170,14 +223,14 @@ jobs: contents: read id-token: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: registry-1-stage.docker.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: registry-1-stage.docker.io @@ -219,16 +272,16 @@ jobs: id-token: write packages: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: | ghcr.io/docker/github-builder-test public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=${{ github.run_id }},prefix=build-ghcr-and-aws- - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: ghcr.io @@ -275,12 +328,13 @@ jobs: contents: read id-token: write with: - output: local - push: ${{ github.event_name != 'pull_request' }} artifact-name: build-output - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 + artifact-upload: true + file: test/hello.Dockerfile + output: local + platforms: linux/amd64,linux/arm64 + sbom: true + sign: ${{ github.event_name != 'pull_request' }} build-local-verify: uses: ./.github/workflows/verify.yml @@ -311,11 +365,12 @@ jobs: contents: read id-token: write with: - output: local - push: ${{ github.event_name != 'pull_request' }} artifact-name: build-single-output - build-file: test/hello.Dockerfile - build-sbom: true + artifact-upload: true + file: test/hello.Dockerfile + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} build-local-single-verify: uses: ./.github/workflows/verify.yml @@ -340,6 +395,76 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + build-local-noupload: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + artifact-upload: false + file: test/hello.Dockerfile + output: local + platforms: linux/amd64,linux/arm64 + sbom: true + + build-local-noupload-verify: + uses: ./.github/workflows/verify.yml + needs: + - build-local-noupload + with: + builder-outputs: ${{ toJSON(needs.build-local-noupload.outputs) }} + + build-local-noupload-outputs: + runs-on: ubuntu-24.04 + needs: + - build-local-noupload + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-noupload.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + build-local-nosign: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + artifact-name: build-nosign-output + artifact-upload: true + file: test/hello.Dockerfile + output: local + platforms: linux/amd64,linux/arm64 + sbom: true + sign: false + + build-local-nosign-verify: + uses: ./.github/workflows/verify.yml + needs: + - build-local-nosign + with: + builder-outputs: ${{ toJSON(needs.build-local-nosign.outputs) }} + + build-local-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - build-local-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + build-set-runner: uses: ./.github/workflows/build.yml permissions: @@ -347,13 +472,13 @@ jobs: id-token: write with: runner: amd64 + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: false meta-images: ghcr.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-platforms: linux/amd64,linux/arm64 bake-aws-single: uses: ./.github/workflows/bake.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 178b414..5f48572 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,32 +5,36 @@ on: inputs: runner: type: string - description: "Linux machine to run build on. Can be one of auto, amd64, arm64 (defaults to auto which selects best-matching runner based on target platform)" + description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)" required: false default: 'auto' - context: - type: string - description: "Context to build from (defaults to repository root)" + setup-qemu: + type: boolean + description: "Runs the setup-qemu-action step to install QEMU static binaries" required: false - default: . - output: - type: string - description: "Build output destination (image or local)" - required: true + default: false artifact-name: type: string - description: "Name of the uploaded artifact (for local output)" + description: "Name of the uploaded GitHub artifact (for local output)" required: false default: 'docker-github-builder-assets' + artifact-upload: + type: boolean + description: "Upload build output GitHub artifact (for local output)" + required: false + default: false envs: type: string - description: "Environment variables to set" + description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" required: false - push: - type: boolean - description: "Push image to the registry (for image output) and/or sign attestation manifests or local artifacts" + annotations: + type: string + description: "List of annotations to set to the image (for image output)" + required: false + build-args: + type: string + description: "List of build-time variables. If you want to set a build-arg through an environment variable, use the envs input" required: false - default: false cache: type: boolean description: "Enable cache to GitHub Actions cache backend" @@ -45,99 +49,86 @@ on: description: "Cache layers to export if cache enabled (min or max)" required: false default: 'min' - set-meta-annotations: - type: boolean - description: "Set metadata-action annotations" - required: false - default: false - set-meta-labels: - type: boolean - description: "Set metadata-action labels" - required: false - default: false - setup-qemu: - type: boolean - description: "Install QEMU static binaries" - required: false - default: false - # same as docker/metadata-action inputs (minus sep-tags, sep-labels, sep-annotations, bake-target) - meta-images: + context: type: string - description: "List of images to use as base name for tags" + description: "Context to build from in the Git working tree" required: false - meta-tags: + default: . + file: type: string - description: "List of tags as key-value pair attributes" + description: "Path to the Dockerfile" required: false - meta-flavor: + labels: type: string - description: "Flavors to apply" + description: "List of labels for an image (for image output)" required: false - meta-labels: + output: type: string - description: "List of custom labels" - required: false - meta-annotations: + description: "Build output destination (one of image or local). Unlike the build-push-action, it only accepts image or local. The reusable workflow takes care of setting the outputs attribute" + required: true + platforms: type: string - description: "List of custom annotations" + description: "List of target platforms to build" required: false - # same as docker/setup-qemu-action inputs (minus platforms, cache-image) - qemu-image: - type: string - description: "QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)" + push: + type: boolean + description: "Push image to the registry (for image output)" required: false - # same as docker/build-push-action inputs - build-annotations: + default: false + sbom: type: string - description: "List of annotation to set to the image" + description: "Generate SBOM attestation for the build" required: false - build-args: + shm-size: type: string - description: "List of build-time variables" + description: "Size of /dev/shm (e.g., 2g)" required: false - build-file: + sign: type: string - description: "Path to the Dockerfile" + description: "Sign attestation manifest for image output or artifacts for local output, can be one of auto, true or false. The auto mode will enable signing if push is enabled for pushing the image or if artifact-upload is enabled for uploading the local build output as GitHub Artifact" required: false - build-labels: + default: auto + target: type: string - description: "List of metadata for an image" + description: "Sets the target stage to build" required: false - build-platforms: + ulimit: type: string - description: "List of target platforms to build" + description: "Ulimit options (e.g., nofile=1024:1024)" required: false - build-pull: + # docker/metadata-action + set-meta-annotations: type: boolean - description: "Always attempt to pull all referenced images" + description: "Append OCI Image Format Specification annotations generated by docker/metadata-action" required: false default: false - build-sbom: - type: string - description: "Generate SBOM attestation for the build (shorthand for --attest=type=sbom)" + set-meta-labels: + type: boolean + description: "Append OCI Image Format Specification labels generated by docker/metadata-action" required: false - build-shm-size: + default: false + meta-images: type: string - description: "Size of /dev/shm (e.g., 2g)" + description: "List of images to use as base name for tags (required for image output)" required: false - build-target: + meta-tags: type: string - description: "Sets the target stage to build" + description: "List of tags as key-value pair attributes" required: false - build-ulimit: + meta-flavor: type: string - description: "Ulimit options (e.g., nofile=1024:1024)" + description: "Flavor defines a global behavior for meta-tags" required: false secrets: registry-auths: - description: "Registry authentication details as YAML objects" + description: "Raw authentication to registries, defined as YAML objects (for image output)" required: false github-token: - description: "GitHub Token used to authenticate against a repository for Git context" + description: "GitHub Token used to authenticate against the repository for Git context" required: false outputs: meta-json: - description: "Metadata JSON output (only for image output)" + description: "Metadata JSON output (for image output)" value: ${{ jobs.finalize.outputs.meta-json }} cosign-version: description: "Cosign version used for verification" @@ -151,6 +142,9 @@ on: output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} + signed: + description: "Whether attestations manifests or artifacts were signed" + value: ${{ jobs.finalize.outputs.signed }} env: BUILDX_VERSION: "v0.30.1" @@ -165,6 +159,7 @@ jobs: runs-on: ubuntu-24.04 outputs: includes: ${{ steps.set.outputs.includes }} + sign: ${{ steps.set.outputs.sign }} steps: - name: Install @docker/actions-toolkit @@ -175,13 +170,17 @@ jobs: script: | await exec.exec('npm', ['install', '--prefer-offline', '--no-audit', '--ignore-scripts', core.getInput('dat-module')]); - - name: Set includes + name: Set outputs id: set uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_MATRIX-SIZE-LIMIT: ${{ env.MATRIX_SIZE_LIMIT }} INPUT_RUNNER: ${{ inputs.runner }} - INPUT_BUILD-PLATFORMS: ${{ inputs.build-platforms }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PLATFORMS: ${{ inputs.platforms }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_SIGN: ${{ inputs.sign }} with: script: | const { GitHub } = require('@docker/actions-toolkit/lib/github'); @@ -190,7 +189,11 @@ jobs: const inpMatrixSizeLimit = parseInt(core.getInput('matrix-size-limit'), 10); const inpRunner = core.getInput('runner'); - const inpBuildPlatforms = Util.getInputList('build-platforms'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpPlatforms = Util.getInputList('platforms'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpSign = core.getInput('sign'); let runner = inpRunner; if (inpRunner === 'amd64') { @@ -198,31 +201,56 @@ jobs: } else if (inpRunner === 'arm64') { runner = 'ubuntu-24.04-arm'; } else if (inpRunner !== 'auto') { - throw new Error(`Invalid runner input: ${inpRunner}`); + core.setFailed(`Invalid runner input: ${inpRunner}`); + return; + } + + const sign = + inpSign === 'auto' + ? (inpOutput === 'image' && inpPush) || (inpOutput === 'local' && inpArtifactUpload) + : inpSign === 'true'; + + if (inpOutput === 'local' && inpPush) { + core.warning(`push is ignored when output is local`); + } else if (inpOutput === 'image' && inpArtifactUpload) { + core.warning(`artifact-upload is ignored when output is image`); + } + if (inpOutput === 'image' && !inpPush && sign) { + core.setFailed(`signing attestation manifests requires push to be enabled`); + return; + } + + if (inpPlatforms.length > inpMatrixSizeLimit) { + core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); + return; } const privateRepo = GitHub.context.payload.repository?.private ?? false; - await core.group(`Set includes`, async () => { + await core.group(`Set includes output`, async () => { let includes = []; - if (inpBuildPlatforms.length > inpMatrixSizeLimit) { - throw new Error(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); - } else if (inpBuildPlatforms.length === 0) { + if (inpPlatforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: runner === 'auto' ? 'ubuntu-24.04' : runner, + sign: sign }); } else { - inpBuildPlatforms.forEach((platform, index) => { + inpPlatforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner, + sign: sign }); }); } core.info(JSON.stringify(includes, null, 2)); core.setOutput('includes', JSON.stringify(includes)); }); + await core.group(`Set sign output`, async () => { + core.info(`sign: ${sign}`); + core.setOutput('sign', sign); + }); build: runs-on: ${{ matrix.runner }} @@ -291,8 +319,6 @@ jobs: name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} - with: - image: ${{ inputs.qemu-image }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -307,20 +333,20 @@ jobs: env: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} - INPUT_CONTEXT: ${{ inputs.context }} - INPUT_OUTPUT: ${{ inputs.output }} - INPUT_PUSH: ${{ inputs.push }} + INPUT_ANNOTATIONS: ${{ inputs.annotations }} INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_LABELS: ${{ inputs.labels }} + INPUT_CONTEXT: ${{ inputs.context }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_TARGET: ${{ inputs.target }} INPUT_META-IMAGES: ${{ inputs.meta-images }} - INPUT_BUILD-ANNOTATIONS: ${{ inputs.build-annotations }} INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} INPUT_META-ANNOTATIONS: ${{ steps.meta.outputs.annotations }} - INPUT_BUILD-LABELS: ${{ inputs.build-labels }} INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} INPUT_META-LABELS: ${{ steps.meta.outputs.labels }} - INPUT_BUILD-TARGET: ${{ inputs.build-target }} with: script: | const { GitHub } = require('@docker/actions-toolkit/lib/github'); @@ -328,23 +354,23 @@ jobs: const inpPlatform = core.getInput('platform'); const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : ''; core.setOutput('platform-pair-suffix', platformPairSuffix); - const inpLocalExportDir = core.getInput('local-export-dir'); - const inpContext = core.getInput('context'); - const inpOutput = core.getInput('output'); - const inpPush = core.getBooleanInput('push'); + const inpAnnotations = core.getMultilineInput('annotations'); const inpCache = core.getBooleanInput('cache'); const inpCacheScope = core.getInput('cache-scope'); const inpCacheMode = core.getInput('cache-mode'); + const inpContext = core.getInput('context'); + const inpLabels = core.getMultilineInput('labels'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpTarget = core.getInput('target'); + const inpMetaImages = core.getMultilineInput('meta-images'); const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); - const inpBuildAnnotations = core.getMultilineInput('build-annotations'); const inpMetaAnnotations = core.getMultilineInput('meta-annotations'); const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); - const inpBuildLabels = core.getMultilineInput('build-labels'); const inpMetaLabels = core.getMultilineInput('meta-labels'); - const inpBuildTarget = core.getInput('build-target'); const buildContext = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; core.setOutput('context', buildContext); @@ -353,6 +379,7 @@ jobs: case 'image': if (inpMetaImages.length == 0) { core.setFailed('meta-images is required when output is image'); + return; } core.setOutput('output', `type=image,"name=${inpMetaImages.join(',')}",oci-artifact=true,push-by-digest=true,name-canonical=true,push=${inpPush}`); break; @@ -361,6 +388,7 @@ jobs: break; default: core.setFailed(`Invalid output: ${inpOutput}`); + return; } if (inpPlatform) { @@ -368,19 +396,19 @@ jobs: } if (inpCache) { - core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'}${platformPairSuffix}`); - core.setOutput('cache-to', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); + core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix}`); + core.setOutput('cache-to', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); } if (inpSetMetaAnnotations && inpMetaAnnotations.length > 0) { - inpBuildAnnotations.push(...inpMetaAnnotations); + inpAnnotations.push(...inpMetaAnnotations); } - core.setOutput('annotations', inpBuildAnnotations.join('\n')); + core.setOutput('annotations', inpAnnotations.join('\n')); if (inpSetMetaLabels && inpMetaLabels.length > 0) { - inpBuildLabels.push(...inpMetaLabels); + inpLabels.push(...inpMetaLabels); } - core.setOutput('labels', inpBuildLabels.join('\n')); + core.setOutput('labels', inpLabels.join('\n')); if (GitHub.context.payload.repository?.private ?? false) { // if this is a private repository, we set the default provenance @@ -406,23 +434,22 @@ jobs: cache-from: ${{ steps.prepare.outputs.cache-from }} cache-to: ${{ steps.prepare.outputs.cache-to }} context: ${{ steps.prepare.outputs.context }} - file: ${{ inputs.build-file }} + file: ${{ inputs.file }} labels: ${{ steps.prepare.outputs.labels }} outputs: ${{ steps.prepare.outputs.output }} platforms: ${{ steps.prepare.outputs.platform }} provenance: ${{ steps.prepare.outputs.provenance }} - pull: ${{ inputs.build-pull }} - sbom: ${{ inputs.build-sbom }} + sbom: ${{ inputs.sbom }} secret-envs: GIT_AUTH_TOKEN=GIT_AUTH_TOKEN - shm-size: ${{ inputs.build-shm-size }} - target: ${{ inputs.build-target }} - ulimit: ${{ inputs.build-ulimit }} + shm-size: ${{ inputs.shm-size }} + target: ${{ inputs.target }} + ulimit: ${{ inputs.ulimit }} env: BUILDKIT_MULTI_PLATFORM: 1 GIT_AUTH_TOKEN: ${{ secrets.github-token || github.token }} - name: Install Cosign - if: ${{ inputs.push }} + if: ${{ needs.prepare.outputs.sign }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERSION: ${{ env.COSIGN_VERSION }} @@ -440,7 +467,7 @@ jobs: - name: Signing attestation manifests id: signing-attestation-manifests - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ matrix.sign && inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} @@ -487,7 +514,7 @@ jobs: - name: Signing local artifacts id: signing-local-artifacts - if: ${{ inputs.push && inputs.output == 'local' }} + if: ${{ matrix.sign && inputs.output == 'local' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_LOCAL-OUTPUT-DIR: ${{ env.LOCAL_EXPORT_DIR }} @@ -523,7 +550,7 @@ jobs: tree -nh ${{ env.LOCAL_EXPORT_DIR }} - name: Upload artifact - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} @@ -538,17 +565,22 @@ jobs: INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.build.outputs.digest }} INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_SIGNED: ${{ matrix.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); const inpArtifactName = core.getInput('artifact-name'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactName + artifactName: inpArtifactUpload ? inpArtifactName : '', + signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -560,9 +592,11 @@ jobs: meta-json: ${{ steps.meta.outputs.json }} cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} - artifact-name: ${{ inputs.artifact-name }} + artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} output-type: ${{ inputs.output }} + signed: ${{ needs.prepare.outputs.sign }} needs: + - prepare - build steps: - @@ -614,7 +648,8 @@ jobs: } } if (digests.length === 0) { - throw new Error('No image digests found from build outputs'); + core.setFailed('No image digests found from build outputs'); + return; } for (const imageName of inpImageNames) { @@ -626,13 +661,7 @@ jobs: createArgs.push(digest); } if (inpPush) { - await exec.getExecOutput('docker', createArgs, { - ignoreReturnCode: true - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr); - } - }); + await exec.exec('docker', createArgs); } else { await core.group(`Generated imagetools create command for ${imageName}`, async () => { core.info(`docker ${createArgs.join(' ')}`); @@ -641,7 +670,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} @@ -653,14 +682,19 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} + INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpBuildOutputs = JSON.parse(core.getInput('build-outputs')); - const verifyCommands = []; - for (const key of Object.keys(inpBuildOutputs)) { - const output = JSON.parse(inpBuildOutputs[key]); - if (output.verifyCommands) { - verifyCommands.push(output.verifyCommands); + const inpSigned = core.getBooleanInput('signed'); + + if (inpSigned) { + const verifyCommands = []; + for (const key of Object.keys(inpBuildOutputs)) { + const output = JSON.parse(inpBuildOutputs[key]); + if (output.verifyCommands) { + verifyCommands.push(output.verifyCommands); + } } + core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); } - core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index d71bcdb..11a9789 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -31,33 +31,40 @@ jobs: const cosignVerifyCommands = builderOutputs['cosign-verify-commands']; const artifactName = builderOutputs['artifact-name']; const outputType = builderOutputs['output-type']; - if (!cosignVersion || !cosignVerifyCommands || !artifactName || !outputType) { - throw new Error('Missing required build outputs for verification'); + const signed = builderOutputs['signed'] === 'true'; + if (!signed) { + core.warning('No signatures to verify, skipping verification steps'); + } else if (!cosignVersion || !cosignVerifyCommands || !outputType || (outputType === 'local' && !artifactName)) { + core.setFailed('Missing required builder outputs for signature verification'); + return; } core.setOutput('cosign-version', cosignVersion); core.setOutput('cosign-verify-commands', cosignVerifyCommands); core.setOutput('artifact-name', artifactName); core.setOutput('output-type', outputType); + core.setOutput('signed', signed); - name: Install Cosign + if: ${{ steps.vars.outputs.signed == 'true' }} uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: cosign-release: ${{ steps.vars.outputs.cosign-version }} - name: Login to registry - if: ${{ steps.vars.outputs.output-type == 'image' }} + if: ${{ steps.vars.outputs.signed == 'true' && steps.vars.outputs.output-type == 'image' }} uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry-auth: ${{ secrets.registry-auths }} - name: Download artifacts - if: ${{ steps.vars.outputs.output-type == 'local' }} + if: ${{ steps.vars.outputs.signed == 'true' && steps.vars.outputs.output-type == 'local' }} uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ steps.vars.outputs.artifact-name }} - name: Verify signatures + if: ${{ steps.vars.outputs.signed == 'true' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERIFY-COMMANDS: ${{ steps.vars.outputs.cosign-verify-commands }} diff --git a/README.md b/README.md index ce8d62e..09c7406 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ ___ * [Isolation & Reliability](#isolation--reliability) * [Usage](#usage) * [Build reusable workflow](#build-reusable-workflow) + * [Inputs](#inputs) + * [Secrets](#secrets) * [Bake reusable workflow](#bake-reusable-workflow) ## Overview @@ -25,8 +27,9 @@ ___ This repository provides official Docker-maintained [reusable GitHub Actions workflows](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows) to securely build container images and artifacts using Docker best practices. The reusable workflows incorporate functionality from our GitHub Actions like -`build-push-action`, `login-action`, `metadata-action`, etc., into a single -workflow: +[`docker/build-push-action`](https://github.com/docker/build-push-action/), +[`docker/metadata-action`](https://github.com/docker/metadata-action/), etc., +into a single workflow: ```yaml name: ci @@ -145,6 +148,14 @@ toward higher levels of security and trust. ### Build reusable workflow +The [`build.yml` reusable workflow](.github/workflows/build.yml) lets you build +container images and artifacts from a Dockerfile with a user experience similar +to [`docker/build-push-action`](https://github.com/docker/build-push-action/). +It provides a Docker-maintained, opinionated build pipeline that applies best +practices for security, performance, and reliability by default, including +isolated execution and signed SLSA provenance while keeping per-repository +configuration minimal. + ```yaml name: ci @@ -167,12 +178,12 @@ on: with: output: image push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 meta-images: name/app meta-tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: docker.io @@ -196,7 +207,56 @@ on: password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -You can find the list of available inputs in [`.github/workflows/build.yml`](.github/workflows/build.yml). +#### Inputs + +> [!NOTE] +> `List` type is a newline-delimited string +> ```yaml +> cache-from: | +> user/app:cache +> type=local,src=path/to/dir +> ``` +> +> `CSV` type is a comma-delimited string +> ```yaml +> tags: name/app:latest,name/app:1.0.0 +> ``` + +| Name | Type | Default | Description | +|------------------------|-------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | `auto` | [Ubuntu GitHub Hosted Runner](https://github.com/actions/runner-images?tab=readme-ov-file#available-images) to build on (one of `auto`, `amd64`, `arm64`). The `auto` runner selects the best-matching runner based on target `platforms`. You can set it to `amd64` if your build doesn't require emulation (e.g. cross-compilation) | +| `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | +| `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | +| `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | +| `annotations` | List | | List of annotations to set to the image (for `image` output) | +| `build-args` | List | `auto` | List of [build-time variables](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-arg). If you want to set a build-arg through an environment variable, use the `envs` input | +| `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | +| `cache-scope` | String | target name or `buildkit` | Which [scope cache object belongs to](https://docs.docker.com/build/cache/backends/gha/#scope) if `cache` is enabled. This is the cache blob prefix name used when pushing cache to GitHub Actions cache backend | +| `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps | +| `context` | String | `.` | Context to build from in the Git working tree | +| `file` | String | `{context}/Dockerfile` | Path to the Dockerfile | +| `labels` | List | | List of labels for an image (for `image` output) | +| `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). Unlike the `build-push-action`, it only accepts `image` or `local`. The reusable workflow takes care of setting the `outputs` attribute | +| `platforms` | List/CSV | | List of [target platforms](https://docs.docker.com/engine/reference/commandline/buildx_build/#platform) to build | +| `push` | Bool | `false` | [Push](https://docs.docker.com/engine/reference/commandline/buildx_build/#push) image to the registry (for `image` output) | +| `sbom` | Bool/String | | Generate [SBOM](https://docs.docker.com/build/attestations/sbom/) attestation for the build | +| `shm-size` | String | | Size of [`/dev/shm`](https://docs.docker.com/engine/reference/commandline/buildx_build/#shm-size) (e.g., `2g`) | +| `sign` | String | `auto` | Sign attestation manifest for `image` output or artifacts for `local` output, can be one of `auto`, `true` or `false`. The `auto` mode will enable signing if `push` is enabled for pushing the `image` or if `artifact-upload` is enabled for uploading the `local` build output as GitHub Artifact | +| `target` | String | | Sets the target stage to build | +| `ulimit` | List | | [Ulimit](https://docs.docker.com/engine/reference/commandline/buildx_build/#ulimit) options (e.g., `nofile=1024:1024`) | +| `set-meta-annotations` | Bool | `false` | Append OCI Image Format Specification annotations generated by `docker/metadata-action` | +| `set-meta-labels` | Bool | `false` | Append OCI Image Format Specification labels generated by `docker/metadata-action` | +| `meta-images` | List | | [List of images](https://github.com/docker/metadata-action?tab=readme-ov-file#images-input) to use as base name for tags (required for image output) | +| `meta-tags` | List | `auto` | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | +| `meta-flavor` | List | `auto` | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | + +#### Secrets + +| Name | Default | Description | +|------------------|-----------------------|--------------------------------------------------------------------------------| +| `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | ### Bake reusable workflow @@ -250,4 +310,5 @@ on: password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -You can find the list of available inputs in [`.github/workflows/bake.yml`](.github/workflows/bake.yml). +> [!TIP] +> You can find the list of available inputs in [`.github/workflows/bake.yml`](.github/workflows/bake.yml). From 7eeaea087bdbb303d5c43a61047656d30dd01d12 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:38:52 +0100 Subject: [PATCH 2/6] build: remove gha cache Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test.yml | 5 ----- .github/workflows/build.yml | 33 +++++---------------------------- README.md | 9 --------- 3 files changed, 5 insertions(+), 42 deletions(-) diff --git a/.github/workflows/.test.yml b/.github/workflows/.test.yml index f1c2666..8583a8f 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test.yml @@ -21,7 +21,6 @@ jobs: contents: read id-token: write with: - cache: true file: test/hello.Dockerfile output: image push: ${{ github.event_name != 'pull_request' }} @@ -71,8 +70,6 @@ jobs: contents: read id-token: write with: - cache: true - cache-scope: build-aws file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 @@ -122,8 +119,6 @@ jobs: contents: read id-token: write with: - cache: true - cache-scope: build-aws file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f48572..fab9004 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,20 +35,6 @@ on: type: string description: "List of build-time variables. If you want to set a build-arg through an environment variable, use the envs input" required: false - cache: - type: boolean - description: "Enable cache to GitHub Actions cache backend" - required: false - default: false - cache-scope: - type: string - description: "Which scope cache object belongs to if cache enabled (defaults to target name if set)" - required: false - cache-mode: - type: string - description: "Cache layers to export if cache enabled (min or max)" - required: false - default: 'min' context: type: string description: "Context to build from in the Git working tree" @@ -319,6 +305,8 @@ jobs: name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} + with: + cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -326,6 +314,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Prepare id: prepare @@ -334,9 +323,6 @@ jobs: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} INPUT_ANNOTATIONS: ${{ inputs.annotations }} - INPUT_CACHE: ${{ inputs.cache }} - INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} - INPUT_CACHE-MODE: ${{ inputs.cache-mode }} INPUT_LABELS: ${{ inputs.labels }} INPUT_CONTEXT: ${{ inputs.context }} INPUT_OUTPUT: ${{ inputs.output }} @@ -357,9 +343,6 @@ jobs: const inpLocalExportDir = core.getInput('local-export-dir'); const inpAnnotations = core.getMultilineInput('annotations'); - const inpCache = core.getBooleanInput('cache'); - const inpCacheScope = core.getInput('cache-scope'); - const inpCacheMode = core.getInput('cache-mode'); const inpContext = core.getInput('context'); const inpLabels = core.getMultilineInput('labels'); const inpOutput = core.getInput('output'); @@ -395,11 +378,6 @@ jobs: core.setOutput('platform', inpPlatform); } - if (inpCache) { - core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix}`); - core.setOutput('cache-to', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); - } - if (inpSetMetaAnnotations && inpMetaAnnotations.length > 0) { inpAnnotations.push(...inpMetaAnnotations); } @@ -431,8 +409,6 @@ jobs: with: annotations: ${{ steps.prepare.outputs.annotations }} build-args: ${{ inputs.build-args }} - cache-from: ${{ steps.prepare.outputs.cache-from }} - cache-to: ${{ steps.prepare.outputs.cache-to }} context: ${{ steps.prepare.outputs.context }} file: ${{ inputs.file }} labels: ${{ steps.prepare.outputs.labels }} @@ -459,7 +435,7 @@ jobs: const { Install } = require('@docker/actions-toolkit/lib/cosign/install'); const cosignInstall = new Install(); - const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), false, true); + const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), true, true); await cosignInstall.install(cosignBinPath); const cosign = new Cosign(); @@ -624,6 +600,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Create manifest if: ${{ inputs.output == 'image' }} diff --git a/README.md b/README.md index 09c7406..86133f1 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,6 @@ toward higher levels of security and trust. requiring emulation or [custom CI logic](https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners) or self-managed runners. -* **Optimized cache warming & reuse.** - The builder can use the GitHub Actions cache backend to persist layers across - branches, PRs, and rebuilds. This significantly reduces cold-start times and - avoids repeating expensive dependency installations, even for external - contributors' pull requests. - * **Centralized build configuration.** Repositories no longer need to configure buildx drivers, tune storage, or adjust resource limits. The reusable workflows encapsulate the recommended @@ -231,9 +225,6 @@ on: | `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | | `annotations` | List | | List of annotations to set to the image (for `image` output) | | `build-args` | List | `auto` | List of [build-time variables](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-arg). If you want to set a build-arg through an environment variable, use the `envs` input | -| `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | -| `cache-scope` | String | target name or `buildkit` | Which [scope cache object belongs to](https://docs.docker.com/build/cache/backends/gha/#scope) if `cache` is enabled. This is the cache blob prefix name used when pushing cache to GitHub Actions cache backend | -| `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps | | `context` | String | `.` | Context to build from in the Git working tree | | `file` | String | `{context}/Dockerfile` | Path to the Dockerfile | | `labels` | List | | List of labels for an image (for `image` output) | From d90f277b51d7c32ad261816afd006ed405906633 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:31:09 +0100 Subject: [PATCH 3/6] ci: split build and bake tests Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 275 ++++++++++++++++++ .../workflows/{.test.yml => .test-build.yml} | 261 +---------------- 2 files changed, 284 insertions(+), 252 deletions(-) create mode 100644 .github/workflows/.test-bake.yml rename .github/workflows/{.test.yml => .test-build.yml} (65%) diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml new file mode 100644 index 0000000..11ee09c --- /dev/null +++ b/.github/workflows/.test-bake.yml @@ -0,0 +1,275 @@ +name: .test-bake + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - 'main' + - 'releases/v*' + tags: + - 'v*' + paths: + - '.github/workflows/.test-bake.yml' + - '.github/workflows/verify.yml' + - 'test/**' + pull_request: + paths: + - '.github/workflows/.test-bake.yml' + - '.github/workflows/verify.yml' + - 'test/**' + +jobs: + bake-aws-single: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + target: hello + output: image + push: ${{ github.event_name != 'pull_request' }} + cache: true + cache-scope: bake-aws + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-single-${{ github.run_id }} + bake-sbom: true + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-single-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws-single + with: + builder-outputs: ${{ toJSON(needs.bake-aws-single.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-single-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws-single + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-single.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + target: hello-cross + output: image + push: ${{ github.event_name != 'pull_request' }} + cache: true + cache-scope: bake-aws + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} + bake-sbom: true + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws + with: + builder-outputs: ${{ toJSON(needs.bake-aws.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-ghcr-and-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + packages: write + with: + context: test + target: hello-cross + output: image + push: ${{ github.event_name != 'pull_request' }} + cache: true + cache-scope: bake-aws + meta-images: | + ghcr.io/docker/github-builder-test + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=${{ github.run_id }},prefix=bake-ghcr-and-aws- + bake-sbom: true + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-ghcr-and-aws-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-ghcr-and-aws + with: + builder-outputs: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-ghcr-and-aws-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-ghcr-and-aws + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + target: hello-cross + output: local + push: ${{ github.event_name != 'pull_request' }} + cache: true + artifact-name: bake-output + bake-sbom: true + + bake-local-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-local + with: + builder-outputs: ${{ toJSON(needs.bake-local.outputs) }} + + bake-local-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-single: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + target: hello + output: local + push: ${{ github.event_name != 'pull_request' }} + cache: true + artifact-name: bake-single-output + bake-sbom: true + + bake-local-single-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-local-single + with: + builder-outputs: ${{ toJSON(needs.bake-local-single.outputs) }} + + bake-local-single-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-single + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-single.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-set-runner: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + runner: amd64 + context: test + target: hello-cross + output: image + push: false + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} diff --git a/.github/workflows/.test.yml b/.github/workflows/.test-build.yml similarity index 65% rename from .github/workflows/.test.yml rename to .github/workflows/.test-build.yml index 8583a8f..0bd6e0c 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test-build.yml @@ -1,4 +1,4 @@ -name: .test +name: .test-build concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -12,7 +12,15 @@ on: - 'releases/v*' tags: - 'v*' + paths: + - '.github/workflows/.test-build.yml' + - '.github/workflows/verify.yml' + - 'test/**' pull_request: + paths: + - '.github/workflows/.test-build.yml' + - '.github/workflows/verify.yml' + - 'test/**' jobs: build-aws-single: @@ -474,254 +482,3 @@ jobs: meta-images: ghcr.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - - bake-aws-single: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello - output: image - push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws - meta-images: | - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=bake-ghbuilder-single-${{ github.run_id }} - bake-sbom: true - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-single-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-aws-single - with: - builder-outputs: ${{ toJSON(needs.bake-aws-single.outputs) }} - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-single-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-aws-single - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-single.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-aws: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello-cross - output: image - push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws - meta-images: | - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=bake-ghbuilder-${{ github.run_id }} - bake-sbom: true - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-aws - with: - builder-outputs: ${{ toJSON(needs.bake-aws.outputs) }} - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-aws - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-ghcr-and-aws: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - packages: write - with: - context: test - target: hello-cross - output: image - push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws - meta-images: | - ghcr.io/docker/github-builder-test - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=${{ github.run_id }},prefix=bake-ghcr-and-aws- - bake-sbom: true - secrets: - registry-auths: | - - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-ghcr-and-aws-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-ghcr-and-aws - with: - builder-outputs: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} - secrets: - registry-auths: | - - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-ghcr-and-aws-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-ghcr-and-aws - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-local: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello-cross - output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-output - bake-sbom: true - - bake-local-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-local - with: - builder-outputs: ${{ toJSON(needs.bake-local.outputs) }} - - bake-local-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-local - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-local-single: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello - output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-single-output - bake-sbom: true - - bake-local-single-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-local-single - with: - builder-outputs: ${{ toJSON(needs.bake-local-single.outputs) }} - - bake-local-single-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-local-single - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-single.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-set-runner: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - runner: amd64 - context: test - target: hello-cross - output: image - push: false - meta-images: | - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=bake-ghbuilder-${{ github.run_id }} From e9735095a64d2a81ecd5594b5fdd4f74fad3d8ef Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:43:35 +0100 Subject: [PATCH 4/6] bake: refactor inputs Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 172 +++++++++++++-- .github/workflows/bake.yml | 366 +++++++++++++++++-------------- README.md | 63 +++++- 3 files changed, 408 insertions(+), 193 deletions(-) diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 11ee09c..9ff68f7 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -29,17 +29,17 @@ jobs: contents: read id-token: write with: + cache: true + cache-scope: bake-aws context: test - target: hello output: image push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws + sbom: true + target: hello meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=bake-ghbuilder-single-${{ github.run_id }} - bake-sbom: true secrets: registry-auths: | - registry: public.ecr.aws @@ -80,17 +80,17 @@ jobs: contents: read id-token: write with: + cache: true + cache-scope: bake-aws context: test - target: hello-cross output: image push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws + sbom: true + target: hello-cross meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=bake-ghbuilder-${{ github.run_id }} - bake-sbom: true secrets: registry-auths: | - registry: public.ecr.aws @@ -125,25 +125,77 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); - bake-ghcr-and-aws: + bake-aws-nosign: uses: ./.github/workflows/bake.yml permissions: contents: read id-token: write - packages: write with: + cache: true + cache-scope: bake-aws-nosign context: test - target: hello-cross output: image push: ${{ github.event_name != 'pull_request' }} + sbom: true + sign: false + target: hello-cross + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-nosign-${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-nosign-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws-nosign + with: + builder-outputs: ${{ toJSON(needs.bake-aws-nosign.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-ghcr-and-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + packages: write + with: cache: true cache-scope: bake-aws + context: test + output: image + push: ${{ github.event_name != 'pull_request' }} + sbom: true + target: hello-cross meta-images: | ghcr.io/docker/github-builder-test public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=${{ github.run_id }},prefix=bake-ghcr-and-aws- - bake-sbom: true secrets: registry-auths: | - registry: ghcr.io @@ -190,13 +242,14 @@ jobs: contents: read id-token: write with: + artifact-name: bake-output + artifact-upload: true + cache: true context: test - target: hello-cross output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-output - bake-sbom: true + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello-cross bake-local-verify: uses: ./.github/workflows/verify.yml @@ -227,13 +280,14 @@ jobs: contents: read id-token: write with: + artifact-name: bake-single-output + artifact-upload: true + cache: true context: test - target: hello output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-single-output - bake-sbom: true + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello bake-local-single-verify: uses: ./.github/workflows/verify.yml @@ -258,6 +312,78 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + bake-local-noupload: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-upload: false + cache: true + context: test + output: local + sbom: true + target: hello-cross + + bake-local-noupload-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-noupload + with: + builder-outputs: ${{ toJSON(needs.bake-local-noupload.outputs) }} + + bake-local-noupload-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-noupload + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-noupload.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-nosign: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-nosign-output + artifact-upload: true + cache: true + context: test + output: local + sbom: true + sign: false + target: hello-cross + + bake-local-nosign-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-nosign + with: + builder-outputs: ${{ toJSON(needs.bake-local-nosign.outputs) }} + + build-local-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + bake-set-runner: uses: ./.github/workflows/bake.yml permissions: @@ -266,9 +392,9 @@ jobs: with: runner: amd64 context: test - target: hello-cross output: image push: false + target: hello-cross meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 07e9645..70e6e39 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -5,37 +5,28 @@ on: inputs: runner: type: string - description: "Linux machine to run build on. Can be one of auto, amd64, arm64 (defaults to auto which selects best-matching runner based on target platform)" + description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)" required: false default: 'auto' - context: - type: string - description: "Context to build from (defaults to repository root)" + setup-qemu: + type: boolean + description: "Runs the setup-qemu-action step to install QEMU static binaries" required: false - default: . - target: - type: string - description: "Target to build" - required: true - default: default - output: - type: string - description: "Build output destination (image or local)" - required: true + default: false artifact-name: type: string - description: "Name of the uploaded artifact (for local output)" + description: "Name of the uploaded GitHub artifact (for local output)" required: false default: 'docker-github-builder-assets' - envs: - type: string - description: "Environment variables to set" - required: false - push: + artifact-upload: type: boolean - description: "Push image to the registry (for image output) and/or sign attestation manifests or local artifacts" + description: "Upload build output GitHub artifact (for local output)" required: false default: false + envs: + type: string + description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" + required: false cache: type: boolean description: "Enable cache to GitHub Actions cache backend" @@ -50,25 +41,56 @@ on: description: "Cache layers to export if cache enabled (min or max)" required: false default: 'min' - set-meta-annotations: + context: + type: string + description: "Context to build from in the Git working tree" + required: false + default: . + files: + type: string + description: "List of bake definition files" + required: false + output: + type: string + description: "Build output destination (one of image or local). Unlike the build-push-action, it only accepts image or local. The reusable workflow takes care of setting the outputs attribute" + required: true + push: type: boolean - description: "Set metadata-action annotations" + description: "Push image to the registry (for image output)" required: false default: false - set-meta-labels: + sbom: + type: string + description: "Generate SBOM attestation for the build" + required: false + set: + type: string + description: "List of targets values to override (eg. targetpattern.key=value)" + required: false + sign: + type: string + description: "Sign attestation manifest for image output or artifacts for local output, can be one of auto, true or false. The auto mode will enable signing if push is enabled for pushing the image or if artifact-upload is enabled for uploading the local build output as GitHub Artifact" + required: false + default: auto + target: + type: string + description: "Bake target to build" + required: true + default: default + # docker/metadata-action + set-meta-annotations: type: boolean - description: "Set metadata-action labels" + description: "Append OCI Image Format Specification annotations generated by docker/metadata-action" required: false default: false - setup-qemu: + set-meta-labels: type: boolean - description: "Install QEMU static binaries" + description: "Append OCI Image Format Specification labels generated by docker/metadata-action" required: false default: false - # same as docker/metadata-action inputs (minus sep-tags, sep-labels, sep-annotations, bake-target) meta-images: type: string - description: "List of images to use as base name for tags" + description: "List of images to use as base name for tags (required for image output)" required: false meta-tags: type: string @@ -76,7 +98,7 @@ on: required: false meta-flavor: type: string - description: "Flavors to apply" + description: "Flavor defines a global behavior for meta-tags" required: false meta-labels: type: string @@ -90,43 +112,16 @@ on: type: string description: "Bake target name for metadata (defaults to docker-metadata-action)" required: false - # same as docker/setup-qemu-action inputs (minus platforms, cache-image) - qemu-image: - type: string - description: "QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)" - required: false - # same as docker/bake-action inputs - bake-allow: - type: string - description: "Allow build to access specified resources (e.g., network.host)" - required: false - bake-files: - type: string - description: "List of bake definition files (defaults to docker-bake.hcl)" - required: false - bake-pull: - type: boolean - description: "Always attempt to pull all referenced images" - required: false - default: false - bake-sbom: - type: string - description: "SBOM is a shorthand for --set=*.attest=type=sbom" - required: false - bake-set: - type: string - description: "List of targets values to override (eg. targetpattern.key=value)" - required: false secrets: registry-auths: - description: "Registry authentication details as YAML objects" + description: "Raw authentication to registries, defined as YAML objects (for image output)" required: false github-token: - description: "GitHub Token used to authenticate against a repository for Git context" + description: "GitHub Token used to authenticate against the repository for Git context" required: false outputs: meta-json: - description: "Metadata JSON output (only for image output)" + description: "Metadata JSON output (for image output)" value: ${{ jobs.finalize.outputs.meta-json }} cosign-version: description: "Cosign version used for verification" @@ -140,6 +135,9 @@ on: output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} + signed: + description: "Whether attestations manifests or artifacts were signed" + value: ${{ jobs.finalize.outputs.signed }} env: BUILDX_VERSION: "v0.30.1" @@ -154,6 +152,7 @@ jobs: runs-on: ubuntu-24.04 outputs: includes: ${{ steps.set.outputs.includes }} + sign: ${{ steps.set.outputs.sign }} steps: - name: Environment variables @@ -176,19 +175,21 @@ jobs: script: | await exec.exec('npm', ['install', '--prefer-offline', '--no-audit', '--ignore-scripts', core.getInput('dat-module')]); - - name: Set includes + name: Set outputs id: set uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_MATRIX-SIZE-LIMIT: ${{ env.MATRIX_SIZE_LIMIT }} INPUT_RUNNER: ${{ inputs.runner }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} INPUT_CONTEXT: ${{ inputs.context }} + INPUT_FILES: ${{ inputs.files }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_SBOM: ${{ inputs.sbom }} + INPUT_SET: ${{ inputs.set }} + INPUT_SIGN: ${{ inputs.sign }} INPUT_TARGET: ${{ inputs.target }} - INPUT_BAKE-ALLOW: ${{ inputs.bake-allow }} - INPUT_BAKE-FILES: ${{ inputs.bake-files }} - INPUT_BAKE-PULL: ${{ inputs.bake-pull }} - INPUT_BAKE-SBOM: ${{ inputs.bake-sbom }} - INPUT_BAKE-SET: ${{ inputs.bake-set }} INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }} with: script: | @@ -200,13 +201,15 @@ jobs: const inpMatrixSizeLimit = parseInt(core.getInput('matrix-size-limit'), 10); const inpRunner = core.getInput('runner'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpContext = core.getInput('context'); + const inpFiles = Util.getInputList('files'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpSbom = core.getInput('sbom'); + const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); + const inpSign = core.getInput('sign'); const inpTarget = core.getInput('target'); - const inpBakeAllow = core.getInput('bake-allow'); - const inpBakeFiles = Util.getInputList('bake-files'); - const inpBakePull = core.getBooleanInput('bake-pull'); - const inpBakeSbom = core.getInput('bake-sbom'); - const inpBakeSet = Util.getInputList('bake-set', {ignoreComma: true, quote: false}); const inpGitHubToken = core.getInput('github-token'); let runner = inpRunner; @@ -215,7 +218,23 @@ jobs: } else if (inpRunner === 'arm64') { runner = 'ubuntu-24.04-arm'; } else if (inpRunner !== 'auto') { - throw new Error(`Invalid runner input: ${inpRunner}`); + core.setFailed(`Invalid runner input: ${inpRunner}`); + return; + } + + const sign = + inpSign === 'auto' + ? (inpOutput === 'image' && inpPush) || (inpOutput === 'local' && inpArtifactUpload) + : inpSign === 'true'; + + if (inpOutput === 'local' && inpPush) { + core.warning(`push is ignored when output is local`); + } else if (inpOutput === 'image' && inpArtifactUpload) { + core.warning(`artifact-upload is ignored when output is image`); + } + if (inpOutput === 'image' && !inpPush && sign) { + core.setFailed(`signing attestation manifests requires push to be enabled`); + return; } const bakeSource = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; @@ -225,50 +244,63 @@ jobs: let def; let target; - await core.group(`Validating definition`, async () => { - const bake = new Bake(); - def = await bake.getDefinition({ - allow: inpBakeAllow, - files: inpBakeFiles, - overrides: inpBakeSet, - sbom: inpBakeSbom, - source: bakeSource, - targets: [inpTarget], - githubToken: inpGitHubToken + try { + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + def = await bake.getDefinition({ + files: inpFiles, + overrides: inpSet, + sbom: inpSbom, + source: bakeSource, + targets: [inpTarget], + githubToken: inpGitHubToken + }); + if (!def) { + throw new Error('Bake definition not set'); + } + const targets = Object.keys(def.target); + if (targets.length > 1) { + throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); + } + target = targets[0]; }); - if (!def) { - throw new Error('Bake definition not set'); - } - const targets = Object.keys(def.target); - if (targets.length > 1) { - throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); - } - target = targets[0]; - }); + } catch (error) { + core.setFailed(error); + return; + } + + const platforms = def.target[target].platforms || []; + if (platforms.length > inpMatrixSizeLimit) { + core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); + return; + } const privateRepo = GitHub.context.payload.repository?.private ?? false; - await core.group(`Set includes`, async () => { + await core.group(`Set includes output`, async () => { let includes = []; - const platforms = def.target[target].platforms || []; - if (platforms.length > inpMatrixSizeLimit) { - throw new Error(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); - } else if (platforms.length === 0) { + if (platforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: runner === 'auto' ? 'ubuntu-24.04' : runner, + sign: sign }); } else { platforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner, + sign: sign }); }); } core.info(JSON.stringify(includes, null, 2)); core.setOutput('includes', JSON.stringify(includes)); }); + await core.group(`Set sign output`, async () => { + core.info(`sign: ${sign}`); + core.setOutput('sign', sign); + }); build: runs-on: ${{ matrix.runner }} @@ -338,8 +370,6 @@ jobs: name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} - with: - image: ${{ inputs.qemu-image }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -354,24 +384,22 @@ jobs: env: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} - INPUT_CONTEXT: ${{ inputs.context }} - INPUT_TARGET: ${{ inputs.target }} - INPUT_OUTPUT: ${{ inputs.output }} - INPUT_PUSH: ${{ inputs.push }} INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_CONTEXT: ${{ inputs.context }} + INPUT_FILES: ${{ inputs.files }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_SBOM: ${{ inputs.sbom }} + INPUT_SET: ${{ inputs.set }} + INPUT_TARGET: ${{ inputs.target }} INPUT_META-IMAGES: ${{ inputs.meta-images }} INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} INPUT_BAKE-FILE-TAGS: ${{ steps.meta.outputs.bake-file-tags }} INPUT_BAKE-FILE-ANNOTATIONS: ${{ steps.meta.outputs.bake-file-annotations }} INPUT_BAKE-FILE-LABELS: ${{ steps.meta.outputs.bake-file-labels }} - INPUT_BAKE-ALLOW: ${{ inputs.bake-allow }} - INPUT_BAKE-FILES: ${{ inputs.bake-files }} - INPUT_BAKE-PULL: ${{ inputs.bake-pull }} - INPUT_BAKE-SBOM: ${{ inputs.bake-sbom }} - INPUT_BAKE-SET: ${{ inputs.bake-set }} INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }} with: script: | @@ -386,25 +414,22 @@ jobs: const inpLocalExportDir = core.getInput('local-export-dir'); - const inpContext = core.getInput('context'); - const inpTarget = core.getInput('target'); - const inpOutput = core.getInput('output'); - const inpPush = core.getBooleanInput('push'); const inpCache = core.getBooleanInput('cache'); const inpCacheScope = core.getInput('cache-scope'); const inpCacheMode = core.getInput('cache-mode'); + const inpContext = core.getInput('context'); + const inpFiles = Util.getInputList('files'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpSbom = core.getInput('sbom'); + const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); + const inpTarget = core.getInput('target'); const inpMetaImages = core.getMultilineInput('meta-images'); const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); const inpBakeFileTags = core.getInput('bake-file-tags'); const inpBakeFileAnnotations = core.getInput('bake-file-annotations'); const inpBakeFileLabels = core.getInput('bake-file-labels'); - - const inpBakeAllow = core.getInput('bake-allow'); - const inpBakeFiles = Util.getInputList('bake-files'); - const inpBakePull = core.getBooleanInput('bake-pull'); - const inpBakeSbom = core.getInput('bake-sbom'); - const inpBakeSet = Util.getInputList('bake-set', {ignoreComma: true, quote: false}); const inpGitHubToken = core.getInput('github-token'); const bakeSource = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; @@ -414,29 +439,33 @@ jobs: }); let target; - await core.group(`Validating definition`, async () => { - const bake = new Bake(); - const def = await bake.getDefinition({ - allow: inpBakeAllow, - files: inpBakeFiles, - overrides: inpBakeSet, - sbom: inpBakeSbom, - source: bakeSource, - targets: [inpTarget], - githubToken: inpGitHubToken + try { + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + const def = await bake.getDefinition({ + files: inpFiles, + overrides: inpSet, + sbom: inpSbom, + source: bakeSource, + targets: [inpTarget], + githubToken: inpGitHubToken + }); + if (!def) { + throw new Error('Bake definition not set'); + } + const targets = Object.keys(def.target); + if (targets.length > 1) { + throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); + } + target = targets[0]; + core.setOutput('target', target); }); - if (!def) { - throw new Error('Bake definition not set'); - } - const targets = Object.keys(def.target); - if (targets.length > 1) { - throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); - } - target = targets[0]; - core.setOutput('target', target); - }); + } catch (error) { + core.setFailed(error); + return; + } - let bakeFiles = inpBakeFiles; + let bakeFiles = inpFiles; await core.group(`Set bake files`, async () => { if (bakeFiles.length === 0) { bakeFiles = ['docker-bake.hcl']; @@ -459,6 +488,7 @@ jobs: case 'image': if (inpMetaImages.length == 0) { core.setFailed('meta-images is required when output is image'); + return; } outputOverride = `*.output=type=image,"name=${inpMetaImages.join(',')}",oci-artifact=true,push-by-digest=true,name-canonical=true,push=${inpPush}`; break; @@ -467,9 +497,10 @@ jobs: break; default: core.setFailed(`Invalid output: ${inpOutput}`); + return; } - let bakeOverrides = [...inpBakeSet, outputOverride]; + let bakeOverrides = [...inpSet, outputOverride]; await core.group(`Set bake overrides`, async () => { bakeOverrides.push('*.tags='); if (GitHub.context.payload.repository?.private ?? false) { @@ -504,9 +535,7 @@ jobs: source: ${{ steps.prepare.outputs.source }} files: ${{ steps.prepare.outputs.files }} targets: ${{ steps.prepare.outputs.target }} - allow: ${{ inputs.bake-allow }} - pull: ${{ inputs.bake-pull }} - sbom: ${{ inputs.bake-sbom }} + sbom: ${{ inputs.sbom }} set: ${{ steps.prepare.outputs.overrides }} env: BUILDKIT_MULTI_PLATFORM: 1 @@ -528,7 +557,7 @@ jobs: core.setOutput('digest', imageDigest); - name: Install Cosign - if: ${{ inputs.push }} + if: ${{ needs.prepare.outputs.sign }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERSION: ${{ env.COSIGN_VERSION }} @@ -546,7 +575,7 @@ jobs: - name: Signing attestation manifests id: signing-attestation-manifests - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ matrix.sign && inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} @@ -593,7 +622,7 @@ jobs: - name: Signing local artifacts id: signing-local-artifacts - if: ${{ inputs.push && inputs.output == 'local' }} + if: ${{ matrix.sign && inputs.output == 'local' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_LOCAL-OUTPUT-DIR: ${{ env.LOCAL_EXPORT_DIR }} @@ -629,7 +658,7 @@ jobs: tree -nh ${{ env.LOCAL_EXPORT_DIR }} - name: Upload artifact - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} @@ -644,17 +673,22 @@ jobs: INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.get-image-digest.outputs.digest }} INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_SIGNED: ${{ matrix.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); const inpArtifactName = core.getInput('artifact-name'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactName + artifactName: inpArtifactUpload ? inpArtifactName : '', + signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -666,9 +700,11 @@ jobs: meta-json: ${{ steps.meta.outputs.json }} cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} - artifact-name: ${{ inputs.artifact-name }} + artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} output-type: ${{ inputs.output }} + signed: ${{ needs.prepare.outputs.sign }} needs: + - prepare - build steps: - @@ -721,7 +757,8 @@ jobs: } } if (digests.length === 0) { - throw new Error('No image digests found from build outputs'); + core.setFailed('No image digests found from build outputs'); + return; } for (const imageName of inpImageNames) { @@ -733,13 +770,7 @@ jobs: createArgs.push(digest); } if (inpPush) { - await exec.getExecOutput('docker', createArgs, { - ignoreReturnCode: true - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr); - } - }); + await exec.exec('docker', createArgs); } else { await core.group(`Generated imagetools create command for ${imageName}`, async () => { core.info(`docker ${createArgs.join(' ')}`); @@ -748,7 +779,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} @@ -760,14 +791,19 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} + INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpBuildOutputs = JSON.parse(core.getInput('build-outputs')); - const verifyCommands = []; - for (const key of Object.keys(inpBuildOutputs)) { - const output = JSON.parse(inpBuildOutputs[key]); - if (output.verifyCommands) { - verifyCommands.push(output.verifyCommands); + const inpSigned = core.getBooleanInput('signed'); + + if (inpSigned) { + const verifyCommands = []; + for (const key of Object.keys(inpBuildOutputs)) { + const output = JSON.parse(inpBuildOutputs[key]); + if (output.verifyCommands) { + verifyCommands.push(output.verifyCommands); + } } + core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); } - core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); diff --git a/README.md b/README.md index 86133f1..311c281 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Test workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test.yml?label=test&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test) +[![Test build workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test-build.yml?label=test%20build&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test-build) +[![Test bake workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test-bake.yml?label=test%20bake&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test-bake) > [!CAUTION] > Do not use it for your production workflows yet! @@ -21,6 +22,8 @@ ___ * [Inputs](#inputs) * [Secrets](#secrets) * [Bake reusable workflow](#bake-reusable-workflow) + * [Inputs](#inputs) + * [Secrets](#secrets) ## Overview @@ -239,8 +242,8 @@ on: | `set-meta-annotations` | Bool | `false` | Append OCI Image Format Specification annotations generated by `docker/metadata-action` | | `set-meta-labels` | Bool | `false` | Append OCI Image Format Specification labels generated by `docker/metadata-action` | | `meta-images` | List | | [List of images](https://github.com/docker/metadata-action?tab=readme-ov-file#images-input) to use as base name for tags (required for image output) | -| `meta-tags` | List | `auto` | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | -| `meta-flavor` | List | `auto` | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | +| `meta-tags` | List | | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | +| `meta-flavor` | List | | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | #### Secrets @@ -251,6 +254,14 @@ on: ### Bake reusable workflow +The [`bake.yml` reusable workflow](.github/workflows/build.yml) lets you build +container images and artifacts from a [Bake definition](https://docs.docker.com/build/bake/) +with a user experience similar to [`docker/bake-action`](https://github.com/docker/bake-action/). +It provides a Docker-maintained, opinionated build pipeline that applies best +practices for security, performance, and reliability by default, including +isolated execution and signed SLSA provenance while keeping per-repository +configuration minimal. + ```yaml name: ci @@ -301,5 +312,47 @@ on: password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -> [!TIP] -> You can find the list of available inputs in [`.github/workflows/bake.yml`](.github/workflows/bake.yml). +#### Inputs + +> `List` type is a newline-delimited string +> ```yaml +> set: target.args.mybuildarg=value +> ``` +> ```yaml +> set: | +> target.args.mybuildarg=value +> foo*.args.mybuildarg=value +> ``` + +| Name | Type | Default | Description | +|------------------------|-------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | `auto` | [Ubuntu GitHub Hosted Runner](https://github.com/actions/runner-images?tab=readme-ov-file#available-images) to build on (one of `auto`, `amd64`, `arm64`). The `auto` runner selects the best-matching runner based on target `platforms`. You can set it to `amd64` if your build doesn't require emulation (e.g. cross-compilation) | +| `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | +| `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | +| `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | +| `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | +| `cache-scope` | String | target name or `buildkit` | Which [scope cache object belongs to](https://docs.docker.com/build/cache/backends/gha/#scope) if `cache` is enabled. This is the cache blob prefix name used when pushing cache to GitHub Actions cache backend | +| `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps | +| `context` | String | `.` | Context to build from in the Git working tree | +| `files` | List | `{context}/docker-bake.hcl` | List of bake definition files | +| `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). | +| `push` | Bool | `false` | Push image to the registry (for `image` output) | +| `sbom` | Bool/String | | Generate [SBOM](https://docs.docker.com/build/attestations/sbom/) attestation for the build | +| `set` | List | | List of [target values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) | +| `sign` | String | `auto` | Sign attestation manifest for `image` output or artifacts for `local` output, can be one of `auto`, `true` or `false`. The `auto` mode will enable signing if `push` is enabled for pushing the `image` or if `artifact-upload` is enabled for uploading the `local` build output as GitHub Artifact | +| `target` | String | `default` | Bake target to build | +| `set-meta-annotations` | Bool | `false` | Append OCI Image Format Specification annotations generated by `docker/metadata-action` | +| `set-meta-labels` | Bool | `false` | Append OCI Image Format Specification labels generated by `docker/metadata-action` | +| `meta-images` | List | | [List of images](https://github.com/docker/metadata-action?tab=readme-ov-file#images-input) to use as base name for tags (required for image output) | +| `meta-tags` | List | | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | +| `meta-labels` | List | | [List of custom labels](https://github.com/docker/metadata-action?tab=readme-ov-file#overwrite-labels-and-annotations) | +| `meta-annotations` | List | | [List of custom annotations](https://github.com/docker/metadata-action?tab=readme-ov-file#overwrite-labels-and-annotations) | +| `meta-flavor` | List | | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | + +#### Secrets + +| Name | Default | Description | +|------------------|-----------------------|--------------------------------------------------------------------------------| +| `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | From 6ef659875fa19af4b7243533261d0ce259f87424 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:56:55 +0100 Subject: [PATCH 5/6] bake: remove gha cache Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 12 ------------ .github/workflows/bake.yml | 30 +++++------------------------- README.md | 3 --- 3 files changed, 5 insertions(+), 40 deletions(-) diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 9ff68f7..203f01b 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -29,8 +29,6 @@ jobs: contents: read id-token: write with: - cache: true - cache-scope: bake-aws context: test output: image push: ${{ github.event_name != 'pull_request' }} @@ -80,8 +78,6 @@ jobs: contents: read id-token: write with: - cache: true - cache-scope: bake-aws context: test output: image push: ${{ github.event_name != 'pull_request' }} @@ -131,8 +127,6 @@ jobs: contents: read id-token: write with: - cache: true - cache-scope: bake-aws-nosign context: test output: image push: ${{ github.event_name != 'pull_request' }} @@ -184,8 +178,6 @@ jobs: id-token: write packages: write with: - cache: true - cache-scope: bake-aws context: test output: image push: ${{ github.event_name != 'pull_request' }} @@ -244,7 +236,6 @@ jobs: with: artifact-name: bake-output artifact-upload: true - cache: true context: test output: local sbom: true @@ -282,7 +273,6 @@ jobs: with: artifact-name: bake-single-output artifact-upload: true - cache: true context: test output: local sbom: true @@ -319,7 +309,6 @@ jobs: id-token: write with: artifact-upload: false - cache: true context: test output: local sbom: true @@ -355,7 +344,6 @@ jobs: with: artifact-name: bake-nosign-output artifact-upload: true - cache: true context: test output: local sbom: true diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 70e6e39..d8e61ee 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -27,20 +27,6 @@ on: type: string description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" required: false - cache: - type: boolean - description: "Enable cache to GitHub Actions cache backend" - required: false - default: false - cache-scope: - type: string - description: "Which scope cache object belongs to if cache enabled (defaults to target name)" - required: false - cache-mode: - type: string - description: "Cache layers to export if cache enabled (min or max)" - required: false - default: 'min' context: type: string description: "Context to build from in the Git working tree" @@ -370,6 +356,8 @@ jobs: name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} + with: + cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -377,6 +365,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Prepare id: prepare @@ -384,9 +373,6 @@ jobs: env: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} - INPUT_CACHE: ${{ inputs.cache }} - INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} - INPUT_CACHE-MODE: ${{ inputs.cache-mode }} INPUT_CONTEXT: ${{ inputs.context }} INPUT_FILES: ${{ inputs.files }} INPUT_OUTPUT: ${{ inputs.output }} @@ -414,9 +400,6 @@ jobs: const inpLocalExportDir = core.getInput('local-export-dir'); - const inpCache = core.getBooleanInput('cache'); - const inpCacheScope = core.getInput('cache-scope'); - const inpCacheMode = core.getInput('cache-mode'); const inpContext = core.getInput('context'); const inpFiles = Util.getInputList('files'); const inpOutput = core.getInput('output'); @@ -514,10 +497,6 @@ jobs: if (inpPlatform) { bakeOverrides.push(`*.platform=${inpPlatform}`); } - if (inpCache) { - bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || target}${platformPairSuffix}`); - bakeOverrides.push(`*.cache-to=type=gha,scope=${inpCacheScope || target}${platformPairSuffix},mode=${inpCacheMode}`); - } core.info(JSON.stringify(bakeOverrides, null, 2)); core.setOutput('overrides', bakeOverrides.join(os.EOL)); }); @@ -567,7 +546,7 @@ jobs: const { Install } = require('@docker/actions-toolkit/lib/cosign/install'); const cosignInstall = new Install(); - const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), false, true); + const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), true, true); await cosignInstall.install(cosignBinPath); const cosign = new Cosign(); @@ -733,6 +712,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Create manifest if: ${{ inputs.output == 'image' }} diff --git a/README.md b/README.md index 311c281..9332577 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,6 @@ on: | `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | | `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | | `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | -| `cache` | Bool | `false` | Enable [GitHub Actions cache](https://docs.docker.com/build/cache/backends/gha/) exporter | -| `cache-scope` | String | target name or `buildkit` | Which [scope cache object belongs to](https://docs.docker.com/build/cache/backends/gha/#scope) if `cache` is enabled. This is the cache blob prefix name used when pushing cache to GitHub Actions cache backend | -| `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps | | `context` | String | `.` | Context to build from in the Git working tree | | `files` | List | `{context}/docker-bake.hcl` | List of bake definition files | | `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). | From 6e46b8d4bcd2aefda166525d1e5c78eeb5d96d57 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:30:52 +0100 Subject: [PATCH 6/6] remove envs input Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/bake.yml | 28 ---------------------------- .github/workflows/build.yml | 18 +----------------- README.md | 2 -- 3 files changed, 1 insertion(+), 47 deletions(-) diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index d8e61ee..0553e31 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -23,10 +23,6 @@ on: description: "Upload build output GitHub artifact (for local output)" required: false default: false - envs: - type: string - description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" - required: false context: type: string description: "Context to build from in the Git working tree" @@ -140,18 +136,6 @@ jobs: includes: ${{ steps.set.outputs.includes }} sign: ${{ steps.set.outputs.sign }} steps: - - - name: Environment variables - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_ENVS: ${{ inputs.envs }} - with: - script: | - for (const env of core.getMultilineInput('envs')) { - core.info(env); - const [key, value] = env.split('=', 2); - core.exportVariable(key, value); - } - name: Install @docker/actions-toolkit uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 @@ -320,18 +304,6 @@ jobs: result_18: ${{ steps.result.outputs.result_18 }} result_19: ${{ steps.result.outputs.result_19 }} steps: - - - name: Environment variables - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_ENVS: ${{ inputs.envs }} - with: - script: | - for (const env of core.getMultilineInput('envs')) { - core.info(env); - const [key, value] = env.split('=', 2); - core.exportVariable(key, value); - } - name: Install @docker/actions-toolkit uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fab9004..33db9a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,17 +23,13 @@ on: description: "Upload build output GitHub artifact (for local output)" required: false default: false - envs: - type: string - description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" - required: false annotations: type: string description: "List of annotations to set to the image (for image output)" required: false build-args: type: string - description: "List of build-time variables. If you want to set a build-arg through an environment variable, use the envs input" + description: "List of build-time variables" required: false context: type: string @@ -270,18 +266,6 @@ jobs: result_18: ${{ steps.result.outputs.result_18 }} result_19: ${{ steps.result.outputs.result_19 }} steps: - - - name: Environment variables - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_ENVS: ${{ inputs.envs }} - with: - script: | - for (const env of core.getMultilineInput('envs')) { - core.info(env); - const [key, value] = env.split('=', 2); - core.exportVariable(key, value); - } - name: Install @docker/actions-toolkit uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 diff --git a/README.md b/README.md index 9332577..29482c9 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,6 @@ on: | `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | | `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | | `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | -| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | | `annotations` | List | | List of annotations to set to the image (for `image` output) | | `build-args` | List | `auto` | List of [build-time variables](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-arg). If you want to set a build-arg through an environment variable, use the `envs` input | | `context` | String | `.` | Context to build from in the Git working tree | @@ -330,7 +329,6 @@ on: | `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | | `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | | `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | -| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | | `context` | String | `.` | Context to build from in the Git working tree | | `files` | List | `{context}/docker-bake.hcl` | List of bake definition files | | `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). |