January 10, 2018·4 min read·engineeringdockerimagesuserns

Docker fixes deleted files surviving user namespace builds

Docker 18.01 corrects the handling of overlay2 deletion markers under user namespace remapping. The patch explains how a successful file removal could disappear when Docker committed the layer.

A file removed during a Docker build could reappear in the next instruction when the daemon used user namespace remapping with overlay2. The command had removed the file, but Docker failed to preserve that removal when it recorded the filesystem change. Docker's 18.01.0-ce release notes, dated January 10, include the correction. The interesting part is the interaction between image layers and ownership translation, both of which normally remain invisible behind a Dockerfile.

I value Docker's repeatable build file and layered image format because they make an environment transferable between machines. This bug interferes with that promise at a specific boundary. The filesystem seen by a running build command and the filesystem reconstructed from its resulting image could disagree about whether a path existed.

The removal succeeded before the layer was recorded

The original report, Moby issue 35709, gives a small reproduction using BusyBox. One instruction creates a file. A later instruction removes it, and another examines the path. The reporter reproduced the problem with Docker 17.09.0-ce and subsequently reported the same behavior on 17.11.0-ce. These details narrow the scope beyond the release note's general reference to user namespaces.

The daemon log reported a failure to add the path to a tar archive because host ID zero could not map to a container ID. That diagnostic initially sounds like an ordinary file ownership problem. The path, however, no longer represented the original regular file. The storage driver had replaced it with a marker indicating that a file in a lower layer should be absent from the assembled filesystem.

Phil Estes's December investigation in the issue showed the transition explicitly. The created file had remapped ownership. After deletion, the corresponding entry was a character device with major and minor numbers both zero, owned by host root. That entry was an overlayfs whiteout. Passing it through the ownership translation for ordinary container files caused the archive operation to fail.

The failure explains why checking the return value of the removal command alone was insufficient. Recording a build step involves another operation after the command has finished. If that operation loses the deletion marker, subsequent steps can see the lower layer's file again. The issue contains a reproduction of precisely that disagreement, rather than a failed invocation of rm.

A whiteout needs different ownership handling

The fix in pull request 35822 identifies overlayfs whiteouts before remapping archive entry ownership. Its accompanying patch checks that the entry is a character device and that both device numbers are zero. When those conditions hold, Docker skips container ID remapping for the entry.

The existing code already exempted whiteouts identified by their filename prefix. This change extends that exception to the representation overlayfs uses. A comment in the archive code explains the underlying distinction. The kernel writes these marker files, and their ownership already has the host-relative meaning required by the archive operation. Translating it as container ownership applies the wrong interpretation.

That makes the patch more precise than ignoring ownership errors in general. Regular files still pass through the existing mapping logic. An entry receives the exemption because its type and device numbers identify a deletion marker. Preserving that distinction matters when the same archive path processes ordinary filesystem contents and storage-driver metadata.

The resulting operational scope is concrete. This correction concerns builds using user namespace remapping and overlay2, as described by the report and reproduction. It does not establish that every apparent failure to delete a file has this cause. A daemon log mentioning an unmappable host ID alongside an affected path is evidence connecting a failing build to this particular defect.

The release also corrects environment substitution in docker commit --change. Anthony Sottile's pull request 35582 patch explains that the build machinery received an uninitialized container configuration. The correction supplies the target container's configuration and copies the configuration before making changes.

Its regression test first commits an image with PATH=/bin, then commits a container from that image with an instruction extending the existing value. The expected result is /usr/bin:/bin. Without the inherited environment, interpreting the second instruction cannot produce the intended value. This is configuration inheritance, separate from the filesystem whiteout defect, but it also concerns information lost while producing an image.

I prefer a Dockerfile that makes the construction of the final image explicit, particularly when a multi-stage build can produce a small service image. The commit fix still matters because Docker exposes configuration changes through that command, and those changes should use the environment of the container being committed. An implementation detail about initializing a configuration object should not change the meaning of an environment reference.

The two corrections concern different records of a container's state. One preserves the absence of a filesystem path when Docker serializes a layer. The other preserves inherited environment values when Docker evaluates configuration changes. For an affected overlay2 build, the meaningful result of the January fix is that the next instruction sees the deletion that the preceding command already performed.