feat(mount): support --mount type=image with image-subpath#4993
feat(mount): support --mount type=image with image-subpath#4993mayur-tolexo wants to merge 1 commit into
Conversation
|
Did a round of manual testing against Docker 29.4.0 to check parity. Sharing the results. Whole-rootfs and subpath read the same as Docker$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img alpine cat /mnt/img/etc/alpine-release
3.24.1
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img alpine cat /mnt/img/etc/alpine-release
3.24.1
$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=etc alpine cat /mnt/img/alpine-release
3.24.1
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=etc alpine cat /mnt/img/alpine-release
3.24.1Writes are rejected on both (read-only): $ ... touch /mnt/img/x
touch: /mnt/img/x: Read-only file systemBad mount specs are rejected, same as Dockernerdctl: $ nerdctl run --rm --mount type=image,destination=/mnt/img alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="type=image requires a source (the image reference)"
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,subpath=etc alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="mount option \"subpath\" is not yet supported"
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=../etc alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="image-subpath \"../etc\" escapes the image rootfs"
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=/etc alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="image-subpath must be relative to the image rootfs, got \"/etc\""
$ nerdctl run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=nope alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="image-subpath \"nope\" does not exist in image \"docker.io/library/alpine:latest\""
$ nerdctl run --rm --mount type=bind,source=/tmp,destination=/mnt,image-subpath=etc alpine true
time="2026-06-22T16:54:52Z" level=fatal msg="image-subpath is only supported for type=image"Docker on the same specs: $ docker run --rm --mount type=image,destination=/mnt/img alpine true
docker: Error response from daemon: invalid mount config for type "image": field Source must not be empty
$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,subpath=etc alpine true
invalid argument "..." for "--mount" flag: unknown option 'subpath' in 'subpath=etc'
$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=../etc alpine true
docker: Error response from daemon: invalid mount config for type "image": subpath must be a relative path within the volume
$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=/etc alpine true
docker: Error response from daemon: invalid mount config for type "image": subpath must be a relative path within the volume
$ docker run --rm --mount type=image,source=alpine:latest,destination=/mnt/img,image-subpath=nope alpine true
docker: Error response from daemon: cannot access path .../nope: ... no such file or directory
$ docker run --rm --mount type=bind,source=/tmp,destination=/mnt,image-subpath=etc alpine true
invalid argument "..." for "--mount" flag: cannot mix 'image-*' options with mount type 'bind'How the subpath mount is wirednerdctl is daemonless, so for a subpath it materializes the image rootfs on the host (read-only) and bind-mounts the subdir into the container. For a container started with $ grep image-mounts /proc/mounts
/dev/vda1 /var/lib/nerdctl/image-mounts/6dca0942365322a1d73059ab7945080b4c9cb470e7f1665e0f1d714804f87f9c-image-mount ext4 ro,relatime,discard 0 0The OCI mount injected for {
"destination": "/mnt/img",
"type": "bind",
"source": "/var/lib/nerdctl/image-mounts/6dca0942365322a1d73059ab7945080b4c9cb470e7f1665e0f1d714804f87f9c-image-mount/etc",
"options": ["rbind", "ro"]
}And inside the container: $ grep ' /mnt/img ' /proc/self/mountinfo
117 110 254:1 /docker/volumes/nerdctl-cd-data/_data/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs/etc /mnt/img ro,relatime master:1 - ext4 /dev/vda1 rw,discardThe snapshot view and host path are recorded on the container so they can be cleaned up on removal: On $ grep -c image-mounts /proc/mounts # while running
2
$ grep -c image-mounts /proc/mounts # after stop + rm
0One difference from Docker
|
Mount an image's filesystem into a container read-only, matching Docker: --mount type=image,source=<image>,destination=<path>. The source image is ensured and unpacked, a read-only snapshot view of its rootfs is created and mounted at the destination, and the view is removed when the container is deleted. The image-subpath option exposes a single directory of the image rootfs at the destination instead of the whole rootfs. An OCI overlay mount cannot select a subdirectory, so a subpath mount materializes the read-only view on a host directory under the data root, resolves the subpath with securejoin (blocking absolute paths and parent traversal, including via symlinks), and bind-mounts the resolved directory read-only into the container. The host materialization path is recorded on a container label and unmounted and removed on container deletion, alongside the snapshot view. The whole-rootfs path hands the snapshotter mount straight to the runtime, which owns its lifecycle. Mounting the same image at multiple destinations is supported; the corresponding tests are skipped on Docker, which rejects mounting the same image more than once. Signed-off-by: Mayur Das <mayur.das@neevcloud.com>
18a55fe to
870f56c
Compare
I only see a single commit |
|
If this PR is expected to be merged after #4990, please click |
What
Implements
--mount type=imageend-to-end, matching Docker's--mount type=image(moby/moby#48798), including theimage-subpathoption.Part of #3867.
This builds on #4990 (the first commit here is that MVP: read-only whole-rootfs image mount). The second commit adds
image-subpath, completing the feature.--mount type=image,source=<image>,destination=<path>: mounts the image rootfs read-only at the destination.--mount type=image,source=<image>,destination=<path>,image-subpath=<rel/path>: mounts only a subdirectory of the image rootfs.Image mounts are read-only, matching Docker (verified against Docker 29.4.0: writes return
Read-only file system).How
pkg/mountutil): acceptsimage-subpath, normalizes it, and rejects absolute paths, parent traversal (..), and values that resolve to the rootfs itself.image-subpathis only valid fortype=image.securejoin(blocking symlink escapes), and a read-only bind mount of the resolved directory is added. The host materialization path is recorded on a container label and unmounted/removed onnerdctl rm, alongside the snapshot view.Not included (parity notes vs moby#48798)
gc.rootlabel. A subpath mount's host materialization persists across container restart but not across a host reboot.Test
pkg/mountutilparser cases (subpath normalization, traversal/absolute/root rejection, non-image rejection).cmd/nerdctl/container/container_run_mount_image_linux_test.go(subpath exposure, multiple subpaths, read-only, error cases).