CodeBuildのDocker Hub 429エラーをECR Public Galleryで回避してみた

CodeBuildのDocker Hub 429エラーをECR Public Galleryで回避してみた

CodeBuildでDocker Hubの429レートリミットエラーが発生する問題を、DockerfileのARG機能とECR Public Galleryを組み合わせて回避しました。--build-argでレジストリを切り替えるだけで、ローカル開発はDocker Hub、 CIはECR Publicと使い分けられます。
2026.04.09

CodeBuildでdocker build実行時にDocker Hubの429エラー(Too Many Requests)が発生する問題を、DockerfileのARG機能とECR Public Galleryを組み合わせて解決しました。

Docker Hub認証情報が利用できる場合はECR Pull Through Cacheが正攻法ですが、認証情報の管理を最小化したかったため、今回はビルド引数でローカル開発とCIのレジストリを切り替える方式を採用しました。

すぐに試せるCloudFormationテンプレートも掲載しています。

発生した問題

CodePipelineのBuildステージで、docker build実行時に以下のエラーが発生しビルドが失敗しました。

ERROR: failed to resolve source metadata for docker.io/library/node:24-alpine:
429 Too Many Requests
toomanyrequests: You have reached your unauthenticated pull rate limit.

Docker Hubは匿名ユーザーに対して 6時間あたり100回 のpull制限を設けています。CodeBuildはAWSが管理する共有IPアドレスからDocker Hubにアクセスするため、他のユーザーとレートリミットを共有してしまい、制限に達しやすい状況に陥ることがあります。

解決策

DockerfileのARG機能を使い、--build-argでレジストリのプレフィックスを切り替えて、ECR Public Galleryを利用します。

Dockerfile

ARG REGISTRY=""
FROM ${REGISTRY}node:24-alpine

ローカル開発(Docker Hub)

# REGISTRY未指定 → デフォルト "" → Docker Hubからpull
docker build -t myapp .

CI環境(ECR Public)

# --build-arg で ECR Public のプレフィックスを注入
docker build --build-arg REGISTRY="public.ecr.aws/docker/library/" -t myapp .

手順はこれだけです。事前にイメージをpullしておく「pre-pull」や、リポジトリ名のタグ付け替え(docker tag)作業も不要で、Dockerの標準機能(ARG)だけで解決できます

仕組みの図解

【ローカル開発】REGISTRY="" (デフォルト)
docker build → FROM node:24-alpine → Docker Hub 

【CI環境】REGISTRY="public.ecr.aws/docker/library/"
docker build --build-arg REGISTRY=... → FROM public.ecr.aws/docker/library/node:24-alpine → ECR Public 
  • Dockerの標準機能(ARG)だけで実現、シェルスクリプトのハック不要
  • ベースイメージの種類(node, python, nginx等)を問わず汎用的に使える
  • Nodeのバージョン変更時もbuildspecの修正は不要です
  • --platform 指定やマルチステージビルドでも安全に動作

やってみた

デモ用のCloudFormationテンプレートで、ECRリポジトリ・CodeBuild・CodePipelineを一括作成し、ビルド・プッシュを確認します。

ファイル構成

demo-ecr-ratelimit/
├── template.yaml     # CloudFormation (ECR + CodeBuild + CodePipeline)
└── Dockerfile        # ARG REGISTRY="" + FROM ${REGISTRY}node:24-alpine

Dockerfile

ARG REGISTRY=""
FROM ${REGISTRY}node:24-alpine
RUN echo "Hello from node:$(node -v)" > /hello.txt
CMD ["cat", "/hello.txt"]

CloudFormation テンプレート解説

リソース 説明
ECRRepository アプリイメージ保存用。ライフサイクルポリシーで最新3イメージを保持
CodeBuildProject ARM64ビルド環境。--build-arg REGISTRY でECR Publicを指定
Pipeline Source(S3)→ Build の2ステージ構成
IAM Roles CodeBuild用、CodePipeline用

buildspecの核心部分はこれだけです。

build:
  commands:
    # ★ --build-arg で ECR Public のプレフィックスを注入
    - docker build --build-arg REGISTRY="public.ecr.aws/docker/library/" -t ${IMAGE_NAME} .

デプロイ

REGION="us-west-2"

# スタック作成
aws cloudformation deploy \
  --stack-name demo-ecr-ratelimit \
  --template-file template.yaml \
  --capabilities CAPABILITY_IAM \
  --region ${REGION}

# ソースバケット名を取得
BUCKET=$(aws cloudformation describe-stacks \
  --stack-name demo-ecr-ratelimit \
  --query 'Stacks[0].Outputs[?OutputKey==`SourceBucket`].OutputValue' \
  --output text --region ${REGION})

# Dockerfileをzip化してアップロード
zip source.zip Dockerfile
aws s3 cp source.zip s3://${BUCKET}/source.zip --region ${REGION}

# パイプライン実行
aws codepipeline start-pipeline-execution \
  --name demo-ecr-ratelimit-pipeline \
  --region ${REGION}

動作確認

CodeBuildのログで、ECR Public経由でイメージが取得されていることを確認できます。

[Container] Running command docker build --build-arg REGISTRY="public.ecr.aws/docker/library/" -t ${IMAGE_NAME} .

#2 [internal] load metadata for public.ecr.aws/docker/library/node:24-alpine
#2 DONE 0.4s        ← ECR Publicから取得(Docker Hubではない)

#4 [1/2] FROM public.ecr.aws/docker/library/node:24-alpine@sha256:01743339...
#4 resolve public.ecr.aws/docker/library/node:24-alpine done
#4 DONE 3.0s        ← レートリミットなしで安定pull

