Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/test-pr-arm64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,14 @@ jobs:
- name: "Install latest devcontainer CLI"
run: npm install -g @devcontainers/cli

- name: "Exclude iptables-isolation scenarios from docker-in-docker"
if: matrix.features == 'docker-in-docker'
run: |
sudo apt-get update && sudo apt-get install -y jq
sed 's://.*$::' test/docker-in-docker/scenarios.json \
| jq 'del(.docker_without_iptables, .docker_without_iptables_ubuntu)' \
> test/docker-in-docker/scenarios.json.tmp
mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json

- name: "Testing '${{ matrix.features }}' scenarios"
run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated .
37 changes: 37 additions & 0 deletions .github/workflows/test-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,42 @@ jobs:
- name: "Install latest devcontainer CLI"
run: npm install -g @devcontainers/cli

- name: "Exclude iptables-isolation scenarios from docker-in-docker"
if: matrix.features == 'docker-in-docker'
run: |
sudo apt-get update && sudo apt-get install -y jq
sed 's://.*$::' test/docker-in-docker/scenarios.json \
| jq 'del(.docker_without_iptables, .docker_without_iptables_ubuntu)' \
> test/docker-in-docker/scenarios.json.tmp
mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json

- name: "Testing '${{ matrix.features }}' scenarios"
run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated .

iptables-isolation:
needs: [detect-changes]
if: contains(fromJSON(needs.detect-changes.outputs.features), 'docker-in-docker')
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
scenario:
- docker_without_iptables
- docker_without_iptables_ubuntu
steps:
- uses: actions/checkout@v6

- name: "Install latest devcontainer CLI"
run: npm install -g @devcontainers/cli

- name: "Isolate scenario '${{ matrix.scenario }}'"
run: |
sudo apt-get update && sudo apt-get install -y jq
sed 's://.*$::' test/docker-in-docker/scenarios.json \
| jq '{ "${{ matrix.scenario }}": .["${{ matrix.scenario }}"] }' \
> test/docker-in-docker/scenarios.json.tmp
mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json

- name: "Testing docker-in-docker scenario '${{ matrix.scenario }}'"
run: devcontainer features test --features docker-in-docker --filter ${{ matrix.scenario }} --skip-autogenerated .
2 changes: 1 addition & 1 deletion src/docker-in-docker/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "docker-in-docker",
"version": "3.0.1",
"version": "4.0.0",
"name": "Docker (Docker-in-Docker)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-in-docker",
"description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.",
Expand Down
58 changes: 41 additions & 17 deletions src/docker-in-docker/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -314,25 +314,28 @@ if [ "${ADJUSTED_ID}" = "debian" ] && command -v update-ca-certificates > /dev/n
fi

# Swap to legacy iptables for compatibility (Debian only)
if [ "${ADJUSTED_ID}" = "debian" ]; then
#if [ "${ADJUSTED_ID}" = "debian" ]; then
# On distros where legacy iptables is no longer kernel-supported (e.g. Ubuntu 26.04 / resolute),
# prefer iptables-nft. Otherwise prefer legacy for backward compatibility.
use_nft=false
case "${VERSION_CODENAME}" in
resolute) use_nft=true ;;
esac

if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then
update-alternatives --set iptables /usr/sbin/iptables-nft || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true
elif type iptables-legacy > /dev/null 2>&1; then
update-alternatives --set iptables /usr/sbin/iptables-legacy || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true
elif type iptables-nft > /dev/null 2>&1; then
update-alternatives --set iptables /usr/sbin/iptables-nft || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true
fi
fi
# use_nft=false
# case "${VERSION_CODENAME}" in
# resolute) use_nft=true ;;
# esac

# if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then
# echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels"
# update-alternatives --set iptables /usr/sbin/iptables-nft || true
# update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true
# elif type iptables-legacy > /dev/null 2>&1 && iptables-legacy -L > /dev/null 2>&1; then
# echo "(*) Setting iptables alternatives to legacy for better compatibility with Docker and older kernels"
# update-alternatives --set iptables /usr/sbin/iptables-legacy || true
# update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true
# elif type iptables-nft > /dev/null 2>&1; then
# echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels for non resolute"
# update-alternatives --set iptables /usr/sbin/iptables-nft || true
# update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true
# fi
#fi

# Set up the necessary repositories
if [ "${USE_MOBY}" = "true" ]; then
Expand Down Expand Up @@ -970,6 +973,27 @@ DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL}
DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES}
EOF

