Skip to content

Regression in 29.7.0: pulling quay.io/buildah/stable fails with "failed to register layer: mkdirat etc/dnf: no such file or directory" #53257

Description

@tibuntu

Description

Since Docker Engine 29.7.0, pulling the public image quay.io/buildah/stable:v1.43.1 fails deterministically during layer extraction:

failed to register layer: mkdirat etc/dnf: no such file or directory

This image pulled fine on the previous engine version (29.6.x). Our CI pulls it many times a day; failures started the day 29.7.0 was published (2026-07-30) on hosts that install the engine from get.docker.com at provisioning time. Nothing else changed — same image digest, same AMI, same kernel.

Reproduce the problem

On a host running 29.7.0:

$ docker pull quay.io/buildah/stable:v1.43.1
v1.43.1: Pulling from buildah/stable
9c7a183a5be5: Pull complete
e1d548ffb6a0: Extracting [==================================================>]     251B/251B
...
failed to register layer: mkdirat etc/dnf: no such file or directory

The failing layer is sha256:e1d548ffb6a01c21199fac9d279812b6ddac622a1806ec589defd29dfef38c0f (251 bytes). Its complete contents:

$ tar tzvf layer.tar.gz
drwxr-xr-x  0 0      0           0 May 31 08:47 etc/dnf/
-rw-r--r--  0 0      0         131 May 31 17:22 etc/dnf/dnf.conf

The layer has no etc/ entry — the parent directory is implied.

Describe the results you received

docker pull fails with failed to register layer: mkdirat etc/dnf: no such file or directory. The image never becomes available locally. It reproduces on every attempt.

Describe the results you expected

The image pulls successfully, as it did on 29.6.x.

Additional context

The proximate cause looks clear. createImpliedDirectories in moby/go-archive only synthesizes missing parent directories for non-directory entries:

func createImpliedDirectories(root *os.Root, hdr *tar.Header, options *TarOptions) error {
	// For non-directory entries, ensure that the parent directory exists.
	if hdr.Typeflag != tar.TypeDir {

The first entry in this layer is etc/dnf/, which is a directory entry, so the implied parent etc/ is never created. Extraction then reaches:

case tar.TypeDir:
	if fi, err := root.Lstat(dstPath); err != nil || !fi.IsDir() {
		if err := root.Mkdir(dstPath, hdrInfo.Mode()&0o777); err != nil {
			return err
		}
	}

root.Mkdir("etc/dnf") with no etc present returns ENOENT, which matches the error exactly.

What I could not determine is why this changed between releases. That Typeflag != tar.TypeDir guard is present in both v0.2.1 and v0.3.0, and both versions create directory entries with a plain (non-recursive) Mkdir, so the guard alone does not explain the regression. 29.7.0 bumped github.com/moby/go-archive to v0.3.0 (for CVE-2026-17106), which reworked Unpack/UnpackLayer onto Go's sandboxed os.Root API:

  • os.OpenRoot(dest) now wraps the destination; os.Mkdir/os.Lstat/os.RemoveAll became root.Mkdir/root.Lstat/root.RemoveAll
  • entry-name normalization changed from filepath.Clean(hdr.Name) to path.Clean(strings.TrimLeft(hdr.Name, "/")) plus a new filepath.IsLocal(name) rejection
  • createImpliedDirectories was reimplemented from a single user.MkdirAllAndChown into a per-component root.Mkdir loop, and in Unpack its call site moved from before the Lstat/RemoveAll block to after it

That bump is the only change in 29.7.0 that touches layer unpacking, so the behavioral delta is very likely in there, but it needs a bisect to pin down.

Whether or not a layer whose first entry is a directory with an unlisted parent is considered well-formed, this is a behavior change that breaks pulls of a widely used public image, and buildah/podman-built images produce this layout routinely.

Docker version

Failing: 29.7.0. Last known-good: the engine version our hosts installed before it, 29.6.x (not bisected).

Client: Docker Engine - Community
 Version:           29.7.0
 API version:       1.55
 Go version:        go1.26.5
 Git commit:        c1eba93
 Built:             Thu Jul 30 20:20:28 2026
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          29.7.0
  API version:      1.55 (minimum version 1.40)
  Go version:       go1.26.5
  Git commit:       4b5cb71
  Built:            Thu Jul 30 20:20:28 2026
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v2.2.6
  GitCommit:        11ce9d5f3c68c941867e82890e93e815c1304f1b
 runc:
  Version:          1.3.6
  GitCommit:        v1.3.6-0-g491b69ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

docker info

Server:
 Containers: 0
 Images: 0
 Server Version: 29.7.0
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 11ce9d5f3c68c941867e82890e93e815c1304f1b
 runc version: v1.3.6-0-g491b69ba
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 6.17.0-1019-aws
 Operating System: Ubuntu 24.04.4 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 7.656GiB
 Live Restore Enabled: false

Metadata

Metadata

Assignees

Labels

kind/bugBugs are bugs. The cause may or may not be known at triage time so debugging may be needed.status/confirmedversion/29.7

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions