From 4e903ae61d7aeb81a88d9a3d9ddb673316d3a81b Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 29 Oct 2026 20:41:56 -0700 Subject: [PATCH] dockerfile: add BUILDKIT_DISABLE_ONBUILD build arg Setting BUILDKIT_DISABLE_ONBUILD skips the ONBUILD triggers of all base images and stages. It can be passed as a build argument or set as a global ARG in the Dockerfile. Signed-off-by: Tonis Tiigi --- frontend/dockerfile/dockerfile2llb/convert.go | 13 ++++- .../dockerfile/dockerfile_onbuild_test.go | 52 +++++++++++++++++++ frontend/dockerfile/dockerfile_test.go | 1 + frontend/dockerfile/docs/reference.md | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index f1e3f0317..12b83f364 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -48,6 +48,7 @@ const ( sbomScanContext = "BUILDKIT_SBOM_SCAN_CONTEXT" sbomScanStage = "BUILDKIT_SBOM_SCAN_STAGE" + disableOnbuild = "BUILDKIT_DISABLE_ONBUILD" ) var nonEnvArgs = map[string]struct{}{ @@ -217,6 +218,7 @@ type dispatchContext struct { allDispatchStates *dispatchStates proxyEnv *llb.ProxyEnv namedContext func(string, dockerui.ContextOpt) (*dockerui.NamedContext, error) + disableOnbuild bool } func namedContextFunc(opt ConvertOpt) func(string, dockerui.ContextOpt) (*dockerui.NamedContext, error) { @@ -311,6 +313,14 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS globalArgs = setBuildArgValue(opt.BuildArgs, globalArgs, "SOURCE_DATE_EPOCH", formatSourceDateEpochValue(resolvedEpoch)) } + var skipOnbuild bool + if v, ok := getBuildArgValue(opt.BuildArgs, globalArgs, disableOnbuild); ok && v != "" { + skipOnbuild, err = strconv.ParseBool(v) + if err != nil { + return nil, errors.Wrapf(err, "invalid %s value %q", disableOnbuild, v) + } + } + metaResolver := opt.MetaResolver if metaResolver == nil { metaResolver = imagemetaresolver.Default() @@ -328,6 +338,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS allDispatchStates: newDispatchStates(), proxyEnv: proxyEnvFromBuildArgs(opt.BuildArgs), namedContext: namedContextFunc(opt), + disableOnbuild: skipOnbuild, } if err := dctx.buildDispatchStates(stages); err != nil { @@ -569,7 +580,7 @@ func (dctx *dispatchContext) resolveStages(ctx context.Context, target *dispatch } d.onBuildInit = true } - if slices.Contains(d.stage.Reset, dfcommand.Onbuild) { + if dctx.disableOnbuild || slices.Contains(d.stage.Reset, dfcommand.Onbuild) { onbuilds = nil d.image.Config.OnBuild = nil } diff --git a/frontend/dockerfile/dockerfile_onbuild_test.go b/frontend/dockerfile/dockerfile_onbuild_test.go index 86a77e0b0..01d25716e 100644 --- a/frontend/dockerfile/dockerfile_onbuild_test.go +++ b/frontend/dockerfile/dockerfile_onbuild_test.go @@ -701,3 +701,55 @@ RUN --mount=type=cache,target=C:\cache type C:\cache\foo | findstr "42" && type }, nil) require.NoError(t, err) } + +func testOnBuildDisabled(t *testing.T, sb integration.Sandbox) { + integration.SkipOnPlatform(t, "windows") + f := getFrontend(t, sb) + + c, err := client.New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + solve := func(dockerfile string, buildArgs map[string]string) error { + dir := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", []byte(dockerfile), 0600), + ) + attrs := map[string]string{} + for k, v := range buildArgs { + attrs["build-arg:"+k] = v + } + _, err := f.Solve(sb.Context(), c, client.SolveOpt{ + FrontendAttrs: attrs, + LocalMounts: map[string]fsutil.FS{ + dockerui.DefaultLocalNameDockerfile: dir, + dockerui.DefaultLocalNameContext: dir, + }, + }, nil) + return err + } + + dockerfile := ` +FROM busybox AS base +ONBUILD RUN false + +FROM base +RUN true +` + err = solve(dockerfile, nil) + require.ErrorContains(t, err, "did not complete successfully") + + err = solve(dockerfile, map[string]string{"BUILDKIT_DISABLE_ONBUILD": "1"}) + require.NoError(t, err) + + err = solve(dockerfile, map[string]string{"BUILDKIT_DISABLE_ONBUILD": "maybe"}) + require.ErrorContains(t, err, `invalid BUILDKIT_DISABLE_ONBUILD value "maybe"`) + + dockerfile = "ARG BUILDKIT_DISABLE_ONBUILD=true\n" + dockerfile + + err = solve(dockerfile, nil) + require.NoError(t, err) + + err = solve(dockerfile, map[string]string{"BUILDKIT_DISABLE_ONBUILD": "false"}) + require.ErrorContains(t, err, "did not complete successfully") +} diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 731e5a4e0..2f3e01d68 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -192,6 +192,7 @@ var allTests = integration.TestFuncs( testOnBuildInheritedStageWithFrom, testOnBuildNewDeps, testOnBuildWithCacheMount, + testOnBuildDisabled, // dockerfile_platform_test.go testPlatformArgsImplicit, diff --git a/frontend/dockerfile/docs/reference.md b/frontend/dockerfile/docs/reference.md index 668b8d4d4..b70b5f2cf 100644 --- a/frontend/dockerfile/docs/reference.md +++ b/frontend/dockerfile/docs/reference.md @@ -2874,6 +2874,7 @@ RUN echo "I'm building for $TARGETPLATFORM" | `BUILDKIT_BUILD_NAME` | String | Override the build name shown in [`buildx history` command](https://docs.docker.com/reference/cli/docker/buildx/history/) and [Docker Desktop Builds view](https://docs.docker.com/desktop/use-desktop/builds/). | | `BUILDKIT_CACHE_MOUNT_NS` | String | Set optional cache ID namespace. | | `BUILDKIT_CONTEXT_KEEP_GIT_DIR` | Bool | Trigger Git context to keep the `.git` directory. | +| `BUILDKIT_DISABLE_ONBUILD` | Bool | Skip the [`ONBUILD`](#onbuild) triggers of all base images and stages. This is not a security boundary: a custom frontend selected with `# syntax=` can ignore it. | | `BUILDKIT_GIT_ADVICE` | Bool | Show Git advice messages from BuildKit-managed Git operations. Defaults to `false`. | | `BUILDKIT_GIT_DEBUG_COMMANDS` | Bool | Print executed Git commands for Git contexts and `ADD` sources in the build log. Defaults to `false`. | | `BUILDKIT_INLINE_CACHE`[^2] | Bool | Inline cache metadata to image config or not. |