From 47ac3db3806b5b799f2fa6487c3f4dade162f9b2 Mon Sep 17 00:00:00 2001 From: Emilien Escalle Date: Wed, 1 Jul 2026 14:49:30 +0200 Subject: [PATCH 1/2] fix(docker/build-image): enhance cache handling for GitHub Actions triggers Signed-off-by: Emilien Escalle --- actions/docker/build-image/README.md | 3 +++ actions/docker/build-image/action.yml | 31 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/actions/docker/build-image/README.md b/actions/docker/build-image/README.md index 3a357cfb..8e328c46 100644 --- a/actions/docker/build-image/README.md +++ b/actions/docker/build-image/README.md @@ -133,6 +133,8 @@ permissions: secret-envs: "" # Cache type. Set to `false` or empty to disable cache entirely. + # For `gha`, cache restore remains enabled on untrusted default-branch-scoped triggers, + # but cache export is skipped automatically because GitHub only grants read-only cache access there. # See https://docs.docker.com/build/cache/backends. # # Default: `gha` @@ -203,6 +205,7 @@ permissions: | **`secret-envs`** | List of secret environment variables to expose to the build (e.g., `key=envname, MY_SECRET=MY_ENV_VAR`). | **false** | - | | | See . | | | | **`cache-type`** | Cache type. Set to `false` or empty to disable cache entirely. | **false** | `gha` | +| | For `gha`, cache restore remains enabled on untrusted default-branch-scoped triggers, but cache export is skipped automatically because GitHub only grants read-only cache access there. | | | | | See . | | | | **`buildkitd-config-inline`** | Inline BuildKit daemon configuration. | **false** | - | | | See . | | | diff --git a/actions/docker/build-image/action.yml b/actions/docker/build-image/action.yml index 4bc8ddc2..eeeb2356 100644 --- a/actions/docker/build-image/action.yml +++ b/actions/docker/build-image/action.yml @@ -98,6 +98,8 @@ inputs: cache-type: description: | Cache type. Set to `false` or empty to disable cache entirely. + For `gha`, cache restore remains enabled on untrusted default-branch-scoped triggers, + but cache export is skipped automatically because GitHub only grants read-only cache access there. See https://docs.docker.com/build/cache/backends. default: "gha" required: false @@ -189,7 +191,9 @@ runs: CACHE_REGISTRY: ${{ steps.docker-setup.outputs.cache-registry }} CACHE_TYPE_INPUT: ${{ inputs.cache-type }} CONTEXT_INPUT: ${{ inputs.context }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} DOCKERFILE_INPUT: ${{ inputs.dockerfile }} + GH_REF: ${{ github.ref }} METADATA_IMAGE: ${{ steps.metadata.outputs.image }} MULTI_PLATFORM_INPUT: ${{ inputs.multi-platform }} PLATFORM_INPUT: ${{ inputs.platform }} @@ -243,6 +247,31 @@ runs: core.setOutput('cache-disabled', cacheDisabled ? 'true' : 'false'); core.setOutput('cache-type', cacheType); + const defaultBranch = (process.env.DEFAULT_BRANCH || '').trim(); + const githubRef = (process.env.GH_REF || '').trim(); + const defaultBranchRef = defaultBranch.length > 0 ? `refs/heads/${defaultBranch}` : ''; + const usesDefaultBranchScope = defaultBranchRef.length > 0 && githubRef === defaultBranchRef; + const ghaCacheReadWriteEvents = new Set([ + 'push', + 'schedule', + 'workflow_dispatch', + 'repository_dispatch', + 'delete', + 'registry_package', + 'page_build', + ]); + const cacheExportDisabled = cacheType === 'gha' + && usesDefaultBranchScope + && !ghaCacheReadWriteEvents.has(context.eventName); + core.setOutput('cache-export-disabled', cacheExportDisabled ? 'true' : 'false'); + + if (cacheExportDisabled) { + core.info( + `Disabling gha cache export for ${context.eventName} on ${githubRef}. ` + + 'GitHub only grants read-only cache access for this default-branch-scoped trigger, so cache restores remain enabled and cache writes are skipped.' + ); + } + const metadataImage = process.env.METADATA_IMAGE || ''; const cacheRegistry = (process.env.CACHE_REGISTRY || '').trim(); if (cacheType === 'registry' && !cacheRegistry.length) { @@ -353,7 +382,7 @@ runs: secret-envs: ${{ inputs.secret-envs }} platforms: ${{ inputs.platform }} cache-from: ${{ steps.get-docker-config.outputs.cache-disabled != 'true' && steps.cache-arguments.outputs.cache-from || '' }} - cache-to: ${{ steps.get-docker-config.outputs.cache-disabled != 'true' && steps.cache-arguments.outputs.cache-to || '' }} + cache-to: ${{ steps.get-docker-config.outputs.cache-disabled != 'true' && steps.get-docker-config.outputs.cache-export-disabled != 'true' && steps.cache-arguments.outputs.cache-to || '' }} no-cache: ${{ steps.get-docker-config.outputs.cache-disabled }} outputs: | ${{ steps.get-docker-config.outputs.multi-platform && 'type=image,push-by-digest=true,name-canonical=true,push=true' || 'type=image,push=true' }} From 6bd89784687b16eeee467ce2de8a2bebb819e917 Mon Sep 17 00:00:00 2001 From: Emilien Escalle Date: Wed, 1 Jul 2026 15:03:22 +0200 Subject: [PATCH 2/2] fix(docker/build-image): enhance cache handling for GitHub Actions triggers Signed-off-by: Emilien Escalle --- actions/docker/build-image/README.md | 102 +++++++++++++------------- actions/docker/build-image/action.yml | 25 ++++++- 2 files changed, 73 insertions(+), 54 deletions(-) diff --git a/actions/docker/build-image/README.md b/actions/docker/build-image/README.md index 8e328c46..0d36d2b4 100644 --- a/actions/docker/build-image/README.md +++ b/actions/docker/build-image/README.md @@ -162,58 +162,58 @@ permissions: ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | -------------------------------- | -| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **true** | `ghcr.io` | -| | Accepts either a registry hostname string (default format) or a JSON object. | | | -| | JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` | | | -| | JSON object keys: | | | -| | - `pull`: registry used to pull public or default base images | | | -| | - `pull:`: additional pull registry | | | -| | - `push`: registry used for published images | | | -| | - `cache`: registry used when `cache-type` is `registry` | | | -| | If no `pull` key is provided, the `push` registry is also used for pulls. | | | -| **`oci-registry-username`** | Username configuration used to log against OCI registries. | **true** | `${{ github.repository_owner }}` | -| | Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. | | | -| | JSON example: | | | -| | `{"pull:private":"$\{{ github.repository_owner }}","push":"$\{{ github.repository_owner }}"}` | | | -| | See . | | | -| **`oci-registry-password`** | Password or personal access token configuration used to log against OCI registries. | **true** | `${{ github.token }}` | -| | Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. | | | -| | JSON example: `{"pull:private":"$\{{ github.token }}","push":"$\{{ github.token }}"}` | | | -| | Can be passed in using `secrets.GITHUB_TOKEN`. | | | -| | See . | | | -| **`repository`** | Repository name. | **false** | `${{ github.repository }}` | -| | Example: `my-org/my-repo`. | | | -| | See [Docker get-image-metadata action](../get-image-metadata/README.md). | | | -| **`image`** | Additional image name. | **false** | - | -| | Example: `application`. | | | -| | See [Docker get-image-metadata action](../get-image-metadata/README.md). | | | -| **`tag`** | Force image tag to publish | **false** | - | -| **`platform`** | Platform to build for. Example: `linux/amd64`. | **true** | - | -| | See . | | | -| **`context`** | Build's context is the set of files located in the specified PATH or URL. | **false** | `.` | -| | See . | | | -| **`dockerfile`** | Location of Dockerfile (defaults to Dockerfile). | **false** | `Dockerfile` | -| | See . | | | -| **`build-args`** | List of build-time variables. | **false** | - | -| | See . | | | -| **`target`** | Sets the target stage to build. | **false** | - | -| | See . | | | -| **`secrets`** | List of secrets to expose to the build. | **false** | - | -| | See . | | | -| **`secret-envs`** | List of secret environment variables to expose to the build (e.g., `key=envname, MY_SECRET=MY_ENV_VAR`). | **false** | - | -| | See . | | | -| **`cache-type`** | Cache type. Set to `false` or empty to disable cache entirely. | **false** | `gha` | +| **Input** | **Description** | **Required** | **Default** | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------- | +| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **true** | `ghcr.io` | +| | Accepts either a registry hostname string (default format) or a JSON object. | | | +| | JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` | | | +| | JSON object keys: | | | +| | - `pull`: registry used to pull public or default base images | | | +| | - `pull:`: additional pull registry | | | +| | - `push`: registry used for published images | | | +| | - `cache`: registry used when `cache-type` is `registry` | | | +| | If no `pull` key is provided, the `push` registry is also used for pulls. | | | +| **`oci-registry-username`** | Username configuration used to log against OCI registries. | **true** | `${{ github.repository_owner }}` | +| | Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. | | | +| | JSON example: | | | +| | `{"pull:private":"$\{{ github.repository_owner }}","push":"$\{{ github.repository_owner }}"}` | | | +| | See . | | | +| **`oci-registry-password`** | Password or personal access token configuration used to log against OCI registries. | **true** | `${{ github.token }}` | +| | Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. | | | +| | JSON example: `{"pull:private":"$\{{ github.token }}","push":"$\{{ github.token }}"}` | | | +| | Can be passed in using `secrets.GITHUB_TOKEN`. | | | +| | See . | | | +| **`repository`** | Repository name. | **false** | `${{ github.repository }}` | +| | Example: `my-org/my-repo`. | | | +| | See [Docker get-image-metadata action](../get-image-metadata/README.md). | | | +| **`image`** | Additional image name. | **false** | - | +| | Example: `application`. | | | +| | See [Docker get-image-metadata action](../get-image-metadata/README.md). | | | +| **`tag`** | Force image tag to publish | **false** | - | +| **`platform`** | Platform to build for. Example: `linux/amd64`. | **true** | - | +| | See . | | | +| **`context`** | Build's context is the set of files located in the specified PATH or URL. | **false** | `.` | +| | See . | | | +| **`dockerfile`** | Location of Dockerfile (defaults to Dockerfile). | **false** | `Dockerfile` | +| | See . | | | +| **`build-args`** | List of build-time variables. | **false** | - | +| | See . | | | +| **`target`** | Sets the target stage to build. | **false** | - | +| | See . | | | +| **`secrets`** | List of secrets to expose to the build. | **false** | - | +| | See . | | | +| **`secret-envs`** | List of secret environment variables to expose to the build (e.g., `key=envname, MY_SECRET=MY_ENV_VAR`). | **false** | - | +| | See . | | | +| **`cache-type`** | Cache type. Set to `false` or empty to disable cache entirely. | **false** | `gha` | | | For `gha`, cache restore remains enabled on untrusted default-branch-scoped triggers, but cache export is skipped automatically because GitHub only grants read-only cache access there. | | | -| | See . | | | -| **`buildkitd-config-inline`** | Inline BuildKit daemon configuration. | **false** | - | -| | See . | | | -| | Example for insecure registry: | | | -| |
[registry."my-registry.local:5000"]
 http = true
 insecure = true
