@@ -287,10 +287,13 @@ else
287287fi
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).
290293base_packages=" curl ca-certificates pigz iptables gnupg2 wget jq"
291294case ${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
877900fi
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+
879954if [ ! -d /usr/local/share ]; then
880955 mkdir -p /usr/local/share
881956fi
@@ -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 ) &
9791088INNEREOF
9801089)"
9811090
0 commit comments