From e6e424505d83bd2210e76e75b3a990afffdeb9fd Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Fri, 3 Jul 2026 22:33:28 +0100 Subject: [PATCH 01/13] v1.29.0 dry-run: flip switch-on feature flags to default true (except RUN_VIA_TINI, blocked on RISC-V tini) --- pkg/config/featureflags.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index 56e5fd85e..95f6aec99 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -112,7 +112,7 @@ func InitFeatureFlags() { ExpandHeredoc: featureFlag("FF_KANIKO_EXPAND_HEREDOC", false), HashDirFraming: featureFlag("FF_KANIKO_HASH_DIR_FRAMING", false), IgnoreCachedManifest: featureFlag("FF_KANIKO_IGNORE_CACHED_MANIFEST", false), - InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", false), + InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", true), NoPropagateAnnotations: featureFlag("FF_KANIKO_NO_PROPAGATE_ANNOTATIONS", true), OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), OCIWarmer: featureFlag("FF_KANIKO_OCI_WARMER", true), @@ -121,19 +121,19 @@ func InitFeatureFlags() { PreserveHardlinks: featureFlag("FF_KANIKO_PRESERVE_HARDLINKS", true), RelativeLinkTargets: featureFlag("FF_KANIKO_RELATIVE_LINK_TARGETS", true), PreserveMountedPaths: featureFlag("FF_KANIKO_PRESERVE_MOUNTED_PATHS", true), - ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", false), - ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", false), + ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", true), + ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", true), RollingCacheKey: featureFlag("FF_KANIKO_ROLLING_CACHE_KEY", false), RunHonorGroup: featureFlag("FF_KANIKO_RUN_HONOR_GROUP", false), RunMountBind: featureFlag("FF_KANIKO_RUN_MOUNT_BIND", true), RunViaTini: featureFlag("FF_KANIKO_RUN_VIA_TINI", false), ScopedDockerignore: featureFlag("FF_KANIKO_SCOPED_DOCKERIGNORE", false), SecurejoinExtraction: featureFlag("FF_KANIKO_SECUREJOIN_EXTRACTION", true), - SharedBaseCache: featureFlag("FF_KANIKO_SHARED_BASE_CACHE", false), - SkipCachedStages: featureFlag("FF_KANIKO_SKIP_CACHED_STAGES", false), - SkipRelabelRecompress: featureFlag("FF_KANIKO_SKIP_RELABEL_RECOMPRESS", false), - SkipWriteWhiteouts: featureFlag("FF_KANIKO_SKIP_WRITE_WHITEOUTS", false), - UntarSkipRoot: featureFlag("FF_KANIKO_UNTAR_SKIP_ROOT", false), + SharedBaseCache: featureFlag("FF_KANIKO_SHARED_BASE_CACHE", true), + SkipCachedStages: featureFlag("FF_KANIKO_SKIP_CACHED_STAGES", true), + SkipRelabelRecompress: featureFlag("FF_KANIKO_SKIP_RELABEL_RECOMPRESS", true), + SkipWriteWhiteouts: featureFlag("FF_KANIKO_SKIP_WRITE_WHITEOUTS", true), + UntarSkipRoot: featureFlag("FF_KANIKO_UNTAR_SKIP_ROOT", true), VolumeSkipMkdir: featureFlag("FF_KANIKO_VOLUME_SKIP_MKDIR", true), WarmerCacheLock: featureFlag("FF_KANIKO_WARMER_CACHE_LOCK", true), } From ebc426aa8001aeee5c291fdc9f872aad6094d81a Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Fri, 3 Jul 2026 23:02:58 +0100 Subject: [PATCH 02/13] v1.29.0 dry-run: remove deprecated feature flags and their legacy code paths --- cmd/executor/cmd/root.go | 6 - .../dockerfiles/Dockerfile_test_issue_mz793 | 8 -- integration/images.go | 8 -- pkg/cache/cache.go | 66 +--------- pkg/commands/env.go | 7 +- pkg/commands/run.go | 6 +- pkg/commands/volume.go | 14 -- pkg/config/featureflags.go | 22 ---- pkg/dockerfile/buildargs.go | 10 +- pkg/executor/build.go | 39 +----- pkg/util/fs_util.go | 63 ++++----- pkg/warmer/lock.go | 2 +- pkg/warmer/warm.go | 120 +++--------------- 13 files changed, 55 insertions(+), 316 deletions(-) delete mode 100644 integration/dockerfiles/Dockerfile_test_issue_mz793 diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 2607a4bd5..4145b0936 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -391,12 +391,6 @@ func checkNoDeprecatedFlags() { if opts.SkipUnusedStagesDeprecated { logrus.Warn("Flag --skip-unused-stages is deprecated. This is the new default behaviour. If you want to build multiple independent stages pass them as --target instead") } - - if !config.FF.DeprecateInterStageRestore { - if opts.PreserveContext && !opts.PreCleanup { - logrus.Warn("--preserve-context without --pre-cleanup restores the original context between stages; this is deprecated and will be removed. Use --mount=type=secret for secrets. Set FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE=1 to opt into the new behaviour now.") - } - } } // cacheFlagsValid makes sure the flags passed in related to caching are valid diff --git a/integration/dockerfiles/Dockerfile_test_issue_mz793 b/integration/dockerfiles/Dockerfile_test_issue_mz793 deleted file mode 100644 index 5dacbbbf5..000000000 --- a/integration/dockerfiles/Dockerfile_test_issue_mz793 +++ /dev/null @@ -1,8 +0,0 @@ -FROM busybox - -# mz793: VOLUME + --cache panics on assertion executor.build.without-fs since v1.27.4. -# With FF_KANIKO_VOLUME_SKIP_MKDIR off, VOLUME creates the volume directory and it gets -# snapshotted, but VolumeCommand reports MetadataOnly, so the without-fs assertion -# (metadata-only commands snapshot zero files) fires. The assertion only holds once the -# flag skips the mkdir, so it must be gated on the flag. -VOLUME /data diff --git a/integration/images.go b/integration/images.go index 34a9dc5bd..ea570200c 100644 --- a/integration/images.go +++ b/integration/images.go @@ -99,7 +99,6 @@ var envsMap = map[string][]string{ "Dockerfile_test_issue_mz775": {"FF_KANIKO_CACHE_LOOKAHEAD=0"}, "Dockerfile_test_issue_mz334": {"FF_KANIKO_SKIP_CACHED_STAGES=1"}, "Dockerfile_test_issue_mz960": {"FF_KANIKO_SKIP_CACHED_STAGES=1"}, - "Dockerfile_test_issue_mz793": {"FF_KANIKO_VOLUME_SKIP_MKDIR=0"}, "Dockerfile_test_issue_mz473": {"KANIKO_DIR=/kaniko2"}, "Dockerfile_test_issue_mz661": {"KANIKO_DIR=/kaniko2"}, // mz970: layout layers carry no reference, so a stored base cannot be mounted @@ -301,10 +300,6 @@ var diffArgsMap = map[string][]string{ // But we discovered a new issue with this. For builtins, buildkit will emit "whiteout" files, // to remember that it was removed, we don't. So we end up with a diff in the resulting image. "TestRun/test_Dockerfile_test_issue_mz511": {"--extra-ignore-files=etc/.wh.nsswitch.conf", "--extra-ignore-layer-length-mismatch"}, - // mz793: with FF_KANIKO_VOLUME_SKIP_MKDIR off, VOLUME creates the directory fresh on - // each build, so its mtime differs between the two cached builds. That divergence is the - // known volume non-determinism the flag fixes, here we only assert the build no longer panics. - "TestCache/test_cache_Dockerfile_test_issue_mz793": {"--extra-ignore-files=data/"}, // Layer-length divergences from buildkit, enforced per-test instead of globally "TestRun/test_Dockerfile_test_add": {"--extra-ignore-layer-length-mismatch"}, "TestRun/test_Dockerfile_test_arg_blank_with_quotes": {"--extra-ignore-layer-length-mismatch"}, @@ -409,8 +404,6 @@ var warmerOutputChecks = map[string]func(string, []byte) error{ var expectedWarnings = map[string]string{ // mz640: COPY to /kaniko (ignored path) must warn rather than silently skip. "Dockerfile_test_issue_mz560": "Skipping copy targeting kaniko directory", - // mz793: the test disables FF_KANIKO_VOLUME_SKIP_MKDIR, which the flag registry warns about. - "Dockerfile_test_issue_mz793": "feature flags explicitly disabled, please create an issue for your use-case: FF_KANIKO_VOLUME_SKIP_MKDIR", // mz936: the read-only store makes both stages degrade to a registry fetch, each warning. "Dockerfile_test_issue_mz936": "shared-base:", // mz991: the repro needs a MAINTAINER, which warns twice, once from the buildkit @@ -554,7 +547,6 @@ func NewDockerFileBuilder() *DockerFileBuilder { "Dockerfile_test_issue_mz775": {}, "Dockerfile_test_issue_mz782": {}, "Dockerfile_test_issue_mz873": {}, - "Dockerfile_test_issue_mz793": {}, "Dockerfile_test_issue_mz879": {}, "Dockerfile_test_issue_mz896": {}, "Dockerfile_test_issue_mz960": {}, diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 17868945d..cadab0ef9 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "path" - "path/filepath" "strings" "time" @@ -29,7 +28,6 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/creds" "github.com/osscontainertools/kaniko/pkg/mounts" @@ -200,69 +198,7 @@ func LocalSource(opts *config.CacheOptions, cacheKey string) (v1.Image, error) { } logrus.Infof("Found %s in local cache", cacheKey) - if config.FF.OCIWarmer { - return ociCachedImageFromPath(path) - } else { - return cachedImageFromPath(path) - } -} - -// cachedImage represents a v1.Tarball that is cached locally in a CAS. -// Computing the digest for a v1.Tarball is very expensive. If the tarball -// is named with the digest we can store this and return it directly rather -// than recompute it. -type cachedImage struct { - digest string - v1.Image - mfst *v1.Manifest -} - -func (c *cachedImage) Digest() (v1.Hash, error) { - return v1.NewHash(c.digest) -} - -func (c *cachedImage) Manifest() (*v1.Manifest, error) { - if config.FF.IgnoreCachedManifest || c.mfst == nil { - return c.Image.Manifest() - } - return c.mfst, nil -} - -func mfstFromPath(p string) (*v1.Manifest, error) { - f, err := util.FSys.Open(p) - if err != nil { - return nil, err - } - defer f.Close() - return v1.ParseManifest(f) -} - -func cachedImageFromPath(p string) (v1.Image, error) { - imgTar, err := tarball.ImageFromPath(p, nil) - if err != nil { - return nil, fmt.Errorf("getting image from path: %w", err) - } - - // Manifests may be present next to the tar, named with a ".json" suffix - mfstPath := p + ".json" - - var mfst *v1.Manifest - if _, err := os.Stat(mfstPath); err != nil { - logrus.Debugf("Manifest does not exist at file: %s", mfstPath) - } else { - mfst, err = mfstFromPath(mfstPath) - if err != nil { - logrus.Debugf("Error parsing manifest from file: %s", mfstPath) - } else { - logrus.Infof("Found manifest at %s", mfstPath) - } - } - - return &cachedImage{ - digest: filepath.Base(p), - Image: imgTar, - mfst: mfst, - }, nil + return ociCachedImageFromPath(path) } func ociCachedImageFromPath(tarPath string) (v1.Image, error) { diff --git a/pkg/commands/env.go b/pkg/commands/env.go index 2721ec5a9..a79dd98a5 100644 --- a/pkg/commands/env.go +++ b/pkg/commands/env.go @@ -19,7 +19,6 @@ package commands import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" - kConfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" ) @@ -37,10 +36,8 @@ func (e *EnvCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui return err } // 3344: An ENV declared after an ARG of the same name overrides it. - if kConfig.FF.BuildkitArgEnvPrecedence { - for _, keyVal := range newEnvs { - buildArgs.RemoveArg(keyVal.Key) - } + for _, keyVal := range newEnvs { + buildArgs.RemoveArg(keyVal.Key) } return nil } diff --git a/pkg/commands/run.go b/pkg/commands/run.go index b95de777a..314f7ea72 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -60,7 +60,6 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui } func runCommandWithFlags(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand, fileContext util.FileContext, secrets kConfig.SecretOptions) (reterr error) { - ff_bind := kConfig.FF.RunMountBind for _, f := range cmdRun.FlagsUsed { if f != "mount" { logrus.Warnf("#969 kaniko does not support '--%s' flags in RUN statements - relying on unsupported flags can lead to invalid builds", f) @@ -216,7 +215,7 @@ func runCommandWithFlags(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmd } } // https://docs.docker.com/reference/dockerfile/#run---mounttypebind - case m.Type == instructions.MountTypeBind && ff_bind: + case m.Type == instructions.MountTypeBind: if m.From != "" && m.From != "context" { logrus.Warnf("Kaniko does not support cross-stage bind mounts (from=%s) - skipping", m.From) continue @@ -556,8 +555,7 @@ func runCmdFilesUsedFromContext( config *v1.Config, buildArgs *dockerfile.BuildArgs, cmd *instructions.RunCommand, fileContext util.FileContext, ) ([]string, error) { - ff_bind := kConfig.FF.RunMountBind - if !ff_bind || len(cmd.FlagsUsed) == 0 { + if len(cmd.FlagsUsed) == 0 { return []string{}, nil } diff --git a/pkg/commands/volume.go b/pkg/commands/volume.go index 4d06b7d4a..85f63544a 100644 --- a/pkg/commands/volume.go +++ b/pkg/commands/volume.go @@ -17,13 +17,9 @@ limitations under the License. package commands import ( - "fmt" - "os" - v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/osscontainertools/kaniko/pkg/assert" - kconfig "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" "github.com/osscontainertools/kaniko/pkg/util" "github.com/sirupsen/logrus" @@ -51,16 +47,6 @@ func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile. var x struct{} existingVolumes[volume] = x util.AddVolumePathToIgnoreList(volume) - - if !kconfig.FF.VolumeSkipMkdir { - // Only create and snapshot the dir if it didn't exist already - if _, err := os.Stat(volume); os.IsNotExist(err) { - logrus.Infof("Creating directory %s", volume) - if err := os.MkdirAll(volume, 0o755); err != nil { - return fmt.Errorf("could not create directory for volume %s: %w", volume, err) - } - } - } } config.Volumes = existingVolumes assert.Assert("volume.count-monotone", len(config.Volumes) >= prevVolumeCount, "VOLUME must not remove volumes: count went from %d to %d", prevVolumeCount, len(config.Volumes)) diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index 95f6aec99..5dbe3d4ac 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -27,7 +27,6 @@ import ( ) type FeatureFlags struct { - BuildkitArgEnvPrecedence bool CacheLookahead bool CacheProbeAfterMiss bool ChownOnImplicitDirs bool @@ -35,35 +34,25 @@ type FeatureFlags struct { CopyAsRoot bool CopyChmodOnImplicitDirs bool CrossRepoMount bool - DeprecateInterStageRestore bool DisableHTTP2 bool ExpandHeredoc bool HashDirFraming bool - IgnoreCachedManifest bool InferCrossStageCacheKey bool - NoPropagateAnnotations bool OCIScratchBase bool - OCIWarmer bool PathScopedRegistryAuth bool PrecompileDockerignore bool - PreserveHardlinks bool - PreserveMountedPaths bool RelativeLinkTargets bool ReproduciblePreserveBaseLayers bool ResolveCacheKey bool RollingCacheKey bool RunHonorGroup bool - RunMountBind bool RunViaTini bool ScopedDockerignore bool - SecurejoinExtraction bool SharedBaseCache bool SkipCachedStages bool SkipRelabelRecompress bool SkipWriteWhiteouts bool UntarSkipRoot bool - VolumeSkipMkdir bool - WarmerCacheLock bool } var FF FeatureFlags @@ -99,7 +88,6 @@ func InitFeatureFlags() { disabledFeatureFlags = nil FF = FeatureFlags{ - BuildkitArgEnvPrecedence: featureFlag("FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE", true), CacheLookahead: featureFlag("FF_KANIKO_CACHE_LOOKAHEAD", false), CacheProbeAfterMiss: featureFlag("FF_KANIKO_CACHE_PROBE_AFTER_MISS", false), ChownOnImplicitDirs: featureFlag("FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS", false), @@ -107,35 +95,25 @@ func InitFeatureFlags() { CopyAsRoot: featureFlag("FF_KANIKO_COPY_AS_ROOT", false), CopyChmodOnImplicitDirs: featureFlag("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS", false), CrossRepoMount: featureFlag("FF_KANIKO_CROSS_REPO_MOUNT", false), - DeprecateInterStageRestore: featureFlag("FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE", true), DisableHTTP2: featureFlag("FF_KANIKO_DISABLE_HTTP2", false), ExpandHeredoc: featureFlag("FF_KANIKO_EXPAND_HEREDOC", false), HashDirFraming: featureFlag("FF_KANIKO_HASH_DIR_FRAMING", false), - IgnoreCachedManifest: featureFlag("FF_KANIKO_IGNORE_CACHED_MANIFEST", false), InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", true), - NoPropagateAnnotations: featureFlag("FF_KANIKO_NO_PROPAGATE_ANNOTATIONS", true), OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), - OCIWarmer: featureFlag("FF_KANIKO_OCI_WARMER", true), PathScopedRegistryAuth: featureFlag("FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH", false), PrecompileDockerignore: featureFlag("FF_KANIKO_PRECOMPILE_DOCKERIGNORE", false), - PreserveHardlinks: featureFlag("FF_KANIKO_PRESERVE_HARDLINKS", true), RelativeLinkTargets: featureFlag("FF_KANIKO_RELATIVE_LINK_TARGETS", true), - PreserveMountedPaths: featureFlag("FF_KANIKO_PRESERVE_MOUNTED_PATHS", true), ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", true), ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", true), RollingCacheKey: featureFlag("FF_KANIKO_ROLLING_CACHE_KEY", false), RunHonorGroup: featureFlag("FF_KANIKO_RUN_HONOR_GROUP", false), - RunMountBind: featureFlag("FF_KANIKO_RUN_MOUNT_BIND", true), RunViaTini: featureFlag("FF_KANIKO_RUN_VIA_TINI", false), ScopedDockerignore: featureFlag("FF_KANIKO_SCOPED_DOCKERIGNORE", false), - SecurejoinExtraction: featureFlag("FF_KANIKO_SECUREJOIN_EXTRACTION", true), SharedBaseCache: featureFlag("FF_KANIKO_SHARED_BASE_CACHE", true), SkipCachedStages: featureFlag("FF_KANIKO_SKIP_CACHED_STAGES", true), SkipRelabelRecompress: featureFlag("FF_KANIKO_SKIP_RELABEL_RECOMPRESS", true), SkipWriteWhiteouts: featureFlag("FF_KANIKO_SKIP_WRITE_WHITEOUTS", true), UntarSkipRoot: featureFlag("FF_KANIKO_UNTAR_SKIP_ROOT", true), - VolumeSkipMkdir: featureFlag("FF_KANIKO_VOLUME_SKIP_MKDIR", true), - WarmerCacheLock: featureFlag("FF_KANIKO_WARMER_CACHE_LOCK", true), } fields := reflect.TypeFor[FeatureFlags]().NumField() diff --git a/pkg/dockerfile/buildargs.go b/pkg/dockerfile/buildargs.go index 5973bebdf..e0e142bde 100644 --- a/pkg/dockerfile/buildargs.go +++ b/pkg/dockerfile/buildargs.go @@ -24,7 +24,6 @@ import ( "github.com/containerd/platforms" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/osscontainertools/kaniko/pkg/assert" - "github.com/osscontainertools/kaniko/pkg/config" ) // builtinAllowedBuildArgs is list of built-in allowed build args @@ -93,13 +92,8 @@ func (b *BuildArgs) ReplacementEnvs(envs []string) []string { nenvs := len(merged) args := b.GetAllAllowed() for key, val := range args { - if config.FF.BuildkitArgEnvPrecedence { - // 3344: args always override envs - merged[key] = &val - } else if _, exists := merged[key]; !exists { - // 3344: legacy behaviour, envs always override args - merged[key] = &val - } + // 3344: args always override envs + merged[key] = &val } result := make([]string, 0, len(merged)) for key, val := range merged { diff --git a/pkg/executor/build.go b/pkg/executor/build.go index f80231efa..b3893e3a9 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -737,9 +737,7 @@ func (s *stageBuilder) build(compositeKey CompositeCache, opts *config.KanikoOpt // So the only case where we don't need a filesystem is if all commands are MetadataOnly. assert.Assert("executor.build.metadata-only", command.MetadataOnly(), "build: non-MetadataOnly command %q ran without unpacked filesystem in stage %d", command.String(), s.index) } - _, isVolume := command.(*commands.VolumeCommand) - volumeCreatesFiles := isVolume && !config.FF.VolumeSkipMkdir - if command.MetadataOnly() && !opts.SingleSnapshot && !volumeCreatesFiles { + if command.MetadataOnly() && !opts.SingleSnapshot { // MetadataOnly commands must not change or even need the filesystem. assert.Assert("executor.build.without-fs", snapshotted == 0, "build: MetadataOnly command %q snapshotted %d file(s)", command.String(), snapshotted) } @@ -797,10 +795,6 @@ func takeSnapshot(files []string, shdDelete bool, opts *config.KanikoOptions, sn if files == nil || opts.SingleSnapshot { snapshot, snapshotted, err = snapshotter.TakeSnapshotFS() } else { - if !config.FF.VolumeSkipMkdir { - // Volumes are very weird. They get snapshotted in the next command. - files = append(files, util.Volumes()...) - } snapshot, snapshotted, err = snapshotter.TakeSnapshot(files, shdDelete) } t.End() @@ -1259,11 +1253,6 @@ func RenderStages(stages []config.KanikoStage, cacheInfo []*stageCacheInfo, opts printf("SAVE FILES %v %s%d\n", filesToSave, config.KanikoInterStageDepsDir, s.Index) } printf("CLEAN\n\n") - if !config.FF.DeprecateInterStageRestore { - if opts.PreserveContext && !opts.PreCleanup { - printf("RESTORE CONTEXT\n\n") - } - } } assert.Unreachable("we should always have a final stage") return retErr @@ -1303,12 +1292,8 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err != nil { return nil, err } - // legacy warmer overrides images override digest method to not return the digest - // as they get stored in a tarball and digest is lost in the process. - // But this also means that our defensive store and load here can't play nicely with them. - legacyCache := !config.FF.OCIWarmer && opts.Cache && opts.CacheDir != "" var sharedRemote map[string]bool - if config.FF.SharedBaseCache && !legacyCache { + if config.FF.SharedBaseCache { sharedRemote = sharedRemoteImages(kanikoStages, externalImageDigests, opts) } @@ -1337,9 +1322,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { return nil, fmt.Errorf("precompute: failed to get baseImage: %w", err) } } - if config.FF.NoPropagateAnnotations { - baseImage = image_util.WithoutAnnotations(baseImage) - } + baseImage = image_util.WithoutAnnotations(baseImage) args := baseArgs if stage.BaseImageStoredLocally { args = stageArgs[stage.BaseImageIndex] @@ -1516,9 +1499,7 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err != nil { return nil, fmt.Errorf("failed to get baseImage: %w", err) } - if config.FF.NoPropagateAnnotations { - baseImage = image_util.WithoutAnnotations(baseImage) - } + baseImage = image_util.WithoutAnnotations(baseImage) args := baseArgs if stage.BaseImageStoredLocally { @@ -1665,18 +1646,6 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) { if err := util.DeleteFilesystem(); err != nil { return nil, fmt.Errorf("deleting file system after stage %d: %w", stage.Index, err) } - if !config.FF.DeprecateInterStageRestore { - if opts.PreserveContext && !opts.PreCleanup { - if tarball == "" { - return nil, errors.New("context snapshot is missing") - } - _, err := util.UnpackLocalTarArchive(tarball, config.RootDir) - if err != nil { - return nil, fmt.Errorf("failed to unpack context snapshot: %w", err) - } - logrus.Info("Context restored") - } - } } assert.Unreachable("we should always have a final stage") diff --git a/pkg/util/fs_util.go b/pkg/util/fs_util.go index 1925f3322..96dec4d93 100644 --- a/pkg/util/fs_util.go +++ b/pkg/util/fs_util.go @@ -208,14 +208,11 @@ func extractLayer(i int, l v1.Layer, root string, cfg *FSConfig) ([]string, erro base := filepath.Base(path) if strings.HasPrefix(base, archive.WhiteoutPrefix) { - dir := filepath.Dir(path) - if config.FF.SecurejoinExtraction { - securePath, err := securejoin.SecureJoin(root, cleanedName) - if err != nil { - return nil, fmt.Errorf("resolving whiteout path for %q: %w", hdr.Name, err) - } - dir = filepath.Dir(securePath) + securePath, err := securejoin.SecureJoin(root, cleanedName) + if err != nil { + return nil, fmt.Errorf("resolving whiteout path for %q: %w", hdr.Name, err) } + dir := filepath.Dir(securePath) name := strings.TrimPrefix(base, archive.WhiteoutPrefix) path := filepath.Join(dir, name) @@ -300,7 +297,7 @@ func childDirInIgnoreList(path string) bool { } func removeAllSkipIgnored(path string) (skip bool, err error) { - if !config.FF.PreserveMountedPaths || !childDirInIgnoreList(path) { + if !childDirInIgnoreList(path) { return false, os.RemoveAll(path) } logrus.Debugf("Not removing %s, as it contains an ignored path", path) @@ -362,23 +359,19 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) } var path string - if config.FF.SecurejoinExtraction { - // cg330: SecureJoin the parent only, then append the basename lexically. Joining - // the full name would resolve the final component when it is a symlink we - // mean to overwrite, writing through it and creating loops like - // bin/sh -> dash -> dash. - secureDir, err := securejoin.SecureJoin(dest, filepath.Dir(cleanedName)) - if err != nil { - if !errors.Is(err, syscall.ELOOP) { - return fmt.Errorf("resolving path for %q: %w", hdr.Name, err) - } - logrus.Warnf("Skipping %q: parent path cannot be securely resolved (symlink loop)", hdr.Name) - return nil + // cg330: SecureJoin the parent only, then append the basename lexically. Joining + // the full name would resolve the final component when it is a symlink we + // mean to overwrite, writing through it and creating loops like + // bin/sh -> dash -> dash. + secureDir, err := securejoin.SecureJoin(dest, filepath.Dir(cleanedName)) + if err != nil { + if !errors.Is(err, syscall.ELOOP) { + return fmt.Errorf("resolving path for %q: %w", hdr.Name, err) } - path = filepath.Join(secureDir, filepath.Base(cleanedName)) - } else { - path = filepath.Join(dest, cleanedName) + logrus.Warnf("Skipping %q: parent path cannot be securely resolved (symlink loop)", hdr.Name) + return nil } + path = filepath.Join(secureDir, filepath.Base(cleanedName)) base := filepath.Base(path) dir := filepath.Dir(path) mode := hdr.FileInfo().Mode() @@ -448,12 +441,10 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) } case tar.TypeDir: logrus.Tracef("Creating dir %s", path) - if config.FF.SecurejoinExtraction { - fi, lerr := os.Lstat(path) - if lerr == nil && fi.Mode()&os.ModeSymlink != 0 { - if err := os.Remove(path); err != nil { - return fmt.Errorf("error removing symlink %s to make way for new directory: %w", path, err) - } + fi, lerr := os.Lstat(path) + if lerr == nil && fi.Mode()&os.ModeSymlink != 0 { + if err := os.Remove(path); err != nil { + return fmt.Errorf("error removing symlink %s to make way for new directory: %w", path, err) } } if err := MkdirAllWithPermissions(path, mode, int64(uid), int64(gid)); err != nil { @@ -495,15 +486,11 @@ func ExtractFile(dest string, hdr *tar.Header, cleanedName string, tr io.Reader) return fmt.Errorf("hardlink target %q is not allowed: references parent directory", hdr.Linkname) } var link string - if config.FF.SecurejoinExtraction { - resolved, err := securejoin.SecureJoin(dest, hdr.Linkname) - if err != nil { - return fmt.Errorf("invalid hardlink target %q: %w", hdr.Linkname, err) - } - link = resolved - } else { - link = filepath.Clean(filepath.Join(dest, hdr.Linkname)) + resolved, err := securejoin.SecureJoin(dest, hdr.Linkname) + if err != nil { + return fmt.Errorf("invalid hardlink target %q: %w", hdr.Linkname, err) } + link = resolved if err := os.Link(link, path); err != nil { return err } @@ -826,7 +813,7 @@ func CopyDir(src, dest string, context FileContext, uid, gid int64, chmod mode.S if _, err := CopySymlink(fullPath, destPath, context); err != nil { return nil, err } - } else if linkDst, ok := checkCopyHardlink(fi, destPath, hardlinksSeen); ok && config.FF.PreserveHardlinks { + } else if linkDst, ok := checkCopyHardlink(fi, destPath, hardlinksSeen); ok { // #2594: inode already copied — create a hardlink instead of duplicating content. logrus.Tracef("Creating hardlink %s -> %s", destPath, linkDst) if err := os.Link(linkDst, destPath); err != nil { diff --git a/pkg/warmer/lock.go b/pkg/warmer/lock.go index ee05a93c4..b3e720596 100644 --- a/pkg/warmer/lock.go +++ b/pkg/warmer/lock.go @@ -31,7 +31,7 @@ import ( const warmerLockDir = ".warmer-locks" // acquireCacheLock takes an exclusive flock on cacheDir/.warmer-locks/.lock -// and returns a cacheLock. It is used by warmToFile and ociWarmToFile to +// and returns a cacheLock. It is used by ociWarmToFile to // serialize the cache write for the same digest across processes sharing // the same cache volume. type cacheLock struct { diff --git a/pkg/warmer/warm.go b/pkg/warmer/warm.go index 21a5178f7..e1cad899a 100644 --- a/pkg/warmer/warm.go +++ b/pkg/warmer/warm.go @@ -59,21 +59,11 @@ func WarmCache(opts *config.WarmerOptions) error { logrus.Debugf("%s\n", images) errs := 0 - if config.FF.OCIWarmer { - for _, img := range images { - err := ociWarmToFile(cacheDir, img, opts) - if err != nil { - logrus.Warnf("Error while trying to warm image: %v %v", img, err) - errs++ - } - } - } else { - for _, img := range images { - err := warmToFile(cacheDir, img, opts) - if err != nil { - logrus.Warnf("Error while trying to warm image: %v %v", img, err) - errs++ - } + for _, img := range images { + err := ociWarmToFile(cacheDir, img, opts) + if err != nil { + logrus.Warnf("Error while trying to warm image: %v %v", img, err) + errs++ } } @@ -84,78 +74,6 @@ func WarmCache(opts *config.WarmerOptions) error { return nil } -// Download image in temporary files then move files to final destination -func warmToFile(cacheDir, img string, opts *config.WarmerOptions) error { - f, err := os.CreateTemp(cacheDir, "warmingImage.*") - if err != nil { - return err - } - // defer called in reverse order - defer os.Remove(f.Name()) - defer f.Close() - - mtfsFile, err := os.CreateTemp(cacheDir, "warmingManifest.*") - if err != nil { - return err - } - defer os.Remove(mtfsFile.Name()) - defer mtfsFile.Close() - - cw := &Warmer{ - Remote: remote.RetrieveRemoteImage, - Local: cache.LocalSource, - TarWriter: f, - ManifestWriter: mtfsFile, - } - - cacheRef, image, digest, err := cw.Resolve(img, opts) - if err != nil { - if cache.IsAlreadyCached(err) { - logrus.Infof("Image already in cache: %v", img) - return nil - } - logrus.Warnf("Error while trying to warm image: %v %v", img, err) - return err - } - - finalCachePath := path.Join(cacheDir, digest.String()) - finalMfstPath := finalCachePath + ".json" - - if config.FF.WarmerCacheLock { - lock, err := acquireCacheLock(cacheDir, digest.String()) - if err != nil { - return fmt.Errorf("failed to acquire cache lock: %w", err) - } - defer lock.Release() - - _, lookupErr := cw.Local(&opts.CacheOptions, digest.String()) - if lookupErr == nil || cache.IsExpired(lookupErr) { - logrus.Infof("Image %v became available in cache while waiting for lock; keeping existing copy", img) - return nil - } - _ = os.RemoveAll(finalCachePath) - _ = os.Remove(finalMfstPath) - } - - if err := cw.Write(cacheRef, image); err != nil { - logrus.Warnf("Error while trying to warm image: %v %v", img, err) - return err - } - - err = os.Rename(f.Name(), finalCachePath) - if err != nil { - return err - } - - err = os.Rename(mtfsFile.Name(), finalMfstPath) - if err != nil { - return fmt.Errorf("failed to rename manifest file: %w", err) - } - - logrus.Debugf("Wrote %s to cache", img) - return nil -} - // Download image in temporary files then move files to final destination func ociWarmToFile(cacheDir, img string, opts *config.WarmerOptions) error { tmp, err := os.MkdirTemp(cacheDir, "") @@ -182,23 +100,21 @@ func ociWarmToFile(cacheDir, img string, opts *config.WarmerOptions) error { finalCachePath := path.Join(cacheDir, digest.String()) - if config.FF.WarmerCacheLock { - lock, err := acquireCacheLock(cacheDir, digest.String()) - if err != nil { - return fmt.Errorf("failed to acquire cache lock: %w", err) - } - defer lock.Release() + lock, err := acquireCacheLock(cacheDir, digest.String()) + if err != nil { + return fmt.Errorf("failed to acquire cache lock: %w", err) + } + defer lock.Release() - _, lookupErr := cw.Local(&opts.CacheOptions, digest.String()) - if lookupErr == nil || cache.IsExpired(lookupErr) { - logrus.Infof("Image %v became available in cache while waiting for lock; keeping existing copy", img) - return nil - } - _ = os.RemoveAll(finalCachePath) - // mz364: finalCachePath+".json" is the legacy tarball manifest sidecar. - // Drop this once the tarball cache format is removed (FF_KANIKO_OCI_WARMER deprecated) - _ = os.Remove(finalCachePath + ".json") + _, lookupErr := cw.Local(&opts.CacheOptions, digest.String()) + if lookupErr == nil || cache.IsExpired(lookupErr) { + logrus.Infof("Image %v became available in cache while waiting for lock; keeping existing copy", img) + return nil } + _ = os.RemoveAll(finalCachePath) + // mz364: finalCachePath+".json" is the legacy tarball manifest sidecar. + // Drop this once the tarball cache format is removed (FF_KANIKO_OCI_WARMER deprecated) + _ = os.Remove(finalCachePath + ".json") if err := cw.Write(cacheRef, image); err != nil { logrus.Warnf("Error while trying to warm image: %v %v", img, err) From fb2eb71e52b4165eff904adb55106a96039b86ad Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sat, 11 Jul 2026 22:28:40 +0100 Subject: [PATCH 03/13] v1.29.0 dry-run: align README flag docs and retire OCI_WARMER, CLEAN_KANIKO_DIR --- README.md | 125 +++---------------------------------- cmd/executor/cmd/root.go | 2 +- pkg/config/featureflags.go | 2 - pkg/warmer/warm.go | 89 -------------------------- 4 files changed, 11 insertions(+), 207 deletions(-) diff --git a/README.md b/README.md index 2e894d85c..1a145bd5f 100644 --- a/README.md +++ b/README.md @@ -125,34 +125,22 @@ expect - see [Known Issues](#known-issues). - [Feature Flags](#feature-flags) - [Profiles](#profiles) - [Flag `FF_KANIKO_COPY_AS_ROOT`](#flag-ff_kaniko_copy_as_root) - - [Flag `FF_KANIKO_IGNORE_CACHED_MANIFEST`](#flag-ff_kaniko_ignore_cached_manifest) - - [Flag `FF_KANIKO_RUN_MOUNT_BIND`](#flag-ff_kaniko_run_mount_bind) - [Flag `FF_KANIKO_DISABLE_HTTP2`](#flag-ff_kaniko_disable_http2) - - [Flag `FF_KANIKO_OCI_WARMER`](#flag-ff_kaniko_oci_warmer) - [Flag `FF_KANIKO_RUN_VIA_TINI`](#flag-ff_kaniko_run_via_tini) - [Flag `FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS`](#flag-ff_kaniko_copy_chmod_on_implicit_dirs) - [Flag `FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS`](#flag-ff_kaniko_chown_on_implicit_dirs) - - [Flag `FF_KANIKO_CLEAN_KANIKO_DIR`](#flag-ff_kaniko_clean_kaniko_dir) - - [Flag `FF_KANIKO_NO_PROPAGATE_ANNOTATIONS`](#flag-ff_kaniko_no_propagate_annotations) - [Flag `FF_KANIKO_OCI_SCRATCH_BASE`](#flag-ff_kaniko_oci_scratch_base) - - [Flag `FF_KANIKO_VOLUME_SKIP_MKDIR`](#flag-ff_kaniko_volume_skip_mkdir) - - [Flag `FF_KANIKO_PRESERVE_HARDLINKS`](#flag-ff_kaniko_preserve_hardlinks) - [Flag `FF_KANIKO_RELATIVE_LINK_TARGETS`](#flag-ff_kaniko_relative_link_targets) - [Flag `FF_KANIKO_SKIP_WRITE_WHITEOUTS`](#flag-ff_kaniko_skip_write_whiteouts) - - [Flag `FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE`](#flag-ff_kaniko_buildkit_arg_env_precedence) - [Flag `FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY`](#flag-ff_kaniko_infer_cross_stage_cache_key) - [Flag `FF_KANIKO_CACHE_LOOKAHEAD`](#flag-ff_kaniko_cache_lookahead) - [Flag `FF_KANIKO_ROLLING_CACHE_KEY`](#flag-ff_kaniko_rolling_cache_key) - [Flag `FF_KANIKO_HASH_DIR_FRAMING`](#flag-ff_kaniko_hash_dir_framing) - [Flag `FF_KANIKO_CACHE_PROBE_AFTER_MISS`](#flag-ff_kaniko_cache_probe_after_miss) - - [Flag `FF_KANIKO_WARMER_CACHE_LOCK`](#flag-ff_kaniko_warmer_cache_lock) - - [Flag `FF_KANIKO_PRESERVE_MOUNTED_PATHS`](#flag-ff_kaniko_preserve_mounted_paths) - [Flag `FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS`](#flag-ff_kaniko_reproducible_preserve_base_layers) - - [Flag `FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE`](#flag-ff_kaniko_deprecate_inter_stage_restore) - [Flag `FF_KANIKO_SCOPED_DOCKERIGNORE`](#flag-ff_kaniko_scoped_dockerignore) - [Flag `FF_KANIKO_PRECOMPILE_DOCKERIGNORE`](#flag-ff_kaniko_precompile_dockerignore) - [Flag `FF_KANIKO_SKIP_RELABEL_RECOMPRESS`](#flag-ff_kaniko_skip_relabel_recompress) - - [Flag `FF_KANIKO_SECUREJOIN_EXTRACTION`](#flag-ff_kaniko_securejoin_extraction) - [Flag `FF_KANIKO_RESOLVE_CACHE_KEY`](#flag-ff_kaniko_resolve_cache_key) - [Flag `FF_KANIKO_UNTAR_SKIP_ROOT`](#flag-ff_kaniko_untar_skip_root) - [Flag `FF_KANIKO_RUN_HONOR_GROUP`](#flag-ff_kaniko_run_honor_group) @@ -1189,37 +1177,12 @@ When files are copied from context, kaniko will copy them as the current user. B Set this flag to `true` to implement COPY as specified. Defaults to `false`. Currently no plans to activate. -#### Flag `FF_KANIKO_IGNORE_CACHED_MANIFEST` - -Warmer does not only store the image as a tarball, but also the original manifest as a separate json file. -This is done to speedup manifest retrieval, but has adverse effects in some scenarios, as storing the image as a tarball actively rewrites part of the image, specifically it forces the mediatype to `vnd.docker.distribution.manifest.v2+json`. This causes the stored manifest being incompatible with the stored image. With this featureflag we ignore the manifest stored in cache and instead create the manifest from the image upon load. -Set this flag to `true` to ignore stored manifest.json in the cache directory. Defaults to `false`. -Currently no plans to activate. - -#### Flag `FF_KANIKO_RUN_MOUNT_BIND` - -Set this flag to `true` to enable bind mounts in `RUN` statements, ie. -```dockerfile -RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \ - uv pip install -r /tmp/requirements.txt -``` -cross-stage bind mounts `from=` are not yet supported. -Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_DISABLE_HTTP2` We noticed that there is a significant performance gap when using http/2.0 together with gitlab registry. Set this flag to `true` to enforce http/1.1 protocol, the same behaviour as if setting `GODEBUG="http2client=0"`. Defaults to `false`. Currently no plans to activate. -#### Flag `FF_KANIKO_OCI_WARMER` - -Warmer stores images in a tarball via go-containerregistry. However, this approach creates two problems. The tarball writer only supports dockerv2 mediatype, so building from warmer cache might result in a different output image than building from remote, as we forcefully rewrite all images to that mediatype. Secondly, the performance/usability of that approach is suboptimal, as we either store the manifest in a separate file, causing consistency issues or recalculate upon load (see [`FF_KANIKO_IGNORE_CACHED_MANIFEST`](#flag-ff_kaniko_ignore_cached_manifest)). With this change we use ocilayout instead. Ocilayout folders support arbitrary mediatypes and store the manifest alongside the image data. -Set this flag to `true` to store warmer cache images as ocilayout. Note that this flag has to be passed to both warmer and executor. -Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_RUN_VIA_TINI` Kaniko usually runs as PID1 in the container, but kaniko currently does not implement reaping of zombie processes, nor does it offload that task to the kernel. As a result, any short-lived child processes spawned by your `RUN` command may linger around as zombies and potentially cause your build to hang. @@ -1240,36 +1203,12 @@ When `WORKDIR` creates a directory whose parents do not exist yet, kaniko create Set this flag to `true` to chown every implicitly created directory to the active user, matching buildkit. Defaults to `false`. Currently no plans to activate. -#### Flag `FF_KANIKO_CLEAN_KANIKO_DIR` - -When using `--cleanup`, kaniko cleans the container filesystem at the end of the build. Set this flag to `true` to also remove kaniko's own working directory artifacts from `/kaniko` (the Dockerfile copy, build context, intermediate stages, inter-stage dependencies, layers cache, and secrets). This is useful when reusing a kaniko container across multiple builds. -Defaults to `true`. - -#### Flag `FF_KANIKO_NO_PROPAGATE_ANNOTATIONS` - -When building from a base image that carries OCI manifest annotations (e.g. `org.opencontainers.image.url`, `org.opencontainers.image.version`), kaniko by default propagates those annotations into the output image manifest. This differs from Docker/BuildKit behaviour, which does not carry base image annotations forward into derived images. -Set this flag to `true` to strip base image manifest annotations from the output, matching Docker behaviour. Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_OCI_SCRATCH_BASE` When a Dockerfile uses `FROM scratch`, kaniko uses an empty Docker-format image as the build base, which means the output image is produced in Docker manifest schema v2 format. Set this flag to `true` to use an empty OCI-format image instead, causing `FROM scratch` builds to produce output in OCI manifest schema v1 format. Defaults to `false`. Currently no plans to activate. -#### Flag `FF_KANIKO_VOLUME_SKIP_MKDIR` - -Kaniko creates the directory declared by `VOLUME` on the filesystem; Docker/BuildKit does not. -This causes a cache bug in multistage builds, the directory gets a fresh `mtime` on every run, which breaks cache hits in downstream stages. -Set this flag to `true` to skip the implicit directory creation, matching Docker/BuildKit behaviour. Defaults to `true`. -Will be deprecated in `v1.29.0`. - -#### Flag `FF_KANIKO_PRESERVE_HARDLINKS` - -When copying a directory via `COPY --from=`, kaniko copies each file independently, breaking hardlink relationships. Files that shared a single inode in the source stage become independent copies in the output image, which can significantly inflate image size for images that rely heavily on hardlinks (e.g. `git` installations where many binaries are hardlinked together). -Set this flag to `true` to preserve hardlinks during `COPY --from`. Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_RELATIVE_LINK_TARGETS` When a snapshot layer contains a hardlink, kaniko writes the link target as an absolute path while writing the entry name itself relative to the tar root. Docker writes both relative. Older clients accept either form and extract them to the same file, but docker `29.7.0` rejects the absolute one with `invalid hardlink target "/usr/bin/unzip"`, so affected images can no longer be loaded or pulled. @@ -1280,33 +1219,15 @@ Will be deprecated in `v1.29.0`. #### Flag `FF_KANIKO_SKIP_WRITE_WHITEOUTS` When kaniko extracts a cached layer it applies the layer's whiteouts by deleting the target files, but it also writes the `.wh.` marker files onto the working filesystem. With `--cache-copy-layers` a later cross-stage `COPY --from=` copies such a marker verbatim and commits it as a real whiteout, so a cache-hit build deletes a file that the cache-miss build kept. -Set this flag to `true` to skip writing the marker files, the deletion alone already applies the whiteout. Defaults to `false`. -Becomes default in `v1.29.0`. - -#### Flag `FF_KANIKO_BUILDKIT_ARG_ENV_PRECEDENCE` - -The [Dockerfile spec](https://docs.docker.com/reference/dockerfile/#using-arg-variables) states that an `ENV` instruction overrides an `ARG` of the same name. This is correct but order-dependent: "override" implies there is already a value to override, so the rule applies when `ENV` appears *after* `ARG`. Applied consistently, an `ARG` declared after an `ENV` (including one inherited from a base image) should win, which is the behaviour BuildKit implements. - -Kaniko's legacy behaviour treats `ENV` as unconditionally winning regardless of declaration order. Enable this flag to match BuildKit semantics, where the later declaration takes precedence: - -```dockerfile -FROM alpine AS base -ENV HELLO=upstream - -FROM base AS child -ARG HELLO -RUN echo $HELLO # prints the --build-arg value, not "upstream" -``` - -Set this flag to `true` to enable BuildKit-compatible ARG/ENV precedence. Defaults to `true`. -Will be deprecated in `v1.29.0`. +Set this flag to `true` to skip writing the marker files, the deletion alone already applies the whiteout. Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY` When a multi-stage build uses `COPY --from=`, kaniko normally hashes the copied files from the source stage's filesystem to compute the downstream cache key. The source stage's `finalCacheKey` is a deterministic function of its build inputs and can be used as a stable proxy for those file contents, so the downstream cache key can be inferred without accessing the filesystem at all. This is a preparatory optimisation for a future change that will avoid unpacking the source stage's filesystem entirely when all downstream stages are also fully cached. Set this flag to `true` to add additional cache entries for the shortcuts, currently they do not yet allow optimization. -Requires `--cache-copy-layers`. Defaults to `false`. -Becomes default in `v1.29.0`. +Requires `--cache-copy-layers`. Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_CACHE_LOOKAHEAD` @@ -1337,31 +1258,12 @@ Set this flag to `true` to keep probing the cache after a miss. Cached tar diffs The other `stopCache` site — `COPY --from` in the precompute pass — is intentionally not affected by this flag. That one signals "key cannot be computed without the file context", not a transient miss, and is required for correctness. Defaults to `false`. -#### Flag `FF_KANIKO_WARMER_CACHE_LOCK` - -Multiple warmer processes sharing a cache volume can race when warming the same image; with [`FF_KANIKO_OCI_WARMER`](#flag-ff_kaniko_oci_warmer) one of them may exit with an error. -Set this flag to `true` to coordinate concurrent warmers and avoid redundant downloads. Corrupt or wrong-format cache entries are detected and replaced, making [`FF_KANIKO_OCI_WARMER`](#flag-ff_kaniko_oci_warmer) toggles transparent. -Defaults to `true`. -Will be deprecated in `v1.29.0`. - -#### Flag `FF_KANIKO_PRESERVE_MOUNTED_PATHS` - -When a container runtime bind-mounts files read-only into the build container — as the NVIDIA GPU operator does with driver artifacts (`nvidia-smi`, `libnvidia*`, firmware blobs) on GPU nodes — and a base image layer ships a directory along that mount path as a symlink, kaniko `os.RemoveAll`s the directory while unpacking to make way for the symlink. The recursive remove hits the read-only bind mount and the build fails with `unlinkat ...: device or resource busy`. -Set this flag to `true` to skip removing a directory that contains a mounted (ignored) path: its other contents are still cleared, but the mount is preserved and the conflicting layer entry is left in place, matching how `DeleteFilesystem` already treats mounts. Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS` `--reproducible` re-tars every layer to zero its timestamps, including layers inherited from the `FROM` image. Base-layer blobs get fresh digests on every build and stop matching the upstream registry, defeating layer reuse even though kaniko changed nothing in them. Set this flag to `true` to re-time only kaniko-appended layers and pass base layers through unchanged. -Defaults to `false`. -Becomes default in `v1.29.0`. - -#### Flag `FF_KANIKO_DEPRECATE_INTER_STAGE_RESTORE` - -Deprecates the inter-stage restore performed by [`--preserve-context`](#flag---preserve-context) when used without [`--pre-cleanup`](#flag---pre-cleanup). Set to `1` to fully disable the restore between stages. The original motivation, smuggling secrets across stages, is now served by `RUN --mount=type=secret`. Defaults to `true`. -Will be deprecated in `v1.29.0`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_SCOPED_DOCKERIGNORE` @@ -1379,29 +1281,22 @@ Becomes default in `v1.29.0`. A `COPY`, `ADD` or `WORKDIR` layer cache key is built from the raw instruction text, so build args and env expanded in the instruction (for example `COPY foo /$A/foo` or `WORKDIR /$A/foo`) do not enter the key. A build that only changes such a variable can hit stale cache entries. Set this flag to `true` to interpolate build args and env. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_SKIP_RELABEL_RECOMPRESS` When a cached layer is reused in an image of a different media-type vendor, kaniko not only relabels the layer but re-gzips it too. if the compression is unchanged, the re-encoded blob is byte-identical to the original. Set this flag to `true` to skip the unecessary re-compression and serve the relabeled already-compressed blob. -Defaults to `false`. -Becomes default in `v1.29.0`. - -#### Flag `FF_KANIKO_SECUREJOIN_EXTRACTION` - -When unpacking image layers kaniko joins each tar entry path lexically, so a malicious base image can ship an escaping symlink followed by a write through it and land the write outside the extraction root, for example overwriting `/kaniko/tini` to gain RCE. -Set this flag to `true` to resolve each entry's parent with SecureJoin so the write stays contained inside the destination. Defaults to `true`. -Will be deprecated in `v1.29.0`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_UNTAR_SKIP_ROOT` When `ADD` extracts a local tar archive into a directory, kaniko applies the archive's root `.` entry to the destination directory and overwrites its mode and ownership, while docker leaves the destination untouched. Set this flag to `true` to skip the root `.` entry when untarring. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_RUN_HONOR_GROUP` diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 4145b0936..24cd4a750 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -224,7 +224,7 @@ var RootCmd = &cobra.Command{ if err := os.Chdir("/"); err != nil { exit(fmt.Errorf("error changing to root dir: %w", err)) } - if opts.Cleanup && config.FF.CleanKanikoDir { + if opts.Cleanup { defer func() { if err := config.Cleanup(); err != nil { logrus.Warnf("error cleaning kaniko dir: %v", err) diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index 5dbe3d4ac..d1f5d3612 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -30,7 +30,6 @@ type FeatureFlags struct { CacheLookahead bool CacheProbeAfterMiss bool ChownOnImplicitDirs bool - CleanKanikoDir bool CopyAsRoot bool CopyChmodOnImplicitDirs bool CrossRepoMount bool @@ -91,7 +90,6 @@ func InitFeatureFlags() { CacheLookahead: featureFlag("FF_KANIKO_CACHE_LOOKAHEAD", false), CacheProbeAfterMiss: featureFlag("FF_KANIKO_CACHE_PROBE_AFTER_MISS", false), ChownOnImplicitDirs: featureFlag("FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS", false), - CleanKanikoDir: featureFlag("FF_KANIKO_CLEAN_KANIKO_DIR", true), CopyAsRoot: featureFlag("FF_KANIKO_COPY_AS_ROOT", false), CopyChmodOnImplicitDirs: featureFlag("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS", false), CrossRepoMount: featureFlag("FF_KANIKO_CROSS_REPO_MOUNT", false), diff --git a/pkg/warmer/warm.go b/pkg/warmer/warm.go index e1cad899a..c58576562 100644 --- a/pkg/warmer/warm.go +++ b/pkg/warmer/warm.go @@ -29,7 +29,6 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/layout" - "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/osscontainertools/kaniko/pkg/cache" "github.com/osscontainertools/kaniko/pkg/config" "github.com/osscontainertools/kaniko/pkg/dockerfile" @@ -112,9 +111,6 @@ func ociWarmToFile(cacheDir, img string, opts *config.WarmerOptions) error { return nil } _ = os.RemoveAll(finalCachePath) - // mz364: finalCachePath+".json" is the legacy tarball manifest sidecar. - // Drop this once the tarball cache format is removed (FF_KANIKO_OCI_WARMER deprecated) - _ = os.Remove(finalCachePath + ".json") if err := cw.Write(cacheRef, image); err != nil { logrus.Warnf("Error while trying to warm image: %v %v", img, err) @@ -140,91 +136,6 @@ type FetchRemoteImage func(image string, opts config.RegistryOptions, customPlat // this type. type FetchLocalSource func(*config.CacheOptions, string) (v1.Image, error) -// Warmer is used to prepopulate the cache with a Docker image -type Warmer struct { - Remote FetchRemoteImage - Local FetchLocalSource - TarWriter io.Writer - ManifestWriter io.Writer -} - -// Resolve fetches the image manifest and resolves its digest, short-circuiting -// with AlreadyCachedErr if the local cache already holds it. -func (w *Warmer) Resolve(image string, opts *config.WarmerOptions) (name.Reference, v1.Image, v1.Hash, error) { - cacheRef, err := name.ParseReference(image, name.WeakValidation) - if err != nil { - return nil, nil, v1.Hash{}, fmt.Errorf("failed to verify image name: %s: %w", image, err) - } - - // mz320: If we have a digest reference, we can try a cache lookup directly. - var oldKey string - var oldErr error - if !opts.Force { - if d, ok := cacheRef.(name.Digest); ok { - cacheKey := d.DigestStr() - _, err := w.Local(&opts.CacheOptions, cacheKey) - if err == nil || cache.IsExpired(err) { - return nil, nil, v1.Hash{}, cache.AlreadyCachedErr{} - } else { - // mz320: But in case it is a cache miss, not all hope is lost. - // It could have also been the digest for an image-index. - // The thin wrapper that only points to the image-manifests for different archs. - // Unfortunately we can't tell a-priori and we only store the image manifests as keys. - // Therefore we don't return and instead try a remote lookup again. - oldKey = cacheKey - oldErr = err - } - } - } - - img, err := w.Remote(image, opts.RegistryOptions, opts.CustomPlatform) - if err != nil || img == nil { - return nil, nil, v1.Hash{}, fmt.Errorf("failed to retrieve image: %s: %w", image, err) - } - - digest, err := img.Digest() - if err != nil { - return nil, nil, v1.Hash{}, fmt.Errorf("failed to retrieve digest: %s: %w", image, err) - } - - if !opts.Force { - var err error - cacheKey := digest.String() - if oldKey != "" && cacheKey == oldKey { - // mz320: But if the cacheKey didn't change, we indeed were looking - // at an image manifest, we already confirmed it is not in cache, - // so we can short-circuit with the previous error here. - err = oldErr - } else { - _, err = w.Local(&opts.CacheOptions, cacheKey) - } - if err == nil || cache.IsExpired(err) { - return nil, nil, v1.Hash{}, cache.AlreadyCachedErr{} - } - } - - return cacheRef, img, digest, nil -} - -// Write streams the image as a Docker tarball to TarWriter and its raw -// manifest to ManifestWriter. -func (w *Warmer) Write(cacheRef name.Reference, img v1.Image) error { - if err := tarball.Write(cacheRef, img, w.TarWriter); err != nil { - return fmt.Errorf("failed to write %s to tar buffer: %w", cacheRef.String(), err) - } - - mfst, err := img.RawManifest() - if err != nil { - return fmt.Errorf("failed to retrieve manifest for %s: %w", cacheRef.String(), err) - } - - if _, err := w.ManifestWriter.Write(mfst); err != nil { - return fmt.Errorf("failed to save manifest to buffer for %s: %w", cacheRef.String(), err) - } - - return nil -} - type OciWarmer struct { Remote FetchRemoteImage Local FetchLocalSource From 8be68b81a0146c28752114991bf3ac55659d8853 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sat, 11 Jul 2026 23:03:50 +0100 Subject: [PATCH 04/13] v1.29.0 dry-run: drop mz507 base-annotation workaround, now redundant --- pkg/executor/build.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/executor/build.go b/pkg/executor/build.go index b3893e3a9..baddcbdad 100644 --- a/pkg/executor/build.go +++ b/pkg/executor/build.go @@ -180,17 +180,6 @@ func newStageBuilder(sourceImage v1.Image, args *dockerfile.BuildArgs, opts *con return nil, err } - // mz507: This workaround to prevent cache invalidation via base image annotations - // can be removed once FF_KANIKO_NO_PROPAGATE_ANNOTATIONS becomes standard. - man, err := sourceImage.Manifest() - if err != nil { - return nil, err - } - ann := map[string]string{} - for k := range man.Annotations { - ann[k] = "" - } - cf, err := sourceImage.ConfigFile() if err != nil { return nil, err @@ -203,7 +192,6 @@ func newStageBuilder(sourceImage v1.Image, args *dockerfile.BuildArgs, opts *con return nil, err } - sourceImageReproducible = mutate.Annotations(sourceImageReproducible, ann).(v1.Image) digest, err := sourceImageReproducible.Digest() if err != nil { return nil, err From 178d58c81ea3ba7be1a8af8d4db490bcb15f3190 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sat, 11 Jul 2026 23:54:32 +0100 Subject: [PATCH 05/13] adapt unit tests to the v1.29.0 default feature flags --- pkg/executor/build_test.go | 6 +- pkg/executor/fakes_test.go | 8 +- pkg/util/fs_util_test.go | 252 ------------------------------------- 3 files changed, 10 insertions(+), 256 deletions(-) diff --git a/pkg/executor/build_test.go b/pkg/executor/build_test.go index caab7a7f8..3dbf86dee 100644 --- a/pkg/executor/build_test.go +++ b/pkg/executor/build_test.go @@ -607,7 +607,7 @@ func Test_stageBuilder_optimize(t *testing.T) { cacheCommand: MockCachedDockerCommand{}, } sb.cmds = []commands.DockerCommand{command} - _, _, _, err = sb.optimize(&ck, cf.Config, sb.args, tc.opts, util.FileContext{}, lc, nil, nil, true) + _, _, _, err = sb.optimize(&ck, cf.Config, sb.args, tc.opts, util.FileContext{}, lc, map[int]string{}, nil, true) if err != nil { t.Errorf("Expected error to be nil but was %v", err) } @@ -1480,11 +1480,11 @@ RUN foobar getFSFromImage = tc.mockGetFSFromImage } compositeKey := NewCompositeCache(sb.baseImageDigest) - _, _, _, err := sb.optimize(compositeKey, sb.cf.Config, sb.args, tc.opts, util.FileContext{}, lc, nil, nil, true) + _, _, _, err := sb.optimize(compositeKey, sb.cf.Config, sb.args, tc.opts, util.FileContext{}, lc, map[int]string{}, nil, true) if err != nil { t.Errorf("failed to optimize instructions: %v", err) } - err = sb.build(*compositeKey, tc.opts, util.FileContext{}, snap, tc.crossStageDeps, nil, nil, lc) + err = sb.build(*compositeKey, tc.opts, util.FileContext{}, snap, tc.crossStageDeps, map[int]string{}, nil, lc) if err != nil { t.Errorf("Expected error to be nil but was %v", err) } diff --git a/pkg/executor/fakes_test.go b/pkg/executor/fakes_test.go index f8b5f7bf0..a711797b9 100644 --- a/pkg/executor/fakes_test.go +++ b/pkg/executor/fakes_test.go @@ -181,7 +181,13 @@ type fakeLayer struct { } func (f fakeLayer) Digest() (v1.Hash, error) { - return v1.Hash{}, nil + rc, err := f.Compressed() + if err != nil { + return v1.Hash{}, err + } + defer rc.Close() + h, _, err := v1.SHA256(rc) + return h, err } func (f fakeLayer) DiffID() (v1.Hash, error) { diff --git a/pkg/util/fs_util_test.go b/pkg/util/fs_util_test.go index fb6611073..dc31b38e7 100644 --- a/pkg/util/fs_util_test.go +++ b/pkg/util/fs_util_test.go @@ -1435,109 +1435,6 @@ func fakeExtract(_ string, _ *tar.Header, _ string, _ io.Reader) error { return nil } -func Test_GetFSFromLayers_with_whiteouts_include_whiteout_enabled(t *testing.T) { - _original := FSys - FSys = OSFS{} - defer func() { FSys = _original }() - - resetMountInfoFile := provideEmptyMountinfoFile() - defer resetMountInfoFile() - - ctrl := gomock.NewController(t) - - root := t.TempDir() - // Write a whiteout path - d1 := []byte("Hello World\n") - if err := os.WriteFile(filepath.Join(root, "foobar"), d1, 0o644); err != nil { - t.Fatal(err) - } - - opts := []FSOpt{ - // I'd rather use the real func (util.ExtractFile) - // but you have to be root to chown - ExtractFunc(fakeExtract), - IncludeWhiteout(), - } - - expectErr := false - - f := func(expectedFiles []string, tw *tar.Writer) { - for _, f := range expectedFiles { - f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") - - hdr := &tar.Header{ - Name: f, - Mode: 0o644, - Size: int64(len("Hello World\n")), - } - - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - if _, err := tw.Write([]byte("Hello World\n")); err != nil { - t.Fatal(err) - } - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - } - - expectedFiles := []string{ - filepath.Join(root, "foobar"), - } - - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - - f(expectedFiles, tw) - - mockLayer := NewMockLayer(ctrl) - mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) - - rc := io.NopCloser(buf) - mockLayer.EXPECT().Uncompressed().Return(rc, nil) - - secondLayerFiles := []string{ - filepath.Join(root, ".wh.foobar"), - } - - buf = new(bytes.Buffer) - tw = tar.NewWriter(buf) - - f(secondLayerFiles, tw) - - mockLayer2 := NewMockLayer(ctrl) - mockLayer2.EXPECT().MediaType().Return(types.OCILayer, nil) - - rc = io.NopCloser(buf) - mockLayer2.EXPECT().Uncompressed().Return(rc, nil) - - layers := []v1.Layer{ - mockLayer, - mockLayer2, - } - - expectedFiles = append(expectedFiles, secondLayerFiles...) - - actualFiles, err := GetFSFromLayers(root, layers, opts...) - - assertGetFSFromLayers( - t, - actualFiles, - expectedFiles, - err, - expectErr, - ) - // Make sure whiteout files are removed form the root. - _, err = os.Lstat(filepath.Join(root, "foobar")) - if err == nil || !os.IsNotExist(err) { - t.Errorf("expected whiteout foobar file to be deleted. However found it.") - } -} - func provideEmptyMountinfoFile() func() { // Provide empty mountinfo file to prevent /tmp from ending up in ignore list on // distributions with /tmp mountpoint. Otherwise, tests expecting operations in /tmp @@ -1655,155 +1552,6 @@ func Test_GetFSFromLayers_with_whiteouts_include_whiteout_disabled(t *testing.T) } } -func Test_GetFSFromLayers_ignorelist(t *testing.T) { - _original := FSys - FSys = OSFS{} - defer func() { FSys = _original }() - - resetMountInfoFile := provideEmptyMountinfoFile() - defer resetMountInfoFile() - - ctrl := gomock.NewController(t) - - root := t.TempDir() - // Write a whiteout path - fileContents := []byte("Hello World\n") - if err := os.Mkdir(filepath.Join(root, "testdir"), 0o775); err != nil { - t.Fatal(err) - } - - opts := []FSOpt{ - // I'd rather use the real func (util.ExtractFile) - // but you have to be root to chown - ExtractFunc(fakeExtract), - IncludeWhiteout(), - } - - f := func(expectedFiles []string, tw *tar.Writer) { - for _, f := range expectedFiles { - f := strings.TrimPrefix(strings.TrimPrefix(f, root), "/") - - hdr := &tar.Header{ - Name: f, - Mode: 0o644, - Size: int64(len(string(fileContents))), - } - - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - if _, err := tw.Write(fileContents); err != nil { - t.Fatal(err) - } - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - } - - // first, testdir is not in ignorelist, so it should be deleted - expectedFiles := []string{ - filepath.Join(root, ".wh.testdir"), - filepath.Join(root, "testdir", "file"), - filepath.Join(root, "other-file"), - } - - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - - f(expectedFiles, tw) - - mockLayer := NewMockLayer(ctrl) - mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) - layerFiles := []string{ - filepath.Join(root, ".wh.testdir"), - filepath.Join(root, "testdir", "file"), - filepath.Join(root, "other-file"), - } - buf = new(bytes.Buffer) - tw = tar.NewWriter(buf) - - f(layerFiles, tw) - - rc := io.NopCloser(buf) - mockLayer.EXPECT().Uncompressed().Return(rc, nil) - - layers := []v1.Layer{ - mockLayer, - } - - actualFiles, err := GetFSFromLayers(root, layers, opts...) - assertGetFSFromLayers( - t, - actualFiles, - expectedFiles, - err, - false, - ) - - // Make sure whiteout files are removed form the root. - _, err = os.Lstat(filepath.Join(root, "testdir")) - if err == nil || !os.IsNotExist(err) { - t.Errorf("expected testdir to be deleted. However found it.") - } - - // second, testdir is in ignorelist, so it should not be deleted - original := append([]IgnoreListEntry{}, defaultIgnoreList...) - defer func() { - defaultIgnoreList = original - }() - defaultIgnoreList = append(defaultIgnoreList, IgnoreListEntry{ - Path: filepath.Join(root, "testdir"), - }) - if err := os.Mkdir(filepath.Join(root, "testdir"), 0o775); err != nil { - t.Fatal(err) - } - - expectedFiles = []string{ - filepath.Join(root, "other-file"), - } - - buf = new(bytes.Buffer) - tw = tar.NewWriter(buf) - - f(expectedFiles, tw) - - mockLayer = NewMockLayer(ctrl) - mockLayer.EXPECT().MediaType().Return(types.OCILayer, nil) - layerFiles = []string{ - filepath.Join(root, ".wh.testdir"), - filepath.Join(root, "other-file"), - } - buf = new(bytes.Buffer) - tw = tar.NewWriter(buf) - - f(layerFiles, tw) - - rc = io.NopCloser(buf) - mockLayer.EXPECT().Uncompressed().Return(rc, nil) - - layers = []v1.Layer{ - mockLayer, - } - - actualFiles, err = GetFSFromLayers(root, layers, opts...) - assertGetFSFromLayers( - t, - actualFiles, - expectedFiles, - err, - false, - ) - - // Make sure testdir still exists. - _, err = os.Lstat(filepath.Join(root, "testdir")) - if err != nil { - t.Errorf("expected testdir to exist, but could not Lstat it: %v", err) - } -} - func Test_GetFSFromLayers(t *testing.T) { ctrl := gomock.NewController(t) From 4b8f0a75098680a5cd61812625d28d2320853e03 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Mon, 27 Jul 2026 09:51:10 +0100 Subject: [PATCH 06/13] v1.29.0 dry-run: flip the remaining switch-on feature flags to default true --- README.md | 36 ++++++++++-------------------------- pkg/config/featureflags.go | 10 +++++----- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1a145bd5f..3bfc88f79 100644 --- a/README.md +++ b/README.md @@ -1142,20 +1142,7 @@ Opting into the Preview profile gives you early access to upcoming performance i ```sh FF_KANIKO_CROSS_REPO_MOUNT=true -FF_KANIKO_EXPAND_HEREDOC=true -FF_KANIKO_HASH_DIR_FRAMING=true -FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY=true -FF_KANIKO_PRECOMPILE_DOCKERIGNORE=true -FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS=true -FF_KANIKO_RESOLVE_CACHE_KEY=true -FF_KANIKO_ROLLING_CACHE_KEY=true -FF_KANIKO_RUN_HONOR_GROUP=true FF_KANIKO_RUN_VIA_TINI=true -FF_KANIKO_SHARED_BASE_CACHE=true -FF_KANIKO_SKIP_CACHED_STAGES=true -FF_KANIKO_SKIP_RELABEL_RECOMPRESS=true -FF_KANIKO_SKIP_WRITE_WHITEOUTS=true -FF_KANIKO_UNTAR_SKIP_ROOT=true ``` ##### BuildKit compatibility @@ -1166,9 +1153,6 @@ In a few places, Kaniko keeps its historical, non-compliant behaviour instead of FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS=true FF_KANIKO_COPY_AS_ROOT=true FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS=true -FF_KANIKO_EXPAND_HEREDOC=true -FF_KANIKO_RUN_HONOR_GROUP=true -FF_KANIKO_UNTAR_SKIP_ROOT=true ``` #### Flag `FF_KANIKO_COPY_AS_ROOT` @@ -1239,15 +1223,15 @@ Defaults to `false`. By default the composite cache joins all inputs with `-` and hashes the result. Since `-` is a legal input too, different sequences can join to the same text and collide, silently picking up the wrong cache layer. This flag switches to a rolling hash, think git, where `state = SHA256(state || input)`. This makes boundaries unambiguous and prevents collisions. It also prevents the hash input from growing, so key computation gets marginally cheaper. Toggling it changes every cache key, forcing a rebuild from scratch. Set this flag to `true` to enable the rolling cache key. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_HASH_DIR_FRAMING` Directory cache keys concatenate each relative path and file hash without recording their boundaries. A filename can therefore absorb an adjacent hash and make distinct directory trees produce the same cache key, silently reusing the wrong cached layer. Set this flag to `true` to length-prefix every path and file hash before hashing the directory. Toggling it changes cache keys for `COPY` and `ADD` directory inputs, forcing those layers to rebuild once. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_CACHE_PROBE_AFTER_MISS` @@ -1274,8 +1258,8 @@ Currently no plans to activate. #### Flag `FF_KANIKO_PRECOMPILE_DOCKERIGNORE` `.dockerignore` rebuilds the pattern matcher and recompiles every glob to a regexp on every file. Set this flag to `true` to pre-compile the matcher instead. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_RESOLVE_CACHE_KEY` @@ -1302,15 +1286,15 @@ Will be deprecated in `v1.30.0`. When a stage sets `USER user:group`, `RUN` applies only the user and the gid falls back to the user's primary group, so an explicit group is silently dropped. Set this flag to `true` to pass the full `user:group` to the command so `RUN` runs with the requested group, matching docker. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_EXPAND_HEREDOC` Docker applies Dockerfile word-expansion to a `COPY` or `ADD` heredoc body when the delimiter is unquoted, so `${VAR}` expands and `\${VAR}` keeps the literal text. A quoted delimiter (`<<'EOF'`) leaves the body verbatim. kaniko writes the body verbatim in every case, so the expanded files diverge from Docker. Set this flag to `true` to expand build args and env in unquoted `COPY` and `ADD` heredoc bodies. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_SKIP_CACHED_STAGES` diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index d1f5d3612..e27794dfe 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -94,17 +94,17 @@ func InitFeatureFlags() { CopyChmodOnImplicitDirs: featureFlag("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS", false), CrossRepoMount: featureFlag("FF_KANIKO_CROSS_REPO_MOUNT", false), DisableHTTP2: featureFlag("FF_KANIKO_DISABLE_HTTP2", false), - ExpandHeredoc: featureFlag("FF_KANIKO_EXPAND_HEREDOC", false), - HashDirFraming: featureFlag("FF_KANIKO_HASH_DIR_FRAMING", false), + ExpandHeredoc: featureFlag("FF_KANIKO_EXPAND_HEREDOC", true), + HashDirFraming: featureFlag("FF_KANIKO_HASH_DIR_FRAMING", true), InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", true), OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), PathScopedRegistryAuth: featureFlag("FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH", false), - PrecompileDockerignore: featureFlag("FF_KANIKO_PRECOMPILE_DOCKERIGNORE", false), + PrecompileDockerignore: featureFlag("FF_KANIKO_PRECOMPILE_DOCKERIGNORE", true), RelativeLinkTargets: featureFlag("FF_KANIKO_RELATIVE_LINK_TARGETS", true), ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", true), ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", true), - RollingCacheKey: featureFlag("FF_KANIKO_ROLLING_CACHE_KEY", false), - RunHonorGroup: featureFlag("FF_KANIKO_RUN_HONOR_GROUP", false), + RollingCacheKey: featureFlag("FF_KANIKO_ROLLING_CACHE_KEY", true), + RunHonorGroup: featureFlag("FF_KANIKO_RUN_HONOR_GROUP", true), RunViaTini: featureFlag("FF_KANIKO_RUN_VIA_TINI", false), ScopedDockerignore: featureFlag("FF_KANIKO_SCOPED_DOCKERIGNORE", false), SharedBaseCache: featureFlag("FF_KANIKO_SHARED_BASE_CACHE", true), From fe41a3c35cff50d2fd2d5f864f0ee8304677c387 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Mon, 27 Jul 2026 11:38:09 +0100 Subject: [PATCH 07/13] adapt unit and golden tests to the remaining v1.29.0 default feature flags --- golden/testdata/test_issue_mz703/test.go | 8 ++--- golden/testdata/test_issue_mz791/test.go | 2 ++ golden/testdata/test_issue_mz813/test.go | 6 ++-- golden/testdata/test_issue_mz822/test.go | 10 +++--- pkg/executor/build_test.go | 40 +++++++++++++----------- pkg/executor/composite_cache_test.go | 2 +- 6 files changed, 38 insertions(+), 30 deletions(-) diff --git a/golden/testdata/test_issue_mz703/test.go b/golden/testdata/test_issue_mz703/test.go index b045d42be..981346838 100644 --- a/golden/testdata/test_issue_mz703/test.go +++ b/golden/testdata/test_issue_mz703/test.go @@ -12,8 +12,8 @@ var Tests = types.GoldenTests{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", }, CachedKeys: []string{ - "ef4ba1bfa1a8010630d9a007fad694d95d88419c791f0053b5525169f21e3247", - "2f4043dde38e8a86a388c786d43c46606463533a2eca79177a7246698f9b62a7", + "85da41b943971c4d3e09af20264c54b75dde9593e8104ffab77fbb86896e4756", + "52b7a07c8f6ea53bb7b5591998838ffb46212e21e322a98bb88fe3fe8c1d0fca", }, Plan: "legacy_stop_after_miss", }, @@ -24,8 +24,8 @@ var Tests = types.GoldenTests{ "FF_KANIKO_CACHE_PROBE_AFTER_MISS": "1", }, CachedKeys: []string{ - "ef4ba1bfa1a8010630d9a007fad694d95d88419c791f0053b5525169f21e3247", - "2f4043dde38e8a86a388c786d43c46606463533a2eca79177a7246698f9b62a7", + "85da41b943971c4d3e09af20264c54b75dde9593e8104ffab77fbb86896e4756", + "52b7a07c8f6ea53bb7b5591998838ffb46212e21e322a98bb88fe3fe8c1d0fca", }, Plan: "probe_after_miss", }, diff --git a/golden/testdata/test_issue_mz791/test.go b/golden/testdata/test_issue_mz791/test.go index bbe17f3a5..a8985aaea 100644 --- a/golden/testdata/test_issue_mz791/test.go +++ b/golden/testdata/test_issue_mz791/test.go @@ -14,6 +14,7 @@ var Tests = types.GoldenTests{ Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved_one", }, @@ -22,6 +23,7 @@ var Tests = types.GoldenTests{ Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved_two", }, diff --git a/golden/testdata/test_issue_mz813/test.go b/golden/testdata/test_issue_mz813/test.go index 948ff7f1c..e793c9604 100644 --- a/golden/testdata/test_issue_mz813/test.go +++ b/golden/testdata/test_issue_mz813/test.go @@ -12,14 +12,16 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--build-arg", "A=one"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved_one", }, { Args: []string{"--no-push", "--cache", "--build-arg", "A=two"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved_two", }, diff --git a/golden/testdata/test_issue_mz822/test.go b/golden/testdata/test_issue_mz822/test.go index bafabd70c..75aed38ca 100644 --- a/golden/testdata/test_issue_mz822/test.go +++ b/golden/testdata/test_issue_mz822/test.go @@ -13,16 +13,18 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--build-arg", "A=one"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved", }, { Args: []string{"--no-push", "--cache", "--build-arg", "A=two"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", }, Plan: "unresolved", }, diff --git a/pkg/executor/build_test.go b/pkg/executor/build_test.go index 3dbf86dee..fa7ea6a8f 100644 --- a/pkg/executor/build_test.go +++ b/pkg/executor/build_test.go @@ -607,7 +607,7 @@ func Test_stageBuilder_optimize(t *testing.T) { cacheCommand: MockCachedDockerCommand{}, } sb.cmds = []commands.DockerCommand{command} - _, _, _, err = sb.optimize(&ck, cf.Config, sb.args, tc.opts, util.FileContext{}, lc, map[int]string{}, nil, true) + _, _, _, err = sb.optimize(&ck, cf.Config, sb.args, tc.opts, util.FileContext{}, lc, nil, nil, true) if err != nil { t.Errorf("Expected error to be nil but was %v", err) } @@ -1025,7 +1025,7 @@ func Test_stageBuilder_build(t *testing.T) { tarContent := generateTar(t, dir, filename) - ch := NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) + ch := NewCompositeCache("", resolvedKey(fmt.Sprintf("COPY %s foo.txt", filename))) ch.AddPath(filepath, util.FileContext{}) hash, err := ch.Hash() @@ -1091,7 +1091,7 @@ func Test_stageBuilder_build(t *testing.T) { tarContent := []byte{} destDir := t.TempDir() filePath := filepath.Join(dir, filename) - ch := NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) + ch := NewCompositeCache("", resolvedKey(fmt.Sprintf("COPY %s foo.txt", filename))) ch.AddPath(filePath, util.FileContext{}) hash, err := ch.Hash() @@ -1149,22 +1149,22 @@ COPY %s foo.txt destDir := t.TempDir() filePath := filepath.Join(dir, filename) - ch := NewCompositeCache("", "RUN foobar") + ch := NewCompositeCache("", resolvedKey("RUN foobar")) hash1, err := ch.Hash() if err != nil { t.Errorf("couldn't create hash %v", err) } - ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) + ch.AddKey(resolvedKey(fmt.Sprintf("COPY %s bar.txt", filename))) ch.AddPath(filePath, util.FileContext{}) hash2, err := ch.Hash() if err != nil { t.Errorf("couldn't create hash %v", err) } - ch = NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename)) - ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename)) + ch = NewCompositeCache("", resolvedKey(fmt.Sprintf("COPY %s foo.txt", filename))) + ch.AddKey(resolvedKey(fmt.Sprintf("COPY %s bar.txt", filename))) ch.AddPath(filePath, util.FileContext{}) image := fakeImage{ @@ -1223,7 +1223,7 @@ COPY %s bar.txt filePath := filepath.Join(dir, filename) - ch := NewCompositeCache("", fmt.Sprintf("COPY %s bar.txt", filename)) + ch := NewCompositeCache("", resolvedKey(fmt.Sprintf("COPY %s bar.txt", filename))) ch.AddPath(filePath, util.FileContext{}) // copy hash @@ -1232,7 +1232,7 @@ COPY %s bar.txt t.Errorf("couldn't create hash %v", err) } - ch.AddKey("RUN foobar") + ch.AddKey(resolvedKey("RUN foobar")) // run hash runHash, err := ch.Hash() @@ -1543,6 +1543,11 @@ func getCommands(fileContext util.FileContext, cmds []instructions.Command, cach return outCommands } +// resolvedKey frames an instruction the way FF_KANIKO_RESOLVE_CACHE_KEY keys it. +func resolvedKey(instruction string) string { + return fmt.Sprintf("%d:%s", len(instruction), instruction) +} + func tempDirAndFile(t *testing.T) (string, []string) { filenames := []string{"bar.txt"} @@ -1606,20 +1611,17 @@ func Test_stageBuild_populateCompositeKeyForCopyCommand(t *testing.T) { // See https://github.com/GoogleContainerTools/kaniko/issues/589 for _, tc := range []struct { - description string - command string - expectedCacheKey string + description string + command string }{ { description: "multi-stage copy command", // dont use digest from previoust stage for COPY - command: "COPY --from=0 foo.txt bar.txt", - expectedCacheKey: "COPY --from=0 foo.txt bar.txt", + command: "COPY --from=0 foo.txt bar.txt", }, { - description: "copy command", - command: "COPY foo.txt bar.txt", - expectedCacheKey: "COPY foo.txt bar.txt", + description: "copy command", + command: "COPY foo.txt bar.txt", }, } { t.Run(tc.description, func(t *testing.T) { @@ -1657,10 +1659,10 @@ func Test_stageBuild_populateCompositeKeyForCopyCommand(t *testing.T) { } actualCacheKey := ck.Key() - if tc.expectedCacheKey != actualCacheKey { + if resolvedKey(tc.command) != actualCacheKey { t.Errorf( "Expected cache key to be %s, was %s", - tc.expectedCacheKey, + resolvedKey(tc.command), actualCacheKey, ) } diff --git a/pkg/executor/composite_cache_test.go b/pkg/executor/composite_cache_test.go index 502091320..71f2b8ed2 100644 --- a/pkg/executor/composite_cache_test.go +++ b/pkg/executor/composite_cache_test.go @@ -59,7 +59,7 @@ func Test_CompositeCache_Hash(t *testing.T) { t.Errorf("expected error to be nil but was %v", err) } - expectedHash := "b4fd5a11af812a11a79d794007c842794cc668c8e7ebaba6d1e6d021b8e06c71" + expectedHash := "05a920567352795efa184ef281b472fb4db8747f6ebf30b72b1392c9d081410d" if h != expectedHash { t.Errorf("expected result to equal %v but was %v", expectedHash, h) } From e0301c0bf208d4373bb6bdf4ff406c3ae2b77ac9 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Mon, 27 Jul 2026 11:55:47 +0100 Subject: [PATCH 08/13] pin the graduated flags off in golden legacy cases, drop the redundant on cases --- golden/testdata/test_issue_mz195/test.go | 15 +++++++ golden/testdata/test_issue_mz333/test.go | 1 + golden/testdata/test_issue_mz334/test.go | 30 +++++++++---- golden/testdata/test_issue_mz338/test.go | 1 + golden/testdata/test_issue_mz480/test.go | 9 ++++ golden/testdata/test_issue_mz487/test.go | 3 ++ golden/testdata/test_issue_mz703/test.go | 14 +++--- golden/testdata/test_issue_mz791/test.go | 26 ++++++----- golden/testdata/test_issue_mz813/test.go | 6 ++- golden/testdata/test_issue_mz822/test.go | 10 ++--- golden/testdata/test_issue_mz936/test.go | 7 ++- golden/testdata/test_unittests/test.go | 57 ++++++++++++++++++++++++ 12 files changed, 143 insertions(+), 36 deletions(-) diff --git a/golden/testdata/test_issue_mz195/test.go b/golden/testdata/test_issue_mz195/test.go index f922708fb..17fea73ce 100644 --- a/golden/testdata/test_issue_mz195/test.go +++ b/golden/testdata/test_issue_mz195/test.go @@ -8,23 +8,38 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, // TODO: clean after first-stage is unnecesary Plan: "normal", }, { Args: []string{"--no-push", "--target=fifth-stage"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "normal", }, { Args: []string{"--destination=registry"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "push", }, { Args: []string{"--no-push", "--target=fourth-stage"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "fourth", }, { Args: []string{"--no-push", "--target=noise"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "noise", }, }, diff --git a/golden/testdata/test_issue_mz333/test.go b/golden/testdata/test_issue_mz333/test.go index 7ae8cd087..23e5aee37 100644 --- a/golden/testdata/test_issue_mz333/test.go +++ b/golden/testdata/test_issue_mz333/test.go @@ -8,6 +8,7 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, + Env: map[string]string{}, Plan: "plan", }, }, diff --git a/golden/testdata/test_issue_mz334/test.go b/golden/testdata/test_issue_mz334/test.go index 1b4d2cc84..7d5415350 100644 --- a/golden/testdata/test_issue_mz334/test.go +++ b/golden/testdata/test_issue_mz334/test.go @@ -17,14 +17,26 @@ var Tests = types.GoldenTests{ Dockerfile: "Dockerfile", Tests: []types.GoldenTest{ { - Args: []string{"--no-push", "--cache", "--cache-copy-layers"}, + Args: []string{"--no-push", "--cache", "--cache-copy-layers"}, + Env: map[string]string{ + "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "0", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + "FF_KANIKO_SKIP_CACHED_STAGES": "0", + }, CachedKeys: []string{}, Plan: "plan", }, { Args: []string{"--no-push", "--cache", "--cache-copy-layers"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "0", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + "FF_KANIKO_SKIP_CACHED_STAGES": "0", }, CachedKeys: []string{ "72e9e0e54e4522d381e54427f5ac6f24dd09910e1ff8d4bc7f60d02f54e2cdc3", @@ -36,9 +48,10 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--cache-copy-layers"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_ROLLING_CACHE_KEY": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + "FF_KANIKO_SKIP_CACHED_STAGES": "0", }, CachedKeys: chainKeys, Plan: "inferred", @@ -46,10 +59,9 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--cache-copy-layers"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_ROLLING_CACHE_KEY": "1", - "FF_KANIKO_SKIP_CACHED_STAGES": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", }, CachedKeys: chainKeys, Plan: "eliminated", diff --git a/golden/testdata/test_issue_mz338/test.go b/golden/testdata/test_issue_mz338/test.go index f2f55fbc3..9e45918c4 100644 --- a/golden/testdata/test_issue_mz338/test.go +++ b/golden/testdata/test_issue_mz338/test.go @@ -8,6 +8,7 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, + Env: map[string]string{}, Plan: "plan", }, }, diff --git a/golden/testdata/test_issue_mz480/test.go b/golden/testdata/test_issue_mz480/test.go index cb2792722..d90cc2b60 100644 --- a/golden/testdata/test_issue_mz480/test.go +++ b/golden/testdata/test_issue_mz480/test.go @@ -8,16 +8,25 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--target=final", "--destination=registry"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, // TODO: clean after "base" stage is unnecesary Plan: "final", }, { Args: []string{"--target=final", "--target=build", "--destination=registry"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, // TODO: clean after "base" stage is unnecesary Plan: "final", }, { Args: []string{"--target=final", "--target=test", "--destination=registry"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, // TODO: clean after "base" stage is unnecesary // TODO: saving the "final" stage is unnecessary // TODO: clean after "final" stage is unnecesary diff --git a/golden/testdata/test_issue_mz487/test.go b/golden/testdata/test_issue_mz487/test.go index dd5c4f1fb..a97db8601 100644 --- a/golden/testdata/test_issue_mz487/test.go +++ b/golden/testdata/test_issue_mz487/test.go @@ -8,6 +8,9 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "plan", }, }, diff --git a/golden/testdata/test_issue_mz703/test.go b/golden/testdata/test_issue_mz703/test.go index 981346838..9f1754f09 100644 --- a/golden/testdata/test_issue_mz703/test.go +++ b/golden/testdata/test_issue_mz703/test.go @@ -9,11 +9,13 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, CachedKeys: []string{ - "85da41b943971c4d3e09af20264c54b75dde9593e8104ffab77fbb86896e4756", - "52b7a07c8f6ea53bb7b5591998838ffb46212e21e322a98bb88fe3fe8c1d0fca", + "ef4ba1bfa1a8010630d9a007fad694d95d88419c791f0053b5525169f21e3247", + "2f4043dde38e8a86a388c786d43c46606463533a2eca79177a7246698f9b62a7", }, Plan: "legacy_stop_after_miss", }, @@ -22,10 +24,12 @@ var Tests = types.GoldenTests{ Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_CACHE_PROBE_AFTER_MISS": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, CachedKeys: []string{ - "85da41b943971c4d3e09af20264c54b75dde9593e8104ffab77fbb86896e4756", - "52b7a07c8f6ea53bb7b5591998838ffb46212e21e322a98bb88fe3fe8c1d0fca", + "ef4ba1bfa1a8010630d9a007fad694d95d88419c791f0053b5525169f21e3247", + "2f4043dde38e8a86a388c786d43c46606463533a2eca79177a7246698f9b62a7", }, Plan: "probe_after_miss", }, diff --git a/golden/testdata/test_issue_mz791/test.go b/golden/testdata/test_issue_mz791/test.go index a8985aaea..1982a1921 100644 --- a/golden/testdata/test_issue_mz791/test.go +++ b/golden/testdata/test_issue_mz791/test.go @@ -12,18 +12,20 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--cache-copy-layers", "--build-arg", "A=one"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", }, Plan: "unresolved_one", }, { Args: []string{"--no-push", "--cache", "--cache-copy-layers", "--build-arg", "A=two"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", }, Plan: "unresolved_two", }, @@ -32,18 +34,18 @@ var Tests = types.GoldenTests{ { Args: []string{"--no-push", "--cache", "--cache-copy-layers", "--build-arg", "A=one"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", }, Plan: "resolved_one", }, { Args: []string{"--no-push", "--cache", "--cache-copy-layers", "--build-arg", "A=two"}, Env: map[string]string{ - "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", }, Plan: "resolved_two", }, diff --git a/golden/testdata/test_issue_mz813/test.go b/golden/testdata/test_issue_mz813/test.go index e793c9604..e1a82e231 100644 --- a/golden/testdata/test_issue_mz813/test.go +++ b/golden/testdata/test_issue_mz813/test.go @@ -14,6 +14,7 @@ var Tests = types.GoldenTests{ Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "unresolved_one", }, @@ -22,6 +23,7 @@ var Tests = types.GoldenTests{ Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "unresolved_two", }, @@ -31,7 +33,7 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=one"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "resolved_one", }, @@ -39,7 +41,7 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=two"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "resolved_two", }, diff --git a/golden/testdata/test_issue_mz822/test.go b/golden/testdata/test_issue_mz822/test.go index 75aed38ca..690db93e3 100644 --- a/golden/testdata/test_issue_mz822/test.go +++ b/golden/testdata/test_issue_mz822/test.go @@ -14,8 +14,8 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=one"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "unresolved", }, @@ -23,8 +23,8 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=two"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "unresolved", }, @@ -36,8 +36,7 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=one"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "resolved_one", }, @@ -45,8 +44,7 @@ var Tests = types.GoldenTests{ Args: []string{"--no-push", "--cache", "--build-arg", "A=two"}, Env: map[string]string{ "FF_KANIKO_CACHE_LOOKAHEAD": "1", - "FF_KANIKO_RESOLVE_CACHE_KEY": "1", - "FF_KANIKO_EXPAND_HEREDOC": "1", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", }, Plan: "resolved_two", }, diff --git a/golden/testdata/test_issue_mz936/test.go b/golden/testdata/test_issue_mz936/test.go index 634e453dd..93665e368 100644 --- a/golden/testdata/test_issue_mz936/test.go +++ b/golden/testdata/test_issue_mz936/test.go @@ -15,17 +15,20 @@ var Tests = types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, - Env: map[string]string{"FF_KANIKO_SHARED_BASE_CACHE": "1"}, + Env: map[string]string{}, Plan: "shared", }, { Args: []string{"-d", "example.com/img:latest"}, - Env: map[string]string{"FF_KANIKO_SHARED_BASE_CACHE": "1"}, + Env: map[string]string{}, Plan: "push", }, { // Flag off: every base streams, the behavior before this change. Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "streamed", }, }, diff --git a/golden/testdata/test_unittests/test.go b/golden/testdata/test_unittests/test.go index 471c5273b..76cf83bee 100644 --- a/golden/testdata/test_unittests/test.go +++ b/golden/testdata/test_unittests/test.go @@ -9,14 +9,23 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push", "--target=base-dev"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_dev", }, { Args: []string{"--no-push", "--target=base-prod"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_prod", }, { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_final", }, }, @@ -27,14 +36,23 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push", "--target=base-dev"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_dev", }, { Args: []string{"--no-push", "--target=base-prod"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_prod", }, { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "copyfrom_final", }, }, @@ -45,14 +63,23 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push", "--target=base-dev"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_dev", }, { Args: []string{"--no-push", "--target=base-prod"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "wo_copyfrom_prod", }, { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "two_copyfrom_final", }, }, @@ -63,10 +90,16 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push", "--target=base"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "two_copyfrom_and_arg_base", }, { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "two_copyfrom_and_arg_final", }, }, @@ -77,18 +110,30 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push", "--target=final"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "final_wo_deps_final", }, { Args: []string{"--no-push", "--target=buzz"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "final_wo_deps_buzz", }, { Args: []string{"--no-push", "--target=fizz"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "final_wo_deps_fizz", }, { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "final_wo_deps_final", }, }, @@ -101,6 +146,9 @@ var Tests = []types.GoldenTests{ // There is no need to run the command twice. { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "multiple_copy", }, }, @@ -112,6 +160,9 @@ var Tests = []types.GoldenTests{ // TODO: alias stages get fully unrolled instead of inlined. { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "alias", }, }, @@ -122,10 +173,16 @@ var Tests = []types.GoldenTests{ Tests: []types.GoldenTest{ { Args: []string{"--no-push"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "global_arg", }, { Args: []string{"--no-push", "--target=stage1"}, + Env: map[string]string{ + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "global_arg_stage1", }, }, From 0bed9a656bb4ed85b513bb0b9e5d927003001872 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sun, 23 Aug 2026 12:23:15 +0100 Subject: [PATCH 09/13] v1.29.0 dry-run: flip CROSS_REPO_MOUNT and PATH_SCOPED_REGISTRY_AUTH to default true --- README.md | 9 ++++----- pkg/config/featureflags.go | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3bfc88f79..4cb3a8c4f 100644 --- a/README.md +++ b/README.md @@ -1141,7 +1141,6 @@ delay of 1 second. Defaults to `0`. Opting into the Preview profile gives you early access to upcoming performance improvements, bugfixes and features. While these flags are tested and ready to use, implementation details may still change. ```sh -FF_KANIKO_CROSS_REPO_MOUNT=true FF_KANIKO_RUN_VIA_TINI=true ``` @@ -1315,8 +1314,8 @@ Becomes default in `v1.29.0`. A registry can copy a blob between its own repositories for free, but only if it is told which repository already holds it. Kaniko loses that as soon as it copies a layer locally. Worse, with `--cache` every built layer goes up twice, once to the cache repo and once inside the image. Set this flag to `true` to remember which layers can be mounted remotely and which ones genuinely need to be pushed. If a cross-repository mount cannot be authorized or completed, kaniko falls back to the normal blob upload path using destination credential. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH` @@ -1331,8 +1330,8 @@ repository: registry.example.com/org-a/project-3 ``` We introduced a new PathScopedKeychain resolver that implements namespace aware authentication, so users would only need to configure a single credential for the entire `registry.example.com/org-a`. This affects explicit `auths` entries only, `DOCKER_AUTH_CONFIG`. `credsStore` and `credHelpers` keep the scopes they have without the flag. See **[docs/registries.md](docs/registries.md)** for further details and the full lookup order. Set this flag to `true` to switch to the new namespace aware PathScopedKeychain resolver and match namespace entries. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. > [!IMPORTANT] > The new resolver also stops offering an entry to repositories it does not cover. An entry for `registry.example.com/org-a` is currently sent for every repository on that host, ie. `registry.example.com/org-b` too. This is legacy behaviour intended to support URL-style entries that docker login used to write, but it was scoped too broadly and included entries without a scheme. We will continue to support URL-style entries, what breaks is implicit auth to sibling namespaces. A build that relies on that behaviour goes anonymous and fails with a `401` once this becomes the default. diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index e27794dfe..881269361 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -92,13 +92,13 @@ func InitFeatureFlags() { ChownOnImplicitDirs: featureFlag("FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS", false), CopyAsRoot: featureFlag("FF_KANIKO_COPY_AS_ROOT", false), CopyChmodOnImplicitDirs: featureFlag("FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS", false), - CrossRepoMount: featureFlag("FF_KANIKO_CROSS_REPO_MOUNT", false), + CrossRepoMount: featureFlag("FF_KANIKO_CROSS_REPO_MOUNT", true), DisableHTTP2: featureFlag("FF_KANIKO_DISABLE_HTTP2", false), ExpandHeredoc: featureFlag("FF_KANIKO_EXPAND_HEREDOC", true), HashDirFraming: featureFlag("FF_KANIKO_HASH_DIR_FRAMING", true), InferCrossStageCacheKey: featureFlag("FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY", true), OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), - PathScopedRegistryAuth: featureFlag("FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH", false), + PathScopedRegistryAuth: featureFlag("FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH", true), PrecompileDockerignore: featureFlag("FF_KANIKO_PRECOMPILE_DOCKERIGNORE", true), RelativeLinkTargets: featureFlag("FF_KANIKO_RELATIVE_LINK_TARGETS", true), ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", true), From 18628c26df308626841219c17e2dc817d949b9d4 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sun, 23 Aug 2026 12:23:16 +0100 Subject: [PATCH 10/13] pin the graduated flags off in the mz989 golden cases --- golden/testdata/test_issue_mz989/test.go | 36 ++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/golden/testdata/test_issue_mz989/test.go b/golden/testdata/test_issue_mz989/test.go index 7775f7e26..e08fe9acc 100644 --- a/golden/testdata/test_issue_mz989/test.go +++ b/golden/testdata/test_issue_mz989/test.go @@ -14,32 +14,36 @@ var Tests = types.GoldenTests{ { // The base still knows where it came from, so its layers mount. Args: []string{"-d", "example.com/img:latest"}, - Plan: "plain", - }, - { - // The mount decision is made at push time, so the plan does not move. - Args: []string{"-d", "example.com/img:latest"}, - Env: map[string]string{"FF_KANIKO_CROSS_REPO_MOUNT": "1"}, + Env: map[string]string{"FF_KANIKO_SHARED_BASE_CACHE": "0"}, Plan: "plain", }, { // The store hands the push a local copy, and the base uploads instead. Args: []string{"-d", "example.com/img:latest"}, - Env: map[string]string{"FF_KANIKO_SHARED_BASE_CACHE": "1"}, Plan: "stored", }, { // Every missed layer reaches example.com/cache before the image push // sends the same blob to example.com/img. Args: []string{"-d", "example.com/img:latest", "--cache", "--cache-repo", "example.com/cache"}, - Env: map[string]string{"FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_CROSS_REPO_MOUNT": "1"}, + Env: map[string]string{ + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "cache_miss", }, { // Both layers are read out of example.com/cache and then uploaded to // example.com/img anyway. Args: []string{"-d", "example.com/img:latest", "--cache", "--cache-repo", "example.com/cache"}, - Env: map[string]string{"FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_CROSS_REPO_MOUNT": "1"}, + Env: map[string]string{ + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, CachedKeys: []string{ "9960b0560d3e4212d47329ac9e3379b8891474e43756b7650ae3bc18092b62f7", "c4d1d053ed51898d12bc0fe84e96f70fc77ad862f9e38b677cf97c1cddd78784", @@ -50,7 +54,12 @@ var Tests = types.GoldenTests{ // --single-snapshot builds one layer for the whole stage, so only the last // command reaches the cache repo or the push. Args: []string{"-d", "example.com/img:latest", "--cache", "--cache-repo", "example.com/cache", "--single-snapshot"}, - Env: map[string]string{"FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_CROSS_REPO_MOUNT": "1"}, + Env: map[string]string{ + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, Plan: "single_snapshot", }, { @@ -58,7 +67,12 @@ var Tests = types.GoldenTests{ // convertLayerMediaType recompresses it and the layer that gets pushed is // not the blob the cache repo holds. These uploads must stay uploads. Args: []string{"-d", "example.com/img:latest", "--cache", "--cache-repo", "example.com/cache", "--compression", "zstd"}, - Env: map[string]string{"FF_KANIKO_CACHE_LOOKAHEAD": "1", "FF_KANIKO_CROSS_REPO_MOUNT": "1"}, + Env: map[string]string{ + "FF_KANIKO_CACHE_LOOKAHEAD": "1", + "FF_KANIKO_RESOLVE_CACHE_KEY": "0", + "FF_KANIKO_ROLLING_CACHE_KEY": "0", + "FF_KANIKO_SHARED_BASE_CACHE": "0", + }, CachedKeys: []string{ "9960b0560d3e4212d47329ac9e3379b8891474e43756b7650ae3bc18092b62f7", "c4d1d053ed51898d12bc0fe84e96f70fc77ad862f9e38b677cf97c1cddd78784", From 5de6708a4bfcf7447da1d6825db6f1d3412c118a Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sun, 23 Aug 2026 12:24:05 +0100 Subject: [PATCH 11/13] drop the retired flags from the k8s test job --- integration/k8s-job.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/integration/k8s-job.yaml b/integration/k8s-job.yaml index 98fdf00b5..95de4ff35 100644 --- a/integration/k8s-job.yaml +++ b/integration/k8s-job.yaml @@ -12,12 +12,8 @@ spec: workingDir: /workspace args: [ "--context=dir:///workspace", "--destination={{.KanikoImage}}"] - env: - - name: FF_KANIKO_NO_PROPAGATE_ANNOTATIONS - value: "1" - - name: FF_KANIKO_RUN_MOUNT_BIND - value: "1" {{- if .CoverageDir}} + env: - name: GOCOVERDIR value: /covdata {{- end}} From 8138cb0174b80e44fe05820a3d9d99cc51e1adcf Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sun, 23 Aug 2026 12:24:35 +0100 Subject: [PATCH 12/13] align the SKIP_CACHED_STAGES and SHARED_BASE_CACHE docs with their new defaults --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4cb3a8c4f..04c4fe523 100644 --- a/README.md +++ b/README.md @@ -1299,15 +1299,15 @@ Will be deprecated in `v1.30.0`. When a multi-stage build uses `COPY --from=`, the downstream cache key depends on the copied files. So the entire source stage has to be built and unpacked, only to then realize that we had a cache hit and can throw away the upstream stage. We recently introduced `FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY`, `FF_KANIKO_CACHE_LOOKAHEAD` and `FF_KANIKO_ROLLING_CACHE_KEY`, with that we can know a-priori whether we will have a cache hit or not. `FF_KANIKO_SKIP_CACHED_STAGES` is the logical conclusion then, it simply runs another elision pass over the now updated list of stages and drops all stages that are no longer required to be built. Where a key cannot be inferred the stage is built as before. A fully cached build collapses into a single stage with nothing to unpack. Set this flag to `true` to run the second elision pass. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_SHARED_BASE_CACHE` When several stages build on the same remote base image, kaniko downloads that base once per stage. Set this flag to `true` to download a shared base once, store it under `/kaniko/bases`, and have the other stages read it from there instead of downloading it again. A base is also stored when a stage is kept for a later stage to build on, or when the built image is pushed, because both re-read the base layers. A base used by a single stage that is not pushed still streams, so nothing is stored that would not be read again. Stored bases stay in `/kaniko/bases` after the build, `--cleanup` does not remove them. A long-lived executor that runs many builds therefore accumulates every base it ever downloaded. -Defaults to `false`. -Becomes default in `v1.29.0`. +Defaults to `true`. +Will be deprecated in `v1.30.0`. #### Flag `FF_KANIKO_CROSS_REPO_MOUNT` From dddaba1bf96444a852f04b27759d2811d12cdb7d Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Sun, 23 Aug 2026 12:38:26 +0100 Subject: [PATCH 13/13] v1.29.0 dry-run: retire RELATIVE_LINK_TARGETS, hardlink targets are always relative --- README.md | 8 -------- pkg/config/featureflags.go | 2 -- pkg/util/tar_util.go | 6 +----- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/README.md b/README.md index 04c4fe523..cb0a89621 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,6 @@ expect - see [Known Issues](#known-issues). - [Flag `FF_KANIKO_COPY_CHMOD_ON_IMPLICIT_DIRS`](#flag-ff_kaniko_copy_chmod_on_implicit_dirs) - [Flag `FF_KANIKO_CHOWN_ON_IMPLICIT_DIRS`](#flag-ff_kaniko_chown_on_implicit_dirs) - [Flag `FF_KANIKO_OCI_SCRATCH_BASE`](#flag-ff_kaniko_oci_scratch_base) - - [Flag `FF_KANIKO_RELATIVE_LINK_TARGETS`](#flag-ff_kaniko_relative_link_targets) - [Flag `FF_KANIKO_SKIP_WRITE_WHITEOUTS`](#flag-ff_kaniko_skip_write_whiteouts) - [Flag `FF_KANIKO_INFER_CROSS_STAGE_CACHE_KEY`](#flag-ff_kaniko_infer_cross_stage_cache_key) - [Flag `FF_KANIKO_CACHE_LOOKAHEAD`](#flag-ff_kaniko_cache_lookahead) @@ -1192,13 +1191,6 @@ When a Dockerfile uses `FROM scratch`, kaniko uses an empty Docker-format image Set this flag to `true` to use an empty OCI-format image instead, causing `FROM scratch` builds to produce output in OCI manifest schema v1 format. Defaults to `false`. Currently no plans to activate. -#### Flag `FF_KANIKO_RELATIVE_LINK_TARGETS` - -When a snapshot layer contains a hardlink, kaniko writes the link target as an absolute path while writing the entry name itself relative to the tar root. Docker writes both relative. Older clients accept either form and extract them to the same file, but docker `29.7.0` rejects the absolute one with `invalid hardlink target "/usr/bin/unzip"`, so affected images can no longer be loaded or pulled. -Set this flag to `true` to write hardlink targets relative to the tar root. -Defaults to `true`. -Will be deprecated in `v1.29.0`. - #### Flag `FF_KANIKO_SKIP_WRITE_WHITEOUTS` When kaniko extracts a cached layer it applies the layer's whiteouts by deleting the target files, but it also writes the `.wh.` marker files onto the working filesystem. With `--cache-copy-layers` a later cross-stage `COPY --from=` copies such a marker verbatim and commits it as a real whiteout, so a cache-hit build deletes a file that the cache-miss build kept. diff --git a/pkg/config/featureflags.go b/pkg/config/featureflags.go index 881269361..3e631687f 100644 --- a/pkg/config/featureflags.go +++ b/pkg/config/featureflags.go @@ -40,7 +40,6 @@ type FeatureFlags struct { OCIScratchBase bool PathScopedRegistryAuth bool PrecompileDockerignore bool - RelativeLinkTargets bool ReproduciblePreserveBaseLayers bool ResolveCacheKey bool RollingCacheKey bool @@ -100,7 +99,6 @@ func InitFeatureFlags() { OCIScratchBase: featureFlag("FF_KANIKO_OCI_SCRATCH_BASE", false), PathScopedRegistryAuth: featureFlag("FF_KANIKO_PATH_SCOPED_REGISTRY_AUTH", true), PrecompileDockerignore: featureFlag("FF_KANIKO_PRECOMPILE_DOCKERIGNORE", true), - RelativeLinkTargets: featureFlag("FF_KANIKO_RELATIVE_LINK_TARGETS", true), ReproduciblePreserveBaseLayers: featureFlag("FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS", true), ResolveCacheKey: featureFlag("FF_KANIKO_RESOLVE_CACHE_KEY", true), RollingCacheKey: featureFlag("FF_KANIKO_ROLLING_CACHE_KEY", true), diff --git a/pkg/util/tar_util.go b/pkg/util/tar_util.go index cd47d2500..a8f2853fa 100644 --- a/pkg/util/tar_util.go +++ b/pkg/util/tar_util.go @@ -100,11 +100,7 @@ func (t *Tar) AddFileToTar(p string) error { hardlink, linkDst := t.checkHardlink(p, i) if hardlink { - if config.FF.RelativeLinkTargets { - hdr.Linkname = strings.TrimLeft(strings.TrimPrefix(linkDst, config.RootDir), "/") - } else { - hdr.Linkname = linkDst - } + hdr.Linkname = strings.TrimLeft(strings.TrimPrefix(linkDst, config.RootDir), "/") hdr.Typeflag = tar.TypeLink hdr.Size = 0 }