Skip to content

Commit a646de4

Browse files
authored
fix(docker-in-docker): disable containerd erofs snapshotter to fix dockerd startup (#1645)
* Test case * Modify the script * Trigger the test * Change installation script to install erofs-utils upfront * Install docker-compose v1 with python venv * Force load erofs to replicate the same setup as reported in the issue * Disable erofs filesystem * Start own containerd process * Major version bump * Add docker probe in some cases. * Support arm64 for azurelinux
1 parent 8c47157 commit a646de4

8 files changed

Lines changed: 136 additions & 17 deletions

File tree

.github/workflows/test-pr-arm64.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ on:
88
paths:
99
- "src/powershell/**"
1010
- "test/powershell/**"
11+
- "src/docker-in-docker/**"
12+
- "test/docker-in-docker/**"
1113

1214
jobs:
1315
detect-changes:
@@ -23,6 +25,7 @@ jobs:
2325
# <feature-name>: ./**/<feature-name>/**
2426
filters: |
2527
powershell: ./**/powershell/**
28+
docker-in-docker: ./**/docker-in-docker/**
2629
2730
test:
2831
needs: [detect-changes]
@@ -42,9 +45,15 @@ jobs:
4245
"mcr.microsoft.com/devcontainers/base:debian",
4346
"mcr.microsoft.com/devcontainers/base:noble"
4447
]
48+
exclude:
49+
- features: docker-in-docker
50+
baseImage: mcr.microsoft.com/devcontainers/base:debian
4551
steps:
4652
- uses: actions/checkout@v6
4753

54+
- name: "Load erofs module and verify"
55+
run: sudo modprobe erofs && grep erofs /proc/filesystems
56+
4857
- name: "Install latest devcontainer CLI"
4958
run: npm install -g @devcontainers/cli
5059

src/docker-in-docker/NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This docker-in-docker Dev Container Feature is roughly based on the [official do
44
* As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them.
55
* The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, like in this example:
66
```
7-
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:16
7+
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:24
88
```
99
See [Issue #219](https://github.com/devcontainers/features/issues/219) for more details.
1010

src/docker-in-docker/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Create child containers *inside* a container, independent from the host's docker
77

88
```json
99
"features": {
10-
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
10+
"ghcr.io/devcontainers/features/docker-in-docker:3": {}
1111
}
1212
```
1313

src/docker-in-docker/devcontainer-feature.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "docker-in-docker",
3-
"version": "2.17.0",
3+
"version": "3.0.0",
44
"name": "Docker (Docker-in-Docker)",
55
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-in-docker",
66
"description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.",

src/docker-in-docker/install.sh

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,13 @@ else
287287
fi
288288

289289
# Install base dependencies
290+
# Note: erofs-utils provides mkfs.erofs, required by containerd >= 2.3.x snapshotter
291+
# to avoid "failed to check mkfs.erofs availability" errors at dockerd startup
292+
# (see https://github.com/devcontainers/features/issues/1642).
290293
base_packages="curl ca-certificates pigz iptables gnupg2 wget jq"
291294
case ${ADJUSTED_ID} in
292295
debian)
293-
check_packages apt-transport-https $base_packages dirmngr
296+
check_packages apt-transport-https $base_packages dirmngr erofs-utils
294297
;;
295298
rhel)
296299
check_packages $base_packages tar gawk shadow-utils policycoreutils procps-ng systemd-libs systemd-devel
@@ -621,9 +624,19 @@ else
621624

622625
# Download packages manually using curl since tdnf doesn't support download
623626
echo "(*) Downloading Docker CE packages manually..."
624-
627+
628+
# Derive repo arch from the current platform. The Docker CE centos
629+
# repo uses 'x86_64' and 'aarch64' as the per-arch directory names,
630+
# which matches the values produced by `rpm --eval '%{_arch}'` /
631+
# `uname -m` for those platforms.
632+
case "${architecture}" in
633+
amd64|x86_64) repo_arch="x86_64" ;;
634+
arm64|aarch64) repo_arch="aarch64" ;;
635+
*) repo_arch="${architecture}" ;;
636+
esac
637+
625638
# Get the repository baseurl
626-
repo_baseurl="https://download.docker.com/linux/centos/9/x86_64/stable"
639+
repo_baseurl="https://download.docker.com/linux/centos/9/${repo_arch}/stable"
627640

628641
# Download packages directly
629642
cd /tmp/docker-ce-install
@@ -640,12 +653,12 @@ else
640653
echo "(*) Attempting to download Docker CE packages from repository..."
641654

642655
# Try to download latest packages if specific version fails
643-
if ! curl -fsSL "${repo_baseurl}/Packages/docker-ce-${docker_ce_version}.el9.x86_64.rpm" -o docker-ce.rpm 2>/dev/null; then
656+
if ! curl -fsSL "${repo_baseurl}/Packages/docker-ce-${docker_ce_version}.el9.${repo_arch}.rpm" -o docker-ce.rpm 2>/dev/null; then
644657
# Fallback: try to get latest available version
645658
echo "(*) Specific version not found, trying latest..."
646-
latest_docker=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1)
647-
latest_cli=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'docker-ce-cli-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1)
648-
latest_containerd=$(curl -s "${repo_baseurl}/Packages/" | grep -o 'containerd\.io-[0-9][^"]*\.el9\.x86_64\.rpm' | head -1)
659+
latest_docker=$(curl -s "${repo_baseurl}/Packages/" | grep -o "docker-ce-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1)
660+
latest_cli=$(curl -s "${repo_baseurl}/Packages/" | grep -o "docker-ce-cli-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1)
661+
latest_containerd=$(curl -s "${repo_baseurl}/Packages/" | grep -o "containerd\.io-[0-9][^\"]*\.el9\.${repo_arch}\.rpm" | head -1)
649662

650663
if [ -n "${latest_docker}" ]; then
651664
curl -fsSL "${repo_baseurl}/Packages/${latest_docker}" -o docker-ce.rpm
@@ -731,11 +744,21 @@ if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then
731744
err "Docker compose v1 is unavailable for 'bookworm' on Arm64. Kindly switch to use v2"
732745
exit 1
733746
else
734-
# Use pip to get a version that runs on this architecture
747+
# Use pip (inside an isolated venv) to get a version that runs on this architecture.
748+
# A dedicated venv avoids PEP 668 "externally-managed-environment" errors on newer
749+
# distros (Debian trixie, Ubuntu noble, etc.) and guarantees we do not modify or
750+
# shadow the distro-managed system Python site-packages.
735751
check_packages python3-minimal python3-pip libffi-dev python3-venv
736-
echo "(*) Installing docker compose v1 via pip..."
737-
export PYTHONUSERBASE=/usr/local
738-
pip3 install --disable-pip-version-check --no-cache-dir --user "Cython<3.0" pyyaml wheel docker-compose --no-build-isolation
752+
echo "(*) Installing docker compose v1 via pip into an isolated virtualenv..."
753+
754+
compose_v1_venv="/usr/local/share/docker-compose-v1-venv"
755+
python3 -m venv "${compose_v1_venv}"
756+
"${compose_v1_venv}/bin/pip" install --disable-pip-version-check --no-cache-dir --upgrade pip setuptools wheel
757+
"${compose_v1_venv}/bin/pip" install --disable-pip-version-check --no-cache-dir "Cython<3.0" pyyaml docker-compose --no-build-isolation
758+
759+
# Expose the venv's docker-compose entrypoint on PATH at the expected location.
760+
ln -sf "${compose_v1_venv}/bin/docker-compose" "${docker_compose_path}"
761+
chmod +x "${docker_compose_path}"
739762
fi
740763
else
741764
compose_version=${DOCKER_DASH_COMPOSE_VERSION#v}
@@ -876,6 +899,58 @@ if [ "$DISABLE_IP6_TABLES" == true ]; then
876899
fi
877900
fi
878901

902+
# Workaround for https://github.com/devcontainers/features/issues/1642
903+
# containerd >= 2.3 ships an erofs snapshotter that requires mkfs.erofs >= 1.7.
904+
# Older distros (Debian 12, Ubuntu 22.04) ship erofs-utils 1.4/1.5, so when the
905+
# host kernel exposes the 'erofs' filesystem the snapshotter fails to
906+
# initialize and dockerd times out waiting for containerd. Disable the plugin
907+
# via the top-level `disabled_plugins` list so containerd always uses
908+
# overlayfs, regardless of distro / mkfs.erofs version.
909+
mkdir -p /etc/containerd
910+
if [ ! -s /etc/containerd/config.toml ]; then
911+
if command -v containerd >/dev/null 2>&1; then
912+
containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml
913+
elif [ -x /usr/sbin/containerd ]; then
914+
/usr/sbin/containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml
915+
elif [ -x /usr/bin/containerd ]; then
916+
/usr/bin/containerd config default > /etc/containerd/config.toml 2>/dev/null || : > /etc/containerd/config.toml
917+
else
918+
: > /etc/containerd/config.toml
919+
fi
920+
fi
921+
922+
EROFS_PLUGIN_URI='io.containerd.snapshotter.v1.erofs'
923+
EROFS_MARKER='# devcontainers-features:disable-erofs'
924+
925+
if ! grep -qF "${EROFS_MARKER}" /etc/containerd/config.toml; then
926+
if grep -qE '^[[:space:]]*disabled_plugins[[:space:]]*=' /etc/containerd/config.toml; then
927+
# Add erofs URI to the existing top-level disabled_plugins list.
928+
# Branches are mutually exclusive and guarded so we never produce
929+
# duplicate entries on re-runs.
930+
if grep -qE '^[[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[[[:space:]]*\]' /etc/containerd/config.toml; then
931+
# disabled_plugins = [] -> insert URI as the only entry.
932+
sed -i -E \
933+
"s|^([[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[)[[:space:]]*\]|\1\"${EROFS_PLUGIN_URI}\"]|" \
934+
/etc/containerd/config.toml
935+
elif ! grep -qF "\"${EROFS_PLUGIN_URI}\"" /etc/containerd/config.toml; then
936+
# disabled_plugins = ["existing", ...] -> append URI.
937+
sed -i -E \
938+
"s|^([[:space:]]*disabled_plugins[[:space:]]*=[[:space:]]*\[)([^]]*[^],[:space:]])[[:space:]]*\]|\1\2, \"${EROFS_PLUGIN_URI}\"]|" \
939+
/etc/containerd/config.toml
940+
fi
941+
else
942+
# No disabled_plugins key in the config: prepend one.
943+
tmp_cfg="$(mktemp)"
944+
{
945+
printf 'disabled_plugins = ["%s"]\n\n' "${EROFS_PLUGIN_URI}"
946+
cat /etc/containerd/config.toml
947+
} > "${tmp_cfg}"
948+
mv "${tmp_cfg}" /etc/containerd/config.toml
949+
fi
950+
# Idempotency marker
951+
printf '\n%s\n' "${EROFS_MARKER}" >> /etc/containerd/config.toml
952+
fi
953+
879954
if [ ! -d /usr/local/share ]; then
880955
mkdir -p /usr/local/share
881956
fi
@@ -974,8 +1049,42 @@ dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAU
9741049
DEFAULT_ADDRESS_POOL="--default-address-pool $DOCKER_DEFAULT_ADDRESS_POOL"
9751050
fi
9761051
1052+
1053+
# Start our own containerd so it picks up /etc/containerd/config.toml
1054+
# (notably the disabled_plugins entry for the erofs snapshotter, see
1055+
# https://github.com/devcontainers/features/issues/1642). dockerd's
1056+
# built-in containerd child uses an auto-generated config that ignores
1057+
# /etc/containerd/config.toml, so we must run containerd ourselves and
1058+
# point dockerd at it via --containerd.
1059+
CONTAINERD_SOCK="/run/containerd/containerd.sock"
1060+
CONTAINERD_BIN=""
1061+
for candidate in /usr/local/bin/containerd /usr/bin/containerd /usr/sbin/containerd; do
1062+
if [ -x "$candidate" ]; then
1063+
CONTAINERD_BIN="$candidate"
1064+
break
1065+
fi
1066+
done
1067+
DOCKERD_CONTAINERD_ARG=""
1068+
if [ -n "$CONTAINERD_BIN" ] && [ -f /etc/containerd/config.toml ]; then
1069+
mkdir -p /run/containerd
1070+
if ! pgrep -x containerd > /dev/null 2>&1; then
1071+
( "$CONTAINERD_BIN" --config /etc/containerd/config.toml > /tmp/containerd.log 2>&1 ) &
1072+
fi
1073+
# Wait up to ~5s for the socket to appear
1074+
i=0
1075+
while [ $i -lt 50 ] && [ ! -S "$CONTAINERD_SOCK" ]; do
1076+
sleep 0.1
1077+
i=$((i + 1))
1078+
done
1079+
if [ -S "$CONTAINERD_SOCK" ]; then
1080+
DOCKERD_CONTAINERD_ARG="--containerd $CONTAINERD_SOCK"
1081+
else
1082+
echo "(*) containerd socket not ready; letting dockerd spawn its own containerd."
1083+
fi
1084+
fi
1085+
9771086
# Start docker/moby engine
978-
( dockerd $CUSTOMDNS $DEFAULT_ADDRESS_POOL $DOCKER_DEFAULT_IP6_TABLES > /tmp/dockerd.log 2>&1 ) &
1087+
( dockerd $DOCKERD_CONTAINERD_ARG $CUSTOMDNS $DEFAULT_ADDRESS_POOL $DOCKER_DEFAULT_IP6_TABLES > /tmp/dockerd.log 2>&1 ) &
9791088
INNEREOF
9801089
)"
9811090

test/docker-in-docker/dockerIp6tablesDisabledTest.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ip6tablesCheck() {
1616
echo "❕ip6tables command not found. ❕"
1717
fi
1818
}
19-
19+
check "docker ps" bash -c "docker ps"
2020
check "ip6tables" ip6tablesCheck
2121
check "ip6tables check" bash -c "docker network inspect bridge"
2222
check "docker-build" docker build ./

test/docker-in-docker/docker_build_older.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ check "docker-buildx" docker buildx version
1010
check "docker-build" docker build ./
1111
check "docker-buildx" bash -c "docker buildx version"
1212
check "docker-buildx-path" bash -c "ls -la /usr/libexec/docker/cli-plugins/docker-buildx"
13-
13+
check "docker ps" bash -c "docker ps"
1414
# Report result
1515
reportResults

test/docker-in-docker/pin_docker-ce_version_moby_false.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ source dev-container-features-test-lib
55

66
check "docker-ce" bash -c "docker --version"
77
check "docker-ce-cli" bash -c "docker version"
8+
check "docker ps" bash -c "docker ps"
89

910
#report result
1011
reportResults

0 commit comments

Comments
 (0)