diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 08fe1c3..23a4e3d 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -90,6 +90,10 @@ jobs: context: test output: image push: ${{ github.event_name != 'pull_request' }} + runner: | + default=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm + linux/arm64=ubuntu-24.04-arm sbom: true set: | *.args.VERSION={{meta.version}} @@ -460,6 +464,24 @@ jobs: core.info(JSON.stringify(builderOutputs, null, 2)); bake-set-runner: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + runner: ubuntu-24.04 + context: test + output: image + push: false + set: | + *.args.VERSION={{meta.version}} + target: hello-cross + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} + + bake-set-runner-deprecated: uses: ./.github/workflows/bake.yml permissions: contents: read diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index e2994b7..7f74b8e 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -93,6 +93,10 @@ jobs: output: image platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + runner: | + default=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm + linux/arm64=ubuntu-24.04-arm sbom: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action @@ -509,6 +513,23 @@ jobs: core.info(JSON.stringify(builderOutputs, null, 2)); build-set-runner: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + runner: ubuntu-24.04 + build-args: | + VERSION={{meta.version}} + 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-set-runner-deprecated: uses: ./.github/workflows/build.yml permissions: contents: read diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 9a1f6bf..877c3e9 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -5,9 +5,12 @@ on: inputs: runner: type: string - 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)" + description: "GitHub-hosted runner label or platform mapping to build on" required: false - default: 'auto' + default: | + default=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm + linux/arm64=ubuntu-24.04-arm distribute: type: boolean description: "Whether to distribute the build across multiple runners (one platform per runner)" @@ -278,7 +281,7 @@ jobs: const inpActionsIdTokenSet = core.getBooleanInput('actions-id-token-set'); const inpMetaImages = core.getMultilineInput('meta-images'); - const inpRunner = core.getInput('runner'); + const inpRunner = core.getMultilineInput('runner'); const inpDistribute = core.getBooleanInput('distribute'); const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpContext = core.getInput('context'); @@ -292,15 +295,108 @@ jobs: const inpTarget = core.getInput('target'); const inpGitHubToken = core.getInput('github-token'); - let runner = inpRunner; - if (inpRunner === 'amd64') { - runner = 'ubuntu-24.04'; - } else if (inpRunner === 'arm64') { - runner = 'ubuntu-24.04-arm'; - } else if (inpRunner !== 'auto') { - core.setFailed(`Invalid runner input: ${inpRunner}`); + const parseRunnerConfig = value => { + const lines = value.map(line => line.trim()).filter(line => line.length > 0); + if (lines.length === 0) { + throw new Error('runner input cannot be empty'); + } + if (lines.length === 1 && !lines[0].includes('=')) { + if (lines[0] === 'auto') { + core.warning('The runner input value "auto" is deprecated; use a runner mapping with default=ubuntu-24.04, linux/arm=ubuntu-24.04-arm, and linux/arm64=ubuntu-24.04-arm instead'); + return { + defaultRunner: 'ubuntu-24.04', + rules: [ + {pattern: 'linux/arm', runner: 'ubuntu-24.04-arm'}, + {pattern: 'linux/arm64', runner: 'ubuntu-24.04-arm'} + ] + }; + } + if (lines[0] === 'amd64') { + core.warning('The runner input value "amd64" is deprecated; use runner=ubuntu-24.04 instead'); + return { + defaultRunner: 'ubuntu-24.04', + rules: [] + }; + } + if (lines[0] === 'arm64') { + core.warning('The runner input value "arm64" is deprecated; use runner=ubuntu-24.04-arm instead'); + return { + defaultRunner: 'ubuntu-24.04-arm', + rules: [] + }; + } + return { + defaultRunner: lines[0], + rules: [] + }; + } + const rules = []; + let defaultRunner; + for (const line of lines) { + const idx = line.indexOf('='); + if (idx === -1) { + throw new Error(`Invalid runner mapping: ${line}`); + } + const pattern = line.substring(0, idx).trim(); + const runner = line.substring(idx + 1).trim(); + if (!pattern) { + throw new Error('Runner mapping pattern cannot be empty'); + } + if (!runner) { + throw new Error(`Runner mapping value cannot be empty for ${pattern}`); + } + if (pattern === 'default') { + defaultRunner = runner; + continue; + } + if (pattern.split('/').some(part => part.length === 0)) { + throw new Error(`Runner mapping pattern is not a valid platform prefix: ${pattern}`); + } + rules.push({pattern, runner}); + } + if (!defaultRunner) { + throw new Error('Runner mapping must define a default runner'); + } + return { + defaultRunner, + rules + }; + }; + + const matchesPlatformPrefix = (pattern, platform) => { + const patternParts = pattern.split('/'); + const platformParts = platform.split('/'); + return patternParts.length <= platformParts.length && + patternParts.every((part, index) => part === platformParts[index]); + }; + + const resolveRunner = (runnerConfig, platform) => { + if (!platform) { + return runnerConfig.defaultRunner; + } + let match; + for (const rule of runnerConfig.rules) { + if (!matchesPlatformPrefix(rule.pattern, platform)) { + continue; + } + const specificity = rule.pattern.split('/').length; + if (!match || specificity >= match.specificity) { + match = {runner: rule.runner, specificity}; + } + } + return match ? match.runner : runnerConfig.defaultRunner; + }; + + let runnerConfig; + try { + runnerConfig = parseRunnerConfig(inpRunner); + } catch (error) { + core.setFailed(error.message); return; } + await core.group(`Set runner config`, async () => { + core.info(JSON.stringify(runnerConfig, null, 2)); + }); const sign = inpSign === 'auto' @@ -425,14 +521,14 @@ jobs: if (!inpDistribute || platforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: resolveRunner(runnerConfig) }); } else { platforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? (platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: resolveRunner(runnerConfig, platform) }); }); } @@ -481,6 +577,17 @@ jobs: result_18: ${{ steps.result.outputs.result_18 }} result_19: ${{ steps.result.outputs.result_19 }} steps: + - + name: Require GitHub-hosted runner + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + INPUT_RUNNER-ENVIRONMENT: ${{ runner.environment }} + with: + script: | + const runnerEnvironment = core.getInput('runner-environment'); + if (runnerEnvironment !== 'github-hosted') { + core.setFailed(`This workflow requires a GitHub-hosted runner, got: ${runnerEnvironment || 'unknown'}`); + } - name: Install dependencies uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e711b82..34cc4dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,12 @@ on: inputs: runner: type: string - 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)" + description: "GitHub-hosted runner label or platform mapping to build on" required: false - default: 'auto' + default: | + default=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm + linux/arm64=ubuntu-24.04-arm distribute: type: boolean description: "Whether to distribute the build across multiple runners (one platform per runner)" @@ -263,7 +266,7 @@ jobs: const inpActionsIdTokenSet = core.getBooleanInput('actions-id-token-set'); const inpMetaImages = core.getMultilineInput('meta-images'); - const inpRunner = core.getInput('runner'); + const inpRunner = core.getMultilineInput('runner'); const inpDistribute = core.getBooleanInput('distribute'); const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpPlatforms = Util.getInputList('platforms'); @@ -271,15 +274,108 @@ jobs: const inpPush = core.getBooleanInput('push'); const inpSign = core.getInput('sign'); - let runner = inpRunner; - if (inpRunner === 'amd64') { - runner = 'ubuntu-24.04'; - } else if (inpRunner === 'arm64') { - runner = 'ubuntu-24.04-arm'; - } else if (inpRunner !== 'auto') { - core.setFailed(`Invalid runner input: ${inpRunner}`); + const parseRunnerConfig = value => { + const lines = value.map(line => line.trim()).filter(line => line.length > 0); + if (lines.length === 0) { + throw new Error('runner input cannot be empty'); + } + if (lines.length === 1 && !lines[0].includes('=')) { + if (lines[0] === 'auto') { + core.warning('The runner input value "auto" is deprecated; use a runner mapping with default=ubuntu-24.04, linux/arm=ubuntu-24.04-arm, and linux/arm64=ubuntu-24.04-arm instead'); + return { + defaultRunner: 'ubuntu-24.04', + rules: [ + {pattern: 'linux/arm', runner: 'ubuntu-24.04-arm'}, + {pattern: 'linux/arm64', runner: 'ubuntu-24.04-arm'} + ] + }; + } + if (lines[0] === 'amd64') { + core.warning('The runner input value "amd64" is deprecated; use runner=ubuntu-24.04 instead'); + return { + defaultRunner: 'ubuntu-24.04', + rules: [] + }; + } + if (lines[0] === 'arm64') { + core.warning('The runner input value "arm64" is deprecated; use runner=ubuntu-24.04-arm instead'); + return { + defaultRunner: 'ubuntu-24.04-arm', + rules: [] + }; + } + return { + defaultRunner: lines[0], + rules: [] + }; + } + const rules = []; + let defaultRunner; + for (const line of lines) { + const idx = line.indexOf('='); + if (idx === -1) { + throw new Error(`Invalid runner mapping: ${line}`); + } + const pattern = line.substring(0, idx).trim(); + const runner = line.substring(idx + 1).trim(); + if (!pattern) { + throw new Error('Runner mapping pattern cannot be empty'); + } + if (!runner) { + throw new Error(`Runner mapping value cannot be empty for ${pattern}`); + } + if (pattern === 'default') { + defaultRunner = runner; + continue; + } + if (pattern.split('/').some(part => part.length === 0)) { + throw new Error(`Runner mapping pattern is not a valid platform prefix: ${pattern}`); + } + rules.push({pattern, runner}); + } + if (!defaultRunner) { + throw new Error('Runner mapping must define a default runner'); + } + return { + defaultRunner, + rules + }; + }; + + const matchesPlatformPrefix = (pattern, platform) => { + const patternParts = pattern.split('/'); + const platformParts = platform.split('/'); + return patternParts.length <= platformParts.length && + patternParts.every((part, index) => part === platformParts[index]); + }; + + const resolveRunner = (runnerConfig, platform) => { + if (!platform) { + return runnerConfig.defaultRunner; + } + let match; + for (const rule of runnerConfig.rules) { + if (!matchesPlatformPrefix(rule.pattern, platform)) { + continue; + } + const specificity = rule.pattern.split('/').length; + if (!match || specificity >= match.specificity) { + match = {runner: rule.runner, specificity}; + } + } + return match ? match.runner : runnerConfig.defaultRunner; + }; + + let runnerConfig; + try { + runnerConfig = parseRunnerConfig(inpRunner); + } catch (error) { + core.setFailed(error.message); return; } + await core.group(`Set runner config`, async () => { + core.info(JSON.stringify(runnerConfig, null, 2)); + }); const sign = inpSign === 'auto' @@ -318,14 +414,14 @@ jobs: if (!inpDistribute || inpPlatforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: resolveRunner(runnerConfig) }); } else { inpPlatforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? (platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: resolveRunner(runnerConfig, platform) }); }); } @@ -374,6 +470,17 @@ jobs: result_18: ${{ steps.result.outputs.result_18 }} result_19: ${{ steps.result.outputs.result_19 }} steps: + - + name: Require GitHub-hosted runner + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + INPUT_RUNNER-ENVIRONMENT: ${{ runner.environment }} + with: + script: | + const runnerEnvironment = core.getInput('runner-environment'); + if (runnerEnvironment !== 'github-hosted') { + core.setFailed(`This workflow requires a GitHub-hosted runner, got: ${runnerEnvironment || 'unknown'}`); + } - name: Install dependencies uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/README.md b/README.md index 25eb553..7d4dcb4 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,17 @@ ___ * [Performance](#performance) * [Security](#security) * [Isolation & Reliability](#isolation--reliability) -* [Usage](#usage) - * [Build reusable workflow](#build-reusable-workflow) - * [Inputs](#inputs) - * [Secrets](#secrets) - * [Outputs](#outputs) - * [Bake reusable workflow](#bake-reusable-workflow) - * [Inputs](#inputs-1) - * [Secrets](#secrets-1) - * [Outputs](#outputs-1) +* [Build reusable workflow](#build-reusable-workflow) + * [Inputs](#inputs) + * [Secrets](#secrets) + * [Outputs](#outputs) +* [Bake reusable workflow](#bake-reusable-workflow) + * [Inputs](#inputs-1) + * [Secrets](#secrets-1) + * [Outputs](#outputs-1) +* [Notes](#notes) + * [Runner mapping](#runner-mapping) + * [Metadata templates](#metadata-templates) ## Overview @@ -141,9 +143,7 @@ toward higher levels of security and trust. *source commit* and the *builder identity* before trusting or promoting an image, an essential part of supply-chain hardening. -## Usage - -### Build reusable workflow +## 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 @@ -187,25 +187,9 @@ jobs: - registry: docker.io username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Optional job to verify the pushed images' signatures. This is already done - # in the `build` job and can be omitted. It's provided here as an example of - # how to use the `verify.yml` reusable workflow. - build-verify: - uses: docker/github-builder/.github/workflows/verify.yml@v1 - if: ${{ github.event_name != 'pull_request' }} - needs: - - build - with: - builder-outputs: ${{ toJSON(needs.build.outputs) }} - secrets: - registry-auths: | - - registry: docker.io - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -#### Inputs +### Inputs > [!NOTE] > `List` type is a newline-delimited string @@ -220,67 +204,44 @@ jobs: > 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) | -| `distribute` | Bool | `true` | Whether to distribute the build across multiple runners (one platform per runner) | -| `fail-fast` | Bool | `false` | Whether to cancel all in-progress and queued jobs in the matrix if any job fails | -| `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) | -| `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 | `false` | 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 | | [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` | - -> [!TIP] -> When `output=image`, following inputs support Handlebars templates rendered -> from selected `docker/metadata-action` outputs: -> - `annotations` -> - `build-args` -> - `labels` -> -> The template context is exposed as `meta` with: -> - `meta.version` -> - `meta.tags` -> -> Example: -> ```yaml -> jobs: -> build: -> uses: docker/github-builder/.github/workflows/build.yml@v1 -> with: -> output: image -> build-args: | -> VERSION={{meta.version}} -> meta-images: name/app -> ``` - -#### Secrets +| Name | Type | Default | Description | +|------------------------|----------|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | See [Runner mapping](#runner-mapping) | GitHub-hosted runner label or platform mapping to build on. See [Runner mapping](#runner-mapping). | +| `distribute` | Bool | `true` | Whether to distribute the build across multiple runners (one platform per runner) | +| `fail-fast` | Bool | `false` | Whether to cancel all in-progress and queued jobs in the matrix if any job fails | +| `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) | +| `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 | `false` | 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 | | [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 | 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 | -#### Outputs +### Outputs These outputs are available as `needs..outputs.*` and can be passed directly to the [`verify.yml` reusable workflow](.github/workflows/verify.yml) @@ -296,7 +257,7 @@ with `builder-outputs: ${{ toJSON(needs..outputs) }}`. | `output-type` | String | Output type selected for the workflow (`image` or `local`) | | `signed` | Bool | Whether attestation manifests or local artifacts were signed | -### Bake reusable workflow +## Bake reusable workflow The [`bake.yml` reusable workflow](.github/workflows/bake.yml) lets you build container images and artifacts from a [Bake definition](https://docs.docker.com/build/bake/) @@ -339,26 +300,11 @@ jobs: - registry: docker.io username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Optional job to verify the pushed images' signatures. This is already done - # in the `bake` job and can be omitted. It's provided here as an example of - # how to use the `verify.yml` reusable workflow. - bake-verify: - uses: docker/github-builder/.github/workflows/verify.yml@v1 - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake - with: - builder-outputs: ${{ toJSON(needs.bake.outputs) }} - secrets: - registry-auths: | - - registry: docker.io - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -#### Inputs +### Inputs +> [!NOTE] > `List` type is a newline-delimited string > ```yaml > set: target.args.mybuildarg=value @@ -369,62 +315,42 @@ jobs: > 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) | -| `distribute` | Bool | `true` | Whether to distribute the build across multiple runners (one platform per runner) | -| `fail-fast` | Bool | `false` | Whether to cancel all in-progress and queued jobs in the matrix if any job fails | -| `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) | -| `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 | `false` | 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 | -| `vars` | List | | [Variables](https://docs.docker.com/build/bake/variables/) to set in the Bake definition as list of key-value pair | -| `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` | - -> [!TIP] -> When `output=image`, the `set` input supports Handlebars templates rendered -> from selected `docker/metadata-action` outputs. -> -> The template context is exposed as `meta` with: -> - `meta.version` -> - `meta.tags` -> -> Example: -> ```yaml -> jobs: -> bake: -> uses: docker/github-builder/.github/workflows/bake.yml@v1 -> with: -> output: image -> set: | -> *.args.VERSION={{meta.version}} -> meta-images: name/app -> ``` - -#### Secrets +| Name | Type | Default | Description | +|------------------------|--------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | See [Runner mapping](#runner-mapping) | GitHub-hosted runner label or platform mapping to build on. See [Runner mapping](#runner-mapping). | +| `distribute` | Bool | `true` | Whether to distribute the build across multiple runners (one platform per runner) | +| `fail-fast` | Bool | `false` | Whether to cancel all in-progress and queued jobs in the matrix if any job fails | +| `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) | +| `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 | `false` | 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 | +| `vars` | List | | [Variables](https://docs.docker.com/build/bake/variables/) to set in the Bake definition as list of key-value pair | +| `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 | -#### Outputs +### Outputs These outputs are available as `needs..outputs.*` and can be passed directly to the [`verify.yml` reusable workflow](.github/workflows/verify.yml) @@ -439,3 +365,69 @@ with `builder-outputs: ${{ toJSON(needs..outputs) }}`. | `digest` | String | Digest of the image pushed or artifact uploaded | | `output-type` | String | Output type selected for the workflow (`image` or `local`) | | `signed` | Bool | Whether attestation manifests or local artifacts were signed | + +## Notes + +### Runner mapping + +The `runner` input accepts either a single GitHub-hosted runner label or a +newline-delimited platform mapping. A single label is used for every build: + +```yaml +runner: ubuntu-24.04 +``` + +The default value uses the standard GitHub-hosted Ubuntu runners: + +```yaml +runner: | + default=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm + linux/arm64=ubuntu-24.04-arm +``` + +A mapping must define a `default` runner. Additional keys are platform prefixes, +and the most specific matching prefix wins. For example: + +```yaml +runner: | + default=ubuntu-24.04 + linux=ubuntu-24.04 + linux/arm=ubuntu-24.04-arm +``` + +For example, `linux` matches all Linux platforms, `linux/arm` matches variants +such as `linux/arm/v7`, and `linux/arm64` is separate from `linux/arm`. + +### Metadata templates + +When `output=image`, some inputs support Handlebars templates rendered from +selected `docker/metadata-action` outputs. The template context is exposed as +`meta` with `meta.version` and `meta.tags`. + +For the build workflow, the `annotations`, `build-args`, and `labels` inputs +support metadata templates: + +```yaml +jobs: + build: + uses: docker/github-builder/.github/workflows/build.yml@v1 + with: + output: image + build-args: | + VERSION={{meta.version}} + meta-images: name/app +``` + +For the bake workflow, the `set` input supports metadata templates: + +```yaml +jobs: + bake: + uses: docker/github-builder/.github/workflows/bake.yml@v1 + with: + output: image + set: | + *.args.VERSION={{meta.version}} + meta-images: name/app +```