Docker 29 で GitHub Actions から ECR への docker push が 403 Forbidden になる問題と対処法

2026-02-13 02:34 (21 days ago)
Manifest Mutiny
この記事をテーマにした曲を再生

AIコーディングエージェント用修正プロンプト

Github ワークフローでの docker push が失敗している。
 https://www.ytyng.com/blog/docker-29-ecr-push-403-forbidden-containerd-image-store に解決方法が書いてあるので、見て対応して

概要

2026年2月9日、GitHub Actions の ubuntu-latest ランナーイメージが更新され、Docker が 28 → 29 にアップデートされた。

これにより、それまで正常に動作していた ECR (Amazon Elastic Container Registry) への docker push が突然 403 Forbidden エラーで失敗するようになった。

エラーの内容

unknown: unexpected status from HEAD request to
https://***.dkr.ecr.ap-northeast-1.amazonaws.com/v2/repo-name/manifests/0.1.99:
403 Forbidden

特徴的なのは、Docker イメージのレイヤーは全て正常に push されるにも関わらず、最後の manifest の push だけが 403 で失敗するという点。

原因: Docker 29 の containerd image store

Docker 29 では、containerd image store がデフォルトで有効になった。

従来の Docker (28以前) では、docker build で作成されたイメージは Docker 独自の image store に保存され、docker push は Docker Image Manifest V2 形式で push していた。

Docker 29 では、ビルドされたイメージが containerd の image store に保存される。この場合、docker push 時に生成される manifest の形式が変わり、ECR がこれを受け付けずに 403 Forbidden を返す。

ビルドログで確認できる兆候として、Docker 28 では見られなかった unpacking to ... という行が docker build の出力に現れる。これが containerd image store が使われている証拠。

# Docker 29 (containerd image store が有効な場合)
#20 naming to ***.dkr.ecr.../image:0.1.99 done
#20 unpacking to ***.dkr.../image:0.1.99          ← これが出る
#20 unpacking to ***.dkr.../image:0.1.99 3.2s done
# Docker 28 (従来の image store)
#20 naming to ***.dkr.ecr.../image:0.1.95 done
                                                    ← unpacking は出ない

対処法: containerd snapshotter を無効化する

GitHub Actions の workflow ファイルで、Docker daemon の設定を変更して containerd snapshotter を無効化する。

steps:
  - uses: actions/checkout@v3

  - name: Disable containerd image store
    run: |
      DAEMON_JSON="/etc/docker/daemon.json"
      if [ -f "$DAEMON_JSON" ]; then
        sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \
          | sudo tee "${DAEMON_JSON}.tmp" > /dev/null
        sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON"
      else
        echo '{"features": {"containerd-snapshotter": false}}' \
          | sudo tee "$DAEMON_JSON" > /dev/null
      fi
      sudo systemctl restart docker

ポイント

  • ubuntu-latest ランナーには /etc/docker/daemon.json が存在しない場合があるため、ファイルの存在確認が必要。存在しない場合は新規作成する。
  • 設定変更後に sudo systemctl restart docker で Docker daemon を再起動する必要がある。
  • 既存の daemon.json がある場合は jq でマージすることで、他の設定を壊さないようにする。

環境情報

項目 成功時 (2025-12) 失敗時 (2026-02)
ランナーイメージ ubuntu24/20251215 ubuntu24/20260209
Docker 28.0.4 29.1.5
Docker Buildx 0.30.1 0.31.1
containerd image store 無効 (デフォルト) 有効 (デフォルト)

参考リンク

アーカイブ