Skip to content

Commit 5b9944e

Browse files
committed
dind-box: warn when the nested daemon runs on the vfs storage driver (issue #104)
Landing on the `vfs` storage driver was silent: a single `log` line named the driver, but nothing flagged that vfs performs NO copy-on-write. vfs stores every image layer as a full, independent copy, so a multi-GB image's on-disk footprint becomes the SUM of all cumulative layer sizes (many times the image size), and `docker pull`/`docker run` can fail with `failed to register layer: no space left on device` on a disk far larger than the image — with no breadcrumb pointing at the driver. Downstream this overflowed a disk with a >30 GB image (link-assistant/hive-mind#1914). The active driver ends up being vfs either pinned explicitly via `DIND_STORAGE_DRIVER=vfs` (legitimate for overlay-on-overlay compatibility) or reached as the last-resort auto-detect fallback. This is observability, not a default change — vfs stays the safe fallback. `start_dockerd` now calls `warn_if_vfs_storage_driver` right after the daemon becomes ready, emitting one actionable warning whenever the active driver is vfs: it explains the copy-on-write/disk implication and names the `DIND_STORAGE_DRIVER=fuse-overlayfs` remediation (copy-on-write, works overlay-on-overlay, already shipped in the image). The remediation adapts to whether `/dev/fuse` is present, pointing at `--privileged` / `--device /dev/fuse` first when it is missing. The `DIND_STORAGE_DRIVER` doc comment now spells out the vfs disk amplification too. Covered by a new unit test (experiments/test-issue104-vfs-warning.sh) and a new assertion in the CI-run tests/dind/example-storage-driver-vfs.sh; documented in docs/dind/USAGE.md. Adds a patch changeset.
1 parent 13d4c05 commit 5b9944e

6 files changed

Lines changed: 197 additions & 2 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
bump: patch
3+
---
4+
5+
dind-box: warn when the nested daemon runs on the `vfs` storage driver (issue
6+
#104).
7+
8+
When the inner dockerd ends up on `vfs` — either pinned explicitly via
9+
`DIND_STORAGE_DRIVER=vfs` (e.g. for overlay-on-overlay compatibility) or reached
10+
as the last-resort auto-detect fallback — large images could fail to pull/run
11+
with a cryptic `failed to register layer: no space left on device` and **no
12+
hint** that the storage driver was the cause. `vfs` performs no copy-on-write: it
13+
stores every image layer as a full, independent copy, so a multi-GB image's
14+
on-disk footprint becomes the *sum* of all cumulative layer sizes (many times the
15+
image size), and a >30 GB image can overflow a disk with far more than 30 GB free
16+
(`link-assistant/hive-mind#1914`).
17+
18+
This is observability, not a default change — `vfs` stays the safe fallback. The
19+
entrypoint now emits a single, actionable warning right after the daemon becomes
20+
ready whenever the active driver is `vfs`, explaining the copy-on-write/disk
21+
implication and naming the `DIND_STORAGE_DRIVER=fuse-overlayfs` remediation
22+
(copy-on-write, works overlay-on-overlay, already shipped in the image). The
23+
remediation line adapts to whether `/dev/fuse` is present, so when it is missing
24+
it points at `--privileged` / `--device /dev/fuse` first. The
25+
`DIND_STORAGE_DRIVER` doc comment now spells out the `vfs` disk amplification too.
26+
27+
Covered by a new unit test (`experiments/test-issue104-vfs-warning.sh`) and a new
28+
assertion in the CI-run `tests/dind/example-storage-driver-vfs.sh`; documented in
29+
`docs/dind/USAGE.md`.

.gitkeep

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/dind/USAGE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,29 @@ docker run -d --privileged \
352352
konard/box-dind sleep infinity
353353
```
354354

355+
Because that trade-off is easy to hit by accident, the entrypoint emits a
356+
one-time warning whenever the **active** driver ends up being `vfs` — whether
357+
pinned explicitly or reached as the last-resort fallback (issue #104). `vfs`
358+
stores every image layer as a full, independent copy, so a multi-GB image's
359+
on-disk footprint becomes the *sum* of all cumulative layer sizes — many times
360+
the image size — and `docker pull`/`docker run` can fail with `failed to register
361+
layer: no space left on device` on a disk far larger than the image. The warning
362+
makes that failure traceable instead of looking like a generic "out of disk".
363+
364+
If your host supports it, prefer `DIND_STORAGE_DRIVER=fuse-overlayfs`: it is
365+
copy-on-write **and** works overlay-on-overlay (the compatibility reason `vfs` is
366+
sometimes chosen), is already shipped in the image, and needs `/dev/fuse`
367+
(provided by `--privileged`). The warning's remediation line adapts to whether
368+
`/dev/fuse` is present, so when it is missing it tells you to add `--privileged`
369+
or `--device /dev/fuse` before switching.
370+
371+
```bash
372+
docker run -d --privileged \
373+
-e DIND_STORAGE_DRIVER=fuse-overlayfs \
374+
--name box-dind-cow \
375+
konard/box-dind sleep infinity
376+
```
377+
355378
CI verifies the forced `vfs` path:
356379

357380
```bash
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
# Isolated unit test for the issue #104 vfs storage-driver warning in
3+
# dind-entrypoint.sh.
4+
#
5+
# Landing on the `vfs` storage driver used to be silent (only a `log` line named
6+
# the driver), so an operator hitting `failed to register layer: no space left on
7+
# device` had no breadcrumb pointing at the copy-on-write-less driver. The
8+
# entrypoint now emits a one-time `warn` whenever the *active* driver is `vfs`,
9+
# with a remediation hint whose wording depends on whether the fuse-overlayfs
10+
# device node is available.
11+
#
12+
# Building the full box-dind image requires overlay-backed nested Docker, which
13+
# this sandbox cannot provide, so — exactly like preload-unit-test.sh — we source
14+
# the real entrypoint (DIND_ENTRYPOINT_SOURCE_ONLY=1 returns before the
15+
# startup/handoff flow) to get `warn_if_vfs_storage_driver` verbatim and drive it
16+
# directly. The /dev/fuse probe is pointed at a temp path via DIND_FUSE_DEVICE so
17+
# both remediation branches are exercised deterministically without a real device
18+
# node.
19+
set -euo pipefail
20+
21+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22+
ENTRYPOINT="$SCRIPT_DIR/../ubuntu/24.04/dind/dind-entrypoint.sh"
23+
24+
WORK="$(mktemp -d)"
25+
trap 'rm -rf "$WORK"' EXIT
26+
27+
# Source the real entrypoint for its functions only.
28+
# shellcheck disable=SC1090
29+
DIND_ENTRYPOINT_SOURCE_ONLY=1 . "$ENTRYPOINT"
30+
31+
pass=0; fail=0
32+
check() { # check <description> <condition-cmd...>
33+
desc="$1"; shift
34+
if "$@"; then echo " PASS: $desc"; pass=$((pass+1)); else echo " FAIL: $desc"; fail=$((fail+1)); fi
35+
}
36+
37+
ERR="$WORK/err.log"
38+
PRESENT_FUSE="$WORK/fuse-present" # an existing path: stands in for /dev/fuse
39+
MISSING_FUSE="$WORK/fuse-missing" # a path that does not exist
40+
: > "$PRESENT_FUSE"
41+
rm -f "$MISSING_FUSE"
42+
43+
echo "== Case 1: vfs driver emits the copy-on-write warning =="
44+
: > "$ERR"
45+
DIND_FUSE_DEVICE="$PRESENT_FUSE" warn_if_vfs_storage_driver vfs 2>"$ERR"
46+
check "warns the driver is vfs" grep -q "'vfs' storage driver" "$ERR"
47+
check "calls out NO copy-on-write" grep -q "NO copy-on-write" "$ERR"
48+
check "names the disk failure mode" grep -q "no space left on device" "$ERR"
49+
check "names the fuse-overlayfs remediation" grep -q "DIND_STORAGE_DRIVER=fuse-overlayfs" "$ERR"
50+
51+
echo "== Case 2: overlay2 driver stays silent =="
52+
: > "$ERR"
53+
DIND_FUSE_DEVICE="$PRESENT_FUSE" warn_if_vfs_storage_driver overlay2 2>"$ERR"
54+
check "no warning for overlay2" bash -c '! test -s "$1"' _ "$ERR"
55+
56+
echo "== Case 3: fuse-overlayfs driver stays silent =="
57+
: > "$ERR"
58+
DIND_FUSE_DEVICE="$PRESENT_FUSE" warn_if_vfs_storage_driver fuse-overlayfs 2>"$ERR"
59+
check "no warning for fuse-overlayfs" bash -c '! test -s "$1"' _ "$ERR"
60+
61+
echo "== Case 4: empty/unknown driver stays silent =="
62+
: > "$ERR"
63+
DIND_FUSE_DEVICE="$PRESENT_FUSE" warn_if_vfs_storage_driver "" 2>"$ERR"
64+
check "no warning for empty driver" bash -c '! test -s "$1"' _ "$ERR"
65+
66+
echo "== Case 5: /dev/fuse present -> 'set fuse-overlayfs' remediation =="
67+
: > "$ERR"
68+
DIND_FUSE_DEVICE="$PRESENT_FUSE" warn_if_vfs_storage_driver vfs 2>"$ERR"
69+
check "remediation says the device is present" grep -q "is present" "$ERR"
70+
check "remediation does NOT claim it is missing" bash -c '! grep -q "is missing" "$1"' _ "$ERR"
71+
72+
echo "== Case 6: /dev/fuse missing -> explains why fuse-overlayfs is unavailable =="
73+
: > "$ERR"
74+
DIND_FUSE_DEVICE="$MISSING_FUSE" warn_if_vfs_storage_driver vfs 2>"$ERR"
75+
check "remediation explains the device is missing" grep -q "is missing" "$ERR"
76+
check "remediation suggests --device /dev/fuse" grep -q -- "--device /dev/fuse" "$ERR"
77+
check "remediation suggests --privileged" grep -q -- "--privileged" "$ERR"
78+
check "still names the fuse-overlayfs driver" grep -q "DIND_STORAGE_DRIVER=fuse-overlayfs" "$ERR"
79+
80+
echo "== Case 7: function returns success so the start_dockerd success branch is unaffected =="
81+
# warn_if_vfs_storage_driver runs immediately before `return 0`; under `set -e`
82+
# a non-zero return would abort startup. Assert exit status 0 for both vfs and
83+
# non-vfs drivers.
84+
check "returns 0 for vfs" bash -c 'DIND_ENTRYPOINT_SOURCE_ONLY=1 . "$1"; DIND_FUSE_DEVICE="$2" warn_if_vfs_storage_driver vfs >/dev/null 2>&1' _ "$ENTRYPOINT" "$PRESENT_FUSE"
85+
check "returns 0 for overlay2" bash -c 'DIND_ENTRYPOINT_SOURCE_ONLY=1 . "$1"; warn_if_vfs_storage_driver overlay2 >/dev/null 2>&1' _ "$ENTRYPOINT"
86+
87+
echo
88+
echo "RESULT: $pass passed, $fail failed"
89+
[ "$fail" -eq 0 ]

tests/dind/example-storage-driver-vfs.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,16 @@ if [ "$driver" != "vfs" ]; then
1919
fi
2020

2121
log "inner dockerd is using the vfs storage driver"
22+
23+
# issue #104: landing on vfs must not be silent. The entrypoint (PID 1) emits a
24+
# copy-on-write warning to stderr, which docker captures in the container logs.
25+
log "verifying the vfs copy-on-write warning was emitted (issue #104)"
26+
logs="$(docker logs "$container" 2>&1 || true)"
27+
for needle in "'vfs' storage driver" "no space left on device" "DIND_STORAGE_DRIVER=fuse-overlayfs"; do
28+
if ! printf '%s' "$logs" | grep -qF "$needle"; then
29+
printf '%s\n' "$logs" >&2
30+
fail "expected the vfs warning to mention \"${needle}\", but it was absent from the container logs"
31+
fi
32+
done
33+
34+
log "vfs copy-on-write warning is present in the container logs"

ubuntu/24.04/dind/dind-entrypoint.sh

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616
# - Sysbox : docker run --runtime=sysbox-runc konard/<base>-dind (no --privileged)
1717
#
1818
# Environment overrides:
19-
# DIND_STORAGE_DRIVER Override storage driver (default: auto-detected: overlay2, fuse-overlayfs, vfs)
19+
# DIND_STORAGE_DRIVER Override storage driver (default: auto-detected: overlay2,
20+
# fuse-overlayfs, vfs). Note: vfs has NO copy-on-write — it
21+
# stores every image layer as a full, independent copy, so
22+
# large (multi-GB) images consume many times their size on
23+
# disk and 'docker pull'/'docker run' can fail with 'no
24+
# space left on device'. When the active driver ends up
25+
# being vfs the entrypoint emits a one-time warning naming
26+
# the fuse-overlayfs (copy-on-write) remediation. (issue #104)
2027
# DIND_DATA_ROOT Override --data-root for dockerd (default: /var/lib/docker)
2128
# DIND_LOG_FILE Where to write dockerd logs (default: /var/log/dockerd.log)
2229
# DIND_WAIT_SECONDS How long to wait for dockerd to come up (default: 30)
@@ -189,6 +196,40 @@ wait_for_dockerd_ready() {
189196
return 2
190197
}
191198

199+
# Device node fuse-overlayfs needs for copy-on-write. Overridable so the unit
200+
# test can exercise both the "present" and "missing" remediation branches without
201+
# a real device node; in production it is always /dev/fuse.
202+
DIND_FUSE_DEVICE="${DIND_FUSE_DEVICE:-/dev/fuse}"
203+
204+
# When the active storage driver is vfs, emit a one-time warning explaining the
205+
# copy-on-write footgun. vfs is a safe last-resort fallback (and a legitimate
206+
# explicit pin for overlay-on-overlay compatibility), so this is observability,
207+
# not a default change: it stores every image layer as a full, independent copy,
208+
# so a multi-GB image's on-disk footprint becomes the SUM of all cumulative layer
209+
# sizes — many times the image size — and 'docker pull'/'docker run' can fail with
210+
# 'failed to register layer: no space left on device' on a disk far larger than
211+
# the image. Without this breadcrumb the generic disk error is easily misdiagnosed
212+
# as "not enough disk" instead of "wrong driver wastes the disk"
213+
# (link-assistant/hive-mind#1914). The remediation depends on whether the
214+
# copy-on-write fuse-overlayfs driver's device node is available. (issue #104)
215+
warn_if_vfs_storage_driver() {
216+
[ "$1" = "vfs" ] || return 0
217+
218+
warn "dockerd is using the 'vfs' storage driver, which has NO copy-on-write:"
219+
warn "every image layer is stored as a full copy, so a multi-GB image's on-disk"
220+
warn "footprint becomes the SUM of all cumulative layer sizes (many times the"
221+
warn "image size). 'docker pull'/'docker run' can then fail with 'failed to"
222+
warn "register layer: no space left on device' on a disk far larger than the image."
223+
if [ -e "$DIND_FUSE_DEVICE" ]; then
224+
warn "For copy-on-write here, set DIND_STORAGE_DRIVER=fuse-overlayfs (works"
225+
warn "overlay-on-overlay; ${DIND_FUSE_DEVICE} is present)."
226+
else
227+
warn "fuse-overlayfs (copy-on-write, works overlay-on-overlay) is unavailable"
228+
warn "because ${DIND_FUSE_DEVICE} is missing; run with --privileged or"
229+
warn "--device /dev/fuse, then set DIND_STORAGE_DRIVER=fuse-overlayfs."
230+
fi
231+
}
232+
192233
start_dockerd() {
193234
if pgrep -x dockerd >/dev/null 2>&1; then
194235
log "dockerd already running (pid $(pgrep -x dockerd | head -n1))"
@@ -215,6 +256,7 @@ start_dockerd() {
215256
launch_dockerd "$DIND_STORAGE_DRIVER"
216257

217258
if wait_for_dockerd_ready "$DIND_DOCKERD_PID" "$DIND_STORAGE_DRIVER"; then
259+
warn_if_vfs_storage_driver "$DIND_STORAGE_DRIVER"
218260
return 0
219261
else
220262
result="$?"

0 commit comments

Comments
 (0)