| | | -| **`multi-platform`** | Whether this build participates in a multi-platform image publication. | **false** | `false` | -| | When true, the image is pushed by digest only so manifests can be assembled later. | | | -| | When false, the image is pushed with its tags directly. | | | +| | See . | | | +| **`buildkitd-config-inline`** | Inline BuildKit daemon configuration. | **false** | - | +| | See . | | | +| | Example for insecure registry: | | | +| |
[registry."my-registry.local:5000"]
 http = true
 insecure = true
| | | +| **`multi-platform`** | Whether this build participates in a multi-platform image publication. | **false** | `false` | +| | When true, the image is pushed by digest only so manifests can be assembled later. | | | +| | When false, the image is pushed with its tags directly. | | | diff --git a/actions/docker/build-image/action.yml b/actions/docker/build-image/action.yml index eeeb2356..bbe237ae 100644 --- a/actions/docker/build-image/action.yml +++ b/actions/docker/build-image/action.yml @@ -268,7 +268,8 @@ runs: if (cacheExportDisabled) { core.info( `Disabling gha cache export for ${context.eventName} on ${githubRef}. ` - + 'GitHub only grants read-only cache access for this default-branch-scoped trigger, so cache restores remain enabled and cache writes are skipped.' + + 'GitHub only grants read-only cache access for this default-branch-scoped trigger, ' + + 'so cache restores remain enabled and cache writes are skipped.' ); } @@ -371,6 +372,24 @@ runs: dockerfile: ${{ steps.get-docker-config.outputs.dockerfile-path }} skip-extraction: ${{ steps.cache.outputs.cache-hit }} + - id: cache-inputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + CACHE_DISABLED: ${{ steps.get-docker-config.outputs.cache-disabled }} + CACHE_EXPORT_DISABLED: ${{ steps.get-docker-config.outputs.cache-export-disabled }} + CACHE_FROM: ${{ steps.cache-arguments.outputs.cache-from }} + CACHE_TO: ${{ steps.cache-arguments.outputs.cache-to }} + with: + script: | + const cacheDisabled = (process.env.CACHE_DISABLED || '') === 'true'; + const cacheExportDisabled = (process.env.CACHE_EXPORT_DISABLED || '') === 'true'; + + core.setOutput('cache-from', cacheDisabled ? '' : (process.env.CACHE_FROM || '')); + core.setOutput( + 'cache-to', + cacheDisabled || cacheExportDisabled ? '' : (process.env.CACHE_TO || '') + ); + - id: build uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: @@ -381,8 +400,8 @@ runs: secrets: ${{ inputs.secrets }} secret-envs: ${{ inputs.secret-envs }} platforms: ${{ inputs.platform }} - cache-from: ${{ steps.get-docker-config.outputs.cache-disabled != 'true' && steps.cache-arguments.outputs.cache-from || '' }} - cache-to: ${{ steps.get-docker-config.outputs.cache-disabled != 'true' && steps.get-docker-config.outputs.cache-export-disabled != 'true' && steps.cache-arguments.outputs.cache-to || '' }} + cache-from: ${{ steps.cache-inputs.outputs.cache-from }} + cache-to: ${{ steps.cache-inputs.outputs.cache-to }} no-cache: ${{ steps.get-docker-config.outputs.cache-disabled }} outputs: | ${{ steps.get-docker-config.outputs.multi-platform && 'type=image,push-by-digest=true,name-canonical=true,push=true' || 'type=image,push=true' }}