From ff79a250c428244de1ceef15ae25d4a3a664bb64 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:39:15 +0200 Subject: [PATCH] BuildKit proxy network opt-in Add a buildkit-proxy-network input to the build and bake reusable workflows and pass it into the BuildKit daemon config. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 12 +++++++++++ .github/workflows/.test-build.yml | 11 +++++++++++ .github/workflows/bake.yml | 27 ++++++++++++++++++++++--- .github/workflows/build.yml | 27 +++++++++++++++++++++---- README.md | 33 +++++++++++++++++++++++++++++++ test/docker-bake.hcl | 4 ++++ test/proxy-network.Dockerfile | 9 +++++++++ 7 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 test/proxy-network.Dockerfile diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index d4aff1fc..dfd5fcd0 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -85,6 +85,18 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + bake-proxy-network: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + with: + cache: true + cache-scope: bake-proxynettest + context: test + output: local + target: proxy-network + buildkit-proxy-network: true + bake-ecr-public: uses: ./.github/workflows/bake.yml if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index 75b395e1..804204b2 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -84,6 +84,17 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + build-proxy-network: + uses: ./.github/workflows/build.yml + permissions: + contents: read + with: + cache: true + cache-scope: build-proxynettest + file: test/proxy-network.Dockerfile + output: local + buildkit-proxy-network: true + build-ecr-public: uses: ./.github/workflows/build.yml if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index fc82d851..c1177682 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -139,6 +139,12 @@ on: type: string description: "Bake target name for metadata (defaults to docker-metadata-action)" required: false + # buildkit config + buildkit-proxy-network: + type: boolean + description: "Enable BuildKit proxy network mode" + required: false + default: false secrets: registry-auths: description: "Raw authentication to registries, defined as YAML objects (for image output)" @@ -210,6 +216,7 @@ jobs: sign: ${{ steps.set.outputs.sign }} privateRepo: ${{ steps.set.outputs.privateRepo }} ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} + proxyNetwork: ${{ steps.set.outputs.proxyNetwork }} steps: - name: Install dependencies @@ -315,6 +322,7 @@ jobs: INPUT_TARGET: ${{ inputs.target }} INPUT_VARS: ${{ inputs.vars }} INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }} + INPUT_BUILDKIT-PROXY-NETWORK: ${{ inputs.buildkit-proxy-network }} with: script: | const os = require('os'); @@ -342,6 +350,7 @@ jobs: const inpSign = core.getInput('sign'); const inpTarget = core.getInput('target'); const inpGitHubToken = core.getInput('github-token'); + const inpProxyNetwork = core.getBooleanInput('buildkit-proxy-network'); const parseRunnerConfig = value => { const lines = value.map(line => line.trim()).filter(line => line.length > 0); @@ -564,6 +573,12 @@ jobs: core.info(`privateRepo: ${privateRepo}`); core.setOutput('privateRepo', privateRepo); }); + + await core.group(`Set proxyNetwork output`, async () => { + const proxyNetwork = inpProxyNetwork ? 'true' : 'false'; + core.info(`proxyNetwork: ${proxyNetwork}`); + core.setOutput('proxyNetwork', proxyNetwork); + }); await core.group(`Set includes output`, async () => { const withJobNamePrefix = include => { @@ -719,6 +734,8 @@ jobs: env.ACTIONS_ID_TOKEN_REQUEST_TOKEN=${{ steps.github-runtime.outputs.actions-id-token-request-token }} env.ACTIONS_ID_TOKEN_REQUEST_URL=${{ steps.github-runtime.outputs.actions-id-token-request-url }} buildkitd-config-inline: | + proxyNetwork = ${{ needs.prepare.outputs.proxyNetwork }} + [cache] [cache.gha] [cache.gha.sign] @@ -849,6 +866,7 @@ jobs: INPUT_BAKE-FILE-ANNOTATIONS: ${{ steps.meta.outputs.bake-file-annotations }} INPUT_BAKE-FILE-LABELS: ${{ steps.meta.outputs.bake-file-labels }} INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }} + INPUT_BUILDKIT-PROXY-NETWORK: ${{ inputs.buildkit-proxy-network }} with: script: | const os = require('os'); @@ -874,6 +892,9 @@ jobs: const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); const inpTarget = core.getInput('target'); const inpVars = Util.getInputList('vars'); + const inpGitHubToken = core.getInput('github-token'); + const inpBuildkitProxyNetwork = core.getBooleanInput('buildkit-proxy-network'); + const inpMetaImages = core.getMultilineInput('meta-images'); const inpMetaVersion = core.getInput('meta-version'); const inpMetaTags = core.getMultilineInput('meta-tags'); @@ -882,7 +903,6 @@ jobs: const inpBakeFileTags = core.getInput('bake-file-tags'); const inpBakeFileAnnotations = core.getInput('bake-file-annotations'); const inpBakeFileLabels = core.getInput('bake-file-labels'); - const inpGitHubToken = core.getInput('github-token'); const meta = { version: inpMetaVersion, @@ -982,8 +1002,9 @@ jobs: bakeOverrides.push(`*.platform=${inpPlatform}`); } if (inpCache) { - bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}${platformPairSuffix}`); - bakeOverrides.push(`*.cache-to=type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget}${platformPairSuffix},mode=${inpCacheMode}`); + const proxyNetworkSuffix = inpBuildkitProxyNetwork ? '-proxy-network' : ''; + bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}${platformPairSuffix}${proxyNetworkSuffix}`); + bakeOverrides.push(`*.cache-to=type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget}${platformPairSuffix}${proxyNetworkSuffix},mode=${inpCacheMode}`); } core.info(JSON.stringify(bakeOverrides, null, 2)); core.setOutput('overrides', bakeOverrides.join(os.EOL)); diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e5ce885..4e4ae49c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -150,6 +150,12 @@ on: type: string description: "Flavor defines a global behavior for meta-tags" required: false + # buildkit config + buildkit-proxy-network: + type: boolean + description: "Enable BuildKit proxy network mode" + required: false + default: false secrets: registry-auths: description: "Raw authentication to registries, defined as YAML objects (for image output)" @@ -221,6 +227,7 @@ jobs: sign: ${{ steps.set.outputs.sign }} privateRepo: ${{ steps.set.outputs.privateRepo }} ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} + proxyNetwork: ${{ steps.set.outputs.proxyNetwork }} steps: - name: Install dependencies @@ -311,6 +318,7 @@ jobs: INPUT_PLATFORMS: ${{ inputs.platforms }} INPUT_PUSH: ${{ inputs.push }} INPUT_SIGN: ${{ inputs.sign }} + INPUT_BUILDKIT-PROXY-NETWORK: ${{ inputs.buildkit-proxy-network }} with: script: | const { GitHub } = require('@docker/github-builder-runtime/lib/github/github'); @@ -328,6 +336,7 @@ jobs: const inpOutput = core.getInput('output'); const inpPush = core.getBooleanInput('push'); const inpSign = core.getInput('sign'); + const inpProxyNetwork = core.getBooleanInput('buildkit-proxy-network'); const parseRunnerConfig = value => { const lines = value.map(line => line.trim()).filter(line => line.length > 0); @@ -458,6 +467,12 @@ jobs: core.setOutput('privateRepo', privateRepo); }); + await core.group(`Set proxyNetwork output`, async () => { + const proxyNetwork = inpProxyNetwork ? 'true' : 'false'; + core.info(`proxyNetwork: ${proxyNetwork}`); + core.setOutput('proxyNetwork', proxyNetwork); + }); + const metaImages = inpMetaImages.map(image => image.toLowerCase()); await core.group(`Set metaImages output`, async () => { core.info(JSON.stringify(metaImages, null, 2)); @@ -617,6 +632,8 @@ jobs: env.ACTIONS_ID_TOKEN_REQUEST_TOKEN=${{ steps.github-runtime.outputs.actions-id-token-request-token }} env.ACTIONS_ID_TOKEN_REQUEST_URL=${{ steps.github-runtime.outputs.actions-id-token-request-url }} buildkitd-config-inline: | + proxyNetwork = ${{ needs.prepare.outputs.proxyNetwork }} + [cache] [cache.gha] [cache.gha.sign] @@ -733,6 +750,7 @@ jobs: INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_BUILDKIT-PROXY-NETWORK: ${{ inputs.buildkit-proxy-network }} INPUT_LABELS: ${{ inputs.labels }} INPUT_CONTEXT: ${{ inputs.context }} INPUT_OUTPUT: ${{ inputs.output }} @@ -773,6 +791,7 @@ jobs: const inpPush = core.getBooleanInput('push'); const inpSbom = core.getBooleanInput('sbom'); const inpTarget = core.getInput('target'); + const inpBuildkitProxyNetwork = core.getBooleanInput('buildkit-proxy-network'); const inpMetaImages = core.getMultilineInput('meta-images'); const inpMetaVersion = core.getInput('meta-version'); @@ -786,9 +805,7 @@ jobs: version: inpMetaVersion, tags: inpMetaTags }; - const renderTemplate = value => Util.compileHandlebars(value, {noEscape: true}, {meta}); - const toMultilineInput = value => value.split(/\r?\n/).map(line => line.trim()).filter(Boolean); const gitContextAttrs = GitHub.context.ref.startsWith('refs/tags/') ? {checksum: GitHub.context.sha} : {'fetch-by-commit': 'true'}; const buildContext = await new Build().gitContext({subdir: inpContext, attrs: gitContextAttrs}); @@ -819,14 +836,16 @@ jobs: core.setOutput('sbom', inpSbom ? `generator=${inpSbomImage}` : 'false'); if (inpCache) { - core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix}`); - core.setOutput('cache-to', `type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); + const proxyNetworkSuffix = inpBuildkitProxyNetwork ? '-proxy-network' : ''; + core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix}${proxyNetworkSuffix}`); + core.setOutput('cache-to', `type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix}${proxyNetworkSuffix},mode=${inpCacheMode}`); } let annotations; let labels; let buildArgs; try { + const toMultilineInput = value => value.split(/\r?\n/).map(line => line.trim()).filter(Boolean); annotations = toMultilineInput(renderTemplate(inpAnnotations)); labels = toMultilineInput(renderTemplate(inpLabels)); buildArgs = renderTemplate(inpBuildArgs); diff --git a/README.md b/README.md index 279b8f65..8ed58594 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ ___ * [Secrets](#secrets-1) * [Outputs](#outputs-1) * [Notes](#notes) + * [BuildKit proxy network](#buildkit-proxy-network) * [Signed GitHub Actions cache](#signed-github-actions-cache) * [Registry identities](#registry-identities) * [Docker Hub OIDC](#docker-hub-oidc) @@ -252,6 +253,7 @@ jobs: | `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` | +| `buildkit-proxy-network` | Bool | `false` | Enable BuildKit proxy network mode for default Dockerfile `RUN` networking. See [BuildKit proxy network](#buildkit-proxy-network). | ### Secrets @@ -364,6 +366,7 @@ jobs: | `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` | +| `buildkit-proxy-network` | Bool | `false` | Enable BuildKit proxy network mode for default Dockerfile `RUN` networking. See [BuildKit proxy network](#buildkit-proxy-network). | ### Secrets @@ -390,6 +393,36 @@ with `builder-outputs: ${{ toJSON(needs..outputs) }}`. ## Notes +### BuildKit proxy network + +The `buildkit-proxy-network` input enables BuildKit proxy network mode for +Dockerfile `RUN` steps with network access. `RUN --network=none` stays offline, +but default-network `RUN` steps are routed through BuildKit's internal HTTP(S) +proxy. + +When enabled, BuildKit rewrites `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, and +`https_proxy` inside affected `RUN` operations to point at that internal proxy. +It also injects a generated CA certificate into common Linux system trust bundle +locations for the duration of the operation. HTTPS clients that use separate or +embedded trust stores may fail TLS verification, and applications that ignore +proxy environment variables or open raw TCP connections cannot bypass the proxy. +Direct DNS and non-HTTP(S) protocols, such as Git-over-SSH, are not available +through this mode. + +BuildKit logs proxy network requests. Successful GET responses can be captured +as build materials and included in provenance dependency metadata when +provenance is requested. When `cache` is enabled, these workflows append +`-proxy-network` to the GitHub Actions cache scope while proxy network mode is +enabled, so unproxied cache entries are not imported into a proxied build. A +cache hit that was originally produced under proxy mode still does not execute +the network step again, so it does not emit a fresh request log or add new +provenance material. + +BuildKit does not chain the internal proxy through a caller-provided upstream +proxy. Enabling `buildkit-proxy-network` replaces Docker's predefined proxy +build arguments for affected `RUN` operations and can bypass an application-level +organizational proxy. See BuildKit's [proxy network documentation](https://github.com/moby/buildkit/blob/master/docs/proxy.md). + ### Signed GitHub Actions cache When the workflow has GitHub OIDC available through `id-token: write`, BuildKit diff --git a/test/docker-bake.hcl b/test/docker-bake.hcl index fbea740c..a782ca74 100644 --- a/test/docker-bake.hcl +++ b/test/docker-bake.hcl @@ -38,6 +38,10 @@ target "hello-cross" { platforms = ["linux/amd64", "linux/arm64"] } +target "proxy-network" { + dockerfile = "proxy-network.Dockerfile" +} + target "go-cross-with-contexts" { inherits = ["go-cross"] contexts = { diff --git a/test/proxy-network.Dockerfile b/test/proxy-network.Dockerfile new file mode 100644 index 00000000..d8e0a112 --- /dev/null +++ b/test/proxy-network.Dockerfile @@ -0,0 +1,9 @@ +# syntax=docker/dockerfile:1 + +FROM alpine AS check +RUN wget -qO- https://checkip.amazonaws.com/ | grep -Eq "^[0-9a-fA-F:.]+$" +RUN grep -q "buildkit proxy CA begin" /etc/ssl/certs/ca-certificates.crt +RUN --network=none ! grep -q "buildkit proxy CA begin" /etc/ssl/certs/ca-certificates.crt + +FROM scratch +COPY --from=check /etc/alpine-release /alpine-release