# On Debian-based images, re-assert the iptables alternative at container start.
if [ "${ADJUSTED_ID}" = "debian" ]; then
tee -a /usr/local/share/docker-init.sh > /dev/null \
<< 'EOF'
# Prefer legacy only when the ip_tables kernel module is actually present.
# (Do NOT call `iptables-legacy -L/-nL` to test this — it auto-modprobes ip_tables
# and would defeat hosts/scenarios where the module is intentionally absent.)
if type iptables-legacy > /dev/null 2>&1 \
&& { grep -qE '^(ip_tables)\b' /proc/modules \
|| [ -d /sys/module/ip_tables ]; } \
&& update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-legacy'; then
update-alternatives --set iptables /usr/sbin/iptables-legacy || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true
elif type iptables-nft > /dev/null 2>&1 \
&& update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-nft'; then
update-alternatives --set iptables /usr/sbin/iptables-nft || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true
fi
EOF
fi

tee -a /usr/local/share/docker-init.sh > /dev/null \
<< 'EOF'
dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} $(cat << 'INNEREOF'
Expand Down
20 changes: 20 additions & 0 deletions test/docker-in-docker/docker_with_iptables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Feature specific tests
check "iptables works" sudo iptables -L
check "iptables uses legacy" bash -c "iptables --version | grep legacy"

check "version" docker --version
check "docker-ps" bash -c "docker ps"
check "log-exists" bash -c "ls /tmp/dockerd.log"
check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'"
check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'"

# Report result
reportResults

20 changes: 20 additions & 0 deletions test/docker-in-docker/docker_with_iptables_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Feature specific tests
check "iptables works" sudo iptables -L
check "iptables uses legacy" bash -c "iptables --version | grep legacy"

check "version" docker --version
check "docker-ps" bash -c "docker ps"
check "log-exists" bash -c "ls /tmp/dockerd.log"
check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'"
check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'"

# Report result
reportResults

34 changes: 34 additions & 0 deletions test/docker-in-docker/docker_without_iptables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Feature specific tests
check "docker-ps" bash -c "docker ps"
# Fail loudly if dockerd never finished initializing, printing the real error
check "dockerd-started-successfully" bash -c '
if ! grep -q "Daemon has completed initialization" /tmp/dockerd.log; then
echo "❌ Docker daemon failed to start. Last errors from /tmp/dockerd.log:"
echo "----- dockerd.log (tail) -----"
tail -n 100 /tmp/dockerd.log
echo "----- error/fatal lines -----"
grep -iE "error|fatal|failed|panic" /tmp/dockerd.log || true
exit 1
fi
'
check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'"

check "iptables works" sudo iptables -L
check "iptables uses nf_tables" bash -c "iptables --version | grep nf_tables"

check "version" docker --version
check "docker-ps" bash -c "docker ps"
check "log-exists" bash -c "ls /tmp/dockerd.log"
check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'"
check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'"

# Report result
reportResults

20 changes: 20 additions & 0 deletions test/docker-in-docker/docker_without_iptables_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

# Feature specific tests
check "iptables works" sudo iptables -L
check "iptables uses nf_tables" bash -c "iptables --version | grep nf_tables"

check "version" docker --version
check "docker-ps" bash -c "docker ps"
check "log-exists" bash -c "ls /tmp/dockerd.log"
check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'"
check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'"

# Report result
reportResults

36 changes: 36 additions & 0 deletions test/docker-in-docker/scenarios.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
{
"docker_without_iptables": {
"image": "mcr.microsoft.com/devcontainers/base:debian",
"features": {
"docker-in-docker": {
"moby": "false"
}
},
"initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables"
},
"docker_with_iptables": {
"image": "mcr.microsoft.com/devcontainers/base:debian",
"features": {
"docker-in-docker": {
"moby": "false"
}
},
"initializeCommand": "sudo modprobe ip_tables"
},
"docker_without_iptables_ubuntu": {
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"docker-in-docker": {
"moby": "false"
}
},
"initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables"
},
"docker_with_iptables_ubuntu": {
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"docker-in-docker": {
"moby": "false"
}
},
"initializeCommand": "sudo modprobe ip_tables"
},
"overlayfs_containerd_root": {
"image": "mcr.microsoft.com/devcontainers/base:noble",
"features": {
Expand Down
Loading