From c9d714579d51e65a3a2822dc28d99bc07b2894f0 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 19 Oct 2026 16:20:58 +0200 Subject: [PATCH 1/2] Build fixed Compose only when needed in CI Move Compose detection and source installation into a shared composite action. Check the installed version tag for docker/compose#14325, keeping fixed versions and the known source build unchanged. Preserve Buildx security enforcement and limit each existing workflow to one setup action. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/setup-compose/action.yml | 88 ++++++++++++++++++++++++ .github/workflows/dev-containers.yml | 1 + .github/workflows/test-docker-v29.yml | 2 + 3 files changed, 91 insertions(+) create mode 100644 .github/actions/setup-compose/action.yml diff --git a/.github/actions/setup-compose/action.yml b/.github/actions/setup-compose/action.yml new file mode 100644 index 000000000..facb2091c --- /dev/null +++ b/.github/actions/setup-compose/action.yml @@ -0,0 +1,88 @@ +name: Set up Compose with build input permissions +description: Build Compose from source only when the installed version does not contain the fix for external Dockerfiles. + +runs: + using: composite + steps: + - name: Check installed Compose + id: check + shell: bash + env: + GH_TOKEN: ${{ github.token }} + # https://github.com/docker/compose/pull/14325 fixes https://github.com/devcontainers/cli/issues/1320. + FIX_COMMIT: 1e1c577eab08ceb5ff0fcf4d8e3cbbc08a34ef35 + FIX_VERSION: 5.6.0-dev.1e1c577 + run: | + version=$(docker compose version --short) + version=${version#v} + echo "Installed Compose: $version" + echo "ref=$FIX_COMMIT" >> "$GITHUB_OUTPUT" + echo "version=$FIX_VERSION" >> "$GITHUB_OUTPUT" + if [[ "$version" == "$FIX_VERSION" ]]; then + echo "Compose already has the source fix; keeping it." + echo "install=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # No release contains the fix yet. Check tag ancestry instead of guessing a minimum version. + if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[[:alnum:].-]+)?$ ]]; then + status=$(gh api "repos/docker/compose/compare/$FIX_COMMIT...v$version" --jq .status) + case "$status" in + ahead|identical) + echo "Compose v$version contains the fix; keeping it." + echo "install=false" >> "$GITHUB_OUTPUT" + exit 0 + ;; + behind|diverged) + echo "Compose v$version does not contain the fix commit; building it from source." + ;; + *) + echo "::error::Unexpected Compose comparison status: $status" + exit 1 + ;; + esac + else + echo "::notice::Cannot verify Compose development version '$version'; building the pinned source fix." + fi + echo "install=true" >> "$GITHUB_OUTPUT" + + - name: Fetch fixed Compose source + if: steps.check.outputs.install == 'true' + id: source + shell: bash + env: + FIX_COMMIT: ${{ steps.check.outputs.ref }} + run: | + source_dir=$(mktemp -d "$RUNNER_TEMP/compose-source.XXXXXX") + git -C "$source_dir" init + git -C "$source_dir" fetch --depth=1 https://github.com/docker/compose.git "$FIX_COMMIT" + git -C "$source_dir" checkout --detach FETCH_HEAD + test "$(git -C "$source_dir" rev-parse HEAD)" = "$FIX_COMMIT" + echo "path=$source_dir" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@v6 + if: steps.check.outputs.install == 'true' + with: + go-version-file: ${{ steps.source.outputs.path }}/go.mod + cache: false + + - name: Build and install fixed Compose + if: steps.check.outputs.install == 'true' + shell: bash + env: + CGO_ENABLED: '0' + SOURCE_DIR: ${{ steps.source.outputs.path }} + FIX_VERSION: ${{ steps.check.outputs.version }} + run: | + cd "$SOURCE_DIR" + # Preserve the CLI's Compose version gates for this unreleased source build. + go build -trimpath \ + -ldflags "-s -w -X github.com/docker/compose/v5/internal.Version=v$FIX_VERSION" \ + -o docker-compose ./cmd + install -D -m 755 docker-compose "${DOCKER_CONFIG:-$HOME/.docker}/cli-plugins/docker-compose" + version=$(docker compose version --short) + if [[ "${version#v}" != "$FIX_VERSION" ]]; then + echo "::error::Expected Compose $FIX_VERSION after installation, got $version" + exit 1 + fi + docker compose version diff --git a/.github/workflows/dev-containers.yml b/.github/workflows/dev-containers.yml index 81ffb8226..a292819b8 100644 --- a/.github/workflows/dev-containers.yml +++ b/.github/workflows/dev-containers.yml @@ -90,6 +90,7 @@ jobs: fi cat "$DAEMON_JSON" sudo systemctl restart docker + - uses: ./.github/actions/setup-compose - name: Tools Info run: | docker info diff --git a/.github/workflows/test-docker-v29.yml b/.github/workflows/test-docker-v29.yml index de475f246..2b4ea9623 100644 --- a/.github/workflows/test-docker-v29.yml +++ b/.github/workflows/test-docker-v29.yml @@ -29,6 +29,8 @@ jobs: sudo VERSION=29.0.0 sh get-docker.sh sudo systemctl restart docker + - uses: ./.github/actions/setup-compose + - name: Verify Docker version, Install and Test run: | # Verify From ee0037a50c617e3778da7762a7d18ba58c2e421c Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 19 Oct 2026 16:40:46 +0200 Subject: [PATCH 2/2] Probe Compose support for external Dockerfiles Replace GitHub API version checks with a scratch build. Install the pinned source fix only for the matching fs.read failure, verify it with the same probe, and clean up the temporary files and image. Preserve unexpected failures instead of treating them as the Compose bug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/setup-compose/action.yml | 82 +++++++++++++++--------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/.github/actions/setup-compose/action.yml b/.github/actions/setup-compose/action.yml index facb2091c..04439bc11 100644 --- a/.github/actions/setup-compose/action.yml +++ b/.github/actions/setup-compose/action.yml @@ -1,50 +1,51 @@ name: Set up Compose with build input permissions -description: Build Compose from source only when the installed version does not contain the fix for external Dockerfiles. +description: Build Compose from source only when a probe reproduces the external-Dockerfile permission error. runs: using: composite steps: - - name: Check installed Compose + - name: Probe installed Compose id: check shell: bash env: - GH_TOKEN: ${{ github.token }} # https://github.com/docker/compose/pull/14325 fixes https://github.com/devcontainers/cli/issues/1320. FIX_COMMIT: 1e1c577eab08ceb5ff0fcf4d8e3cbbc08a34ef35 FIX_VERSION: 5.6.0-dev.1e1c577 run: | - version=$(docker compose version --short) - version=${version#v} - echo "Installed Compose: $version" echo "ref=$FIX_COMMIT" >> "$GITHUB_OUTPUT" echo "version=$FIX_VERSION" >> "$GITHUB_OUTPUT" - if [[ "$version" == "$FIX_VERSION" ]]; then - echo "Compose already has the source fix; keeping it." - echo "install=false" >> "$GITHUB_OUTPUT" - exit 0 - fi + docker compose version + probe_dir=$(mktemp -d "$RUNNER_TEMP/compose-probe.XXXXXX") + echo "probe=$probe_dir" >> "$GITHUB_OUTPUT" + image="devcontainers-compose-probe:${probe_dir##*/}" + echo "image=$image" >> "$GITHUB_OUTPUT" + mkdir "$probe_dir/context" + printf 'FROM scratch\nLABEL devcontainers.compose-probe=true\n' > "$probe_dir/Dockerfile" + cat > "$probe_dir/compose.yaml" <> "$GITHUB_OUTPUT" - exit 0 - ;; - behind|diverged) - echo "Compose v$version does not contain the fix commit; building it from source." - ;; - *) - echo "::error::Unexpected Compose comparison status: $status" - exit 1 - ;; - esac + if docker compose --project-name compose-probe -f "$probe_dir/compose.yaml" build > "$probe_dir/build.log" 2>&1; then + cat "$probe_dir/build.log" + echo "image-created=true" >> "$GITHUB_OUTPUT" + echo "Compose can build with an external Dockerfile; keeping it." + echo "install=false" >> "$GITHUB_OUTPUT" else - echo "::notice::Cannot verify Compose development version '$version'; building the pinned source fix." + status=$? + cat "$probe_dir/build.log" + if grep -Fq "additional privileges requested: pass \"--allow=fs.read=$probe_dir/Dockerfile\"" "$probe_dir/build.log"; then + echo "Compose lacks the external-Dockerfile permission fix; building it from source." + echo "install=true" >> "$GITHUB_OUTPUT" + else + echo "::error::Compose probe failed for a reason other than the external-Dockerfile permission error." + exit "$status" + fi fi - echo "install=true" >> "$GITHUB_OUTPUT" - name: Fetch fixed Compose source if: steps.check.outputs.install == 'true' @@ -68,11 +69,13 @@ runs: - name: Build and install fixed Compose if: steps.check.outputs.install == 'true' + id: install shell: bash env: CGO_ENABLED: '0' SOURCE_DIR: ${{ steps.source.outputs.path }} FIX_VERSION: ${{ steps.check.outputs.version }} + PROBE_DIR: ${{ steps.check.outputs.probe }} run: | cd "$SOURCE_DIR" # Preserve the CLI's Compose version gates for this unreleased source build. @@ -86,3 +89,22 @@ runs: exit 1 fi docker compose version + docker compose --project-name compose-probe -f "$PROBE_DIR/compose.yaml" build + echo "image-created=true" >> "$GITHUB_OUTPUT" + + - name: Clean up Compose probe + if: always() && steps.check.outputs.probe != '' + shell: bash + env: + PROBE_DIR: ${{ steps.check.outputs.probe }} + PROBE_IMAGE: ${{ steps.check.outputs.image }} + IMAGE_CREATED: ${{ steps.check.outputs.image-created == 'true' || steps.install.outputs.image-created == 'true' }} + run: | + rm -f -- "$PROBE_DIR/Dockerfile" "$PROBE_DIR/compose.yaml" "$PROBE_DIR/build.log" + if [[ -d "$PROBE_DIR/context" ]]; then + rmdir -- "$PROBE_DIR/context" + fi + rmdir -- "$PROBE_DIR" + if [[ "$IMAGE_CREATED" == 'true' ]]; then + docker image rm "$PROBE_IMAGE" + fi