Hello from node:v24.14.1

Pushed 123456789012.dkr.ecr.us-west-2.amazonaws.com/demo-ecr-ratelimit-app:20260409105429

FROM の解決先が public.ecr.aws になっており、Docker Hubへのアクセスが発生していません。

バージョン変更の自動追従を確認

Dockerfileを node:23-alpine に変更して再実行しました。buildspecは一切変更していません。

  ARG REGISTRY=""
- FROM ${REGISTRY}node:24-alpine
+ FROM ${REGISTRY}node:23-alpine
#2 [internal] load metadata for public.ecr.aws/docker/library/node:23-alpine
#2 DONE 0.3s        ← 自動的に23を取得

Hello from node:v23.11.1
実行 Dockerfile buildspec変更 結果
1回目 FROM ${REGISTRY}node:24-alpine - Hello from node:v24.14.1
2回目 FROM ${REGISTRY}node:23-alpine 変更なし Hello from node:v23.11.1

補足: ECR Public で利用可能なバージョンの確認方法

# 特定バージョンの存在確認
docker manifest inspect public.ecr.aws/docker/library/node:23-alpine

# ECR Public Gallery のWebページでも確認可能
# https://gallery.ecr.aws/docker/library/node

撤去

aws ecr delete-repository \
  --repository-name demo-ecr-ratelimit-app \
  --force --region ${REGION}

aws cloudformation delete-stack \
  --stack-name demo-ecr-ratelimit \
  --region ${REGION}

Docker Hub レートリミットの回避方法比較

方法 メリット デメリット
ARG + ECR Public(本記事) 無料、Docker標準機能、汎用的 Dockerfileに ARG REGISTRY="" の追加が必要
ECR Pull Through Cache 自動キャッシュ、透過的、正攻法 Secrets ManagerにDocker Hub認証情報の登録が必要
CodeBuildをVPC内で実行(NAT Gateway経由) EIP固定で他ユーザーとレートリミットを共有しない NAT Gateway費用(約$45/月〜)、VPC構築が必要
Docker Hub有料プラン レートリミット撤廃 月額費用が発生
Secrets ManagerでDocker Hub認証 200 pulls/6hに緩和 認証情報の管理が必要、完全な回避ではない
プライベートECRにコピー 完全にDocker Hub非依存 イメージ更新の運用が必要

まとめ

CodeBuildでDocker Hubのレートリミット(429)を回避する方法として、DockerfileのARG機能とECR Public Galleryの組み合わせを紹介しました。Dockerの標準機能だけで実現でき、ベースイメージの種類やバージョンを問わず汎用的に使えます。CodeBuildでDocker Hubのレートリミットにお困りの方は、ぜひお試しください。

参考情報

CloudFormation テンプレート全文

クリックで展開
AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Demo: Docker Hub Rate Limit回避 - ARG REGISTRY + ECR Public で安全にCI/CDを実現

Resources:
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub "${AWS::StackName}-app"
      LifecyclePolicy:
        LifecyclePolicyText: |
          {"rules":[{"rulePriority":1,"selection":{"tagStatus":"any","countType":"imageCountMoreThan","countNumber":3},"action":{"type":"expire"}}]}

  ArtifactBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      VersioningConfiguration:
        Status: Enabled

  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: Policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*"
              - Effect: Allow
                Action: ecr:GetAuthorizationToken
                Resource: "*"
              - Effect: Allow
                Action:
                  - ecr:BatchCheckLayerAvailability
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                Resource: !GetAtt ECRRepository.Arn
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:PutObject
                Resource: !Sub "${ArtifactBucket.Arn}/*"

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub "${AWS::StackName}-build"
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      TimeoutInMinutes: 10
      Environment:
        Type: ARM_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux-aarch64-standard:3.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: ECR_REPOSITORY_URI
            Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}"
          - Name: IMAGE_NAME
            Value: !Sub "${AWS::StackName}-app"
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin ${ECR_REPOSITORY_URI}
            build:
              commands:
                # ★ --build-arg で ECR Public のプレフィックスを注入
                - docker build --build-arg REGISTRY="public.ecr.aws/docker/library/" -t ${IMAGE_NAME} .
                - docker run --rm ${IMAGE_NAME}
            post_build:
              commands:
                - IMAGE_TAG=$(date +%Y%m%d%H%M%S)
                - docker tag ${IMAGE_NAME}:latest ${ECR_REPOSITORY_URI}:${IMAGE_TAG}
                - docker push ${ECR_REPOSITORY_URI}:${IMAGE_TAG}
                - echo "Pushed ${ECR_REPOSITORY_URI}:${IMAGE_TAG}"
      Artifacts:
        Type: CODEPIPELINE

  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: Policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
                  - s3:PutObject
                Resource:
                  - !Sub "${ArtifactBucket.Arn}"
                  - !Sub "${ArtifactBucket.Arn}/*"
              - Effect: Allow
                Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                Resource: !GetAtt CodeBuildProject.Arn

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub "${AWS::StackName}-pipeline"
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      PipelineType: V2
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: S3Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: S3
                Version: "1"
              Configuration:
                S3Bucket: !Ref ArtifactBucket
                S3ObjectKey: source.zip
                PollForSourceChanges: false
              OutputArtifacts:
                - Name: SourceCode
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: SourceCode

Outputs:
  SourceBucket:
    Value: !Ref ArtifactBucket
  PipelineName:
    Value: !Ref Pipeline
  ECRRepositoryUri:
    Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}"

この記事をシェアする

関連記事