AWS上級 #2 ECR — イメージレジストリ
#1 ECS と Fargate でコンテナ運用の全体像を掴みました。1 つ — その ECS / Fargate が持っていく イメージ がどこに住むかが空白です。Docker Hub のような外部レジストリも可能ですが、AWS の中での標準は Amazon ECR (Elastic Container Registry)。
今回は ECR の構造 — private / public の違い、IAM 認証、イメージのプッシュ / プル、セキュリティ (スキャン、タグ不変性)、運用 (ライフサイクルポリシー、マルチアーキテクチャ) を一気に整理します。
イメージレジストリの役割 #
Docker の流れを思い出すと:
docker build → ローカルイメージ
↓
docker push → レジストリ (リモート)
↓
docker pull (別マシン) → そのイメージを取ってきて docker runレジストリはその中間にあります。誰が、どこから、どのバージョンのイメージを受け取れるか を決めます。
オプション比較 #
| レジストリ | 用途 |
|---|---|
| Docker Hub | 最も有名。無料だが pull 上限、public がデフォルト |
| GHCR (GitHub Container Registry) | GitHub アカウントと連携。private 無料枠が大きい |
| Amazon ECR Private | AWS の中で IAM で認証。ECS / Lambda / EKS と自然に接続 |
| Amazon ECR Public | OSS 配布用。誰でも anonymous pull |
| GCR / Azure ACR | 他クラウド用 |
ECS / Lambda / EKS が AWS の中なら ECR が標準:
- IAM で認証 — 別途のパスワード管理 X
- VPC Endpoint でインターネットを経由せず pull (NAT コスト節約)
- 同じリージョン → 高速 pull
- イメージスキャン (脆弱性自動分析) 統合
Private vs Public #
ECR は 2 種類。
Private (大半の場合) #
私のアカウントのユーザー / ロールだけがアクセス可能。会社 / 運用ワークロードはほとんどここ。
- リージョン単位 (イメージごとにリージョンが刻まれる)
- IAM ポリシーでアクセス制御
- 費用: GB ストレージ + Data Transfer
Public (OSS 配布 / 学習資料) #
世界中の誰でも anonymous pull 可能。AWS が運営する Public Gallery に公開。
- 常に
us-east-1でホスト (グローバル) - Push は IAM 認証、Pull は無認証
- 費用: GB ストレージ + Data Transfer (push 側)
この記事は Private ベース で進めます。
Repository を作る #
ECR の単位は Repository。1 つの repo の中に同じアプリの複数バージョン (タグ) を保管。
aws ecr create-repository \
--repository-name myapp \
--region ap-northeast-2 \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=AES256成功すると URI:
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myappこれがすべての push / pull のアドレス。形式:
<アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<repo>:<タグ>オプション整理 #
| オプション | 意味 |
|---|---|
image-scanning-configuration scanOnPush=true | push 時に自動脆弱性スキャン |
image-tag-mutability IMMUTABLE | 同じタグの上書き禁止 — 運用推奨 |
encryption-configuration encryptionType=KMS | ユーザー管理 KMS キーで暗号化 |
コンソールで GUI でも同じように作れます。
認証 — aws ecr get-login-password
#
Docker Hub と違い ECR は AWS IAM で 認証します。パスワードは別途ありません。代わりに 一時トークン を受け取って docker login。
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.comトークンは 12 時間有効。CI では毎回新しく取って使うのが自然 (CI ジョブ開始時に 1 回)。
必要な権限 #
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:ap-northeast-2:123456789012:repository/myapp"
}
]
}GetAuthorizationToken だけが * リソースで、残りは特定の repo に制限します (基礎 #6 最小権限)。
Pull だけする権限 #
ECS Task の Execution Role のように pull だけすれば OK。AWS マネージドポリシー AmazonECSTaskExecutionRolePolicy が ECR pull 権限を自動で含む。
Push / Pull #
Push #
# ビルド
docker build -t myapp:v1 .
# タグ (ECR URI に)
docker tag myapp:v1 \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1
# ログイン (上を参照)
aws ecr get-login-password --region ap-northeast-2 | docker login ...
# push
docker push \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1イメージが 100MB なら最初の push は 100MB アップロード。次からは レイヤー単位のキャッシュ なので変更された部分だけ上がり通常は数 MB。
Pull #
docker pull 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1ECS / Lambda は自動で pull。コンソールで直接やることはほぼないがデバッグ時に便利。
タグ戦略 #
ECR repo の中に同じイメージの複数バージョンをどう呼ぶかのルールです。よく見るパターンです。
1) Semver #
myapp:1.4.2
myapp:1.4
myapp:1
myapp:latestライブラリ / ツールのように外部に配布する用途に自然です。運用では latest が危険 (どの時点の latest か不明)。
2) Git SHA #
myapp:abc1234 ← short sha
myapp:abc1234567... ← full shaCI でビルドした commit と 1:1 マッチング。運用に最も推奨 — どの commit が運用に乗っているか即時追跡。
3) 環境 + シーケンス #
myapp:prod-2025-04-01.001
myapp:staging-2025-04-01.005リリースを日付別に数える用途で使います。
4) マルチタグ #
運用推奨パターン: immutable + alias。
docker tag myapp:abc1234 myapp:abc1234 # 不変 (永遠にそのまま)
docker tag myapp:abc1234 myapp:prod-current # 可変 (現在の運用を指す)ECR repo 自体は IMMUTABLE (一度 push したタグを上書き不可) にしておき、alias が必要なら別ツール (deployment system) が管理。
イメージスキャン #
ECR は push されたイメージの 脆弱性を自動スキャン してくれます。scanOnPush=true オプション (上で設定)。
2 種類 #
| 種類 | 何 | 費用 |
|---|---|---|
| Basic Scanning | open source CVE DB (CoreOS Clair) ベースの単発スキャン | 無料 |
| Enhanced Scanning | Inspector 統合。OS レイヤー + 言語ライブラリ (npm、pip 等) まで。継続モニタリング (イメージ push 後にも新しい CVE が見つかれば通知) | repo 当たり時間 / イメージ当たり |
運用ワークロードは Enhanced を検討。Basic も最初の出発には十分。
結果を見る #
aws ecr describe-image-scan-findings \
--repository-name myapp \
--image-id imageTag=v1コンソールでは repo → イメージ → “Vulnerabilities” タブで CRITICAL / HIGH / MEDIUM / LOW の数が一目で。
ビルド側でブロックする #
CRITICAL があればデプロイをブロック — CI ジョブで:
CRITICAL=$(aws ecr describe-image-scan-findings \
--repository-name myapp --image-id imageTag=$SHA \
--query 'imageScanFindings.findingSeverityCounts.CRITICAL' \
--output text)
if [ "$CRITICAL" != "None" ] && [ "$CRITICAL" -gt 0 ]; then
echo "🚨 CRITICAL CVE 発見。デプロイ中断。"
exit 1
fiライフサイクルポリシー — 自動整理 #
イメージが溜まると ECR コストが来ます。ライフサイクルポリシー で自動整理。
{
"rules": [
{
"rulePriority": 1,
"description": "untagged イメージを 7 日後に削除",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "最新 30 個だけ維持 (それ以外は削除)",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 30
},
"action": { "type": "expire" }
}
]
}aws ecr put-lifecycle-policy \
--repository-name myapp \
--lifecycle-policy-text file://lifecycle.jsonよくあるパターン:
- untagged 7〜14 日後に削除 (失敗したビルドの残骸)
- タグ prefix
pr-のものを 30 日後に削除 (PR プレビューイメージ) - タグ prefix
release-は永久保存
運用ワークロードは 1 年分のイメージが累積すると GB 単位のコスト。ライフサイクルを最初に決めておくのが運用衛生の核心です。
マルチアーキテクチャイメージ #
Apple Silicon Mac (arm64) でビルドしたイメージをそのまま運用 (通常 amd64) に push すると 起動しません。2 つの道:
1) buildx で複数プラットフォームに push #
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp:v1 \
--push .ECR の 1 タグ (v1) の中に 2 つのアーキテクチャが一緒に入る manifest list。pull 側が自分のアーキテクチャに合うものを自動選択。
2) Fargate ARM に統一 #
#1 Fargate で task definition の runtimePlatform.cpuArchitecture: ARM64 にしておけば ARM 専用イメージだけ push しても OK。単価が約 20% 安い ボーナス。
{
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
}
}小〜中規模トラフィックの新プロジェクトは最初から ARM 推奨。
VPC Endpoint — NAT なしで pull #
private subnet の ECS Task が ECR から pull → デフォルトは NAT Gateway 経由 → GB 当たり課金。
ECR は VPC Endpoint をサポートし NAT を回避:
# api 呼び出し用
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.ecr.api \
--vpc-endpoint-type Interface \
--subnet-ids subnet-aaa subnet-bbb
# イメージ layer ダウンロード用
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.ecr.dkr \
--vpc-endpoint-type Interface \
--subnet-ids subnet-aaa subnet-bbb
# イメージ layer は S3 に住むので S3 endpoint も一緒に
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.ap-northeast-2.s3 \
--vpc-endpoint-type Gateway \
--route-table-ids rtb-xxx3 つ (api、dkr、s3) で 1 セット。NAT Gateway コストを大きく削減 — 運用トラフィックの大きい環境ではほぼ必須です。
クロスアカウントアクセス #
prod アカウントの ECR repo を dev アカウントから pull したい場合 — Repository Policy で許可。
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowDevAccountPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:root"
},
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
}
]
}aws ecr set-repository-policy \
--repository-name myapp \
--policy-text file://repo-policy.json原則: 運用イメージは運用アカウントの ECR に 1 度だけ置き、他のアカウントは pull。同じイメージを環境別アカウントに重複ビルドしません。
コスト #
| 項目 | 価格 (ソウルリージョン) |
|---|---|
| Storage | GB / 月 $0.10 |
| Data Transfer Out (インターネット) | GB $0.126 (1GB 無料) |
| Data Transfer Out (同じリージョン) | 無料 |
| Enhanced Scanning | イメージ当たり + repo 当たり時間 |
同じリージョン内の ECS が pull → 無料。インターネットに出る (CI / 外部ツール) pull だけが費用。ライフサイクルポリシーで storage さえ抑えれば費用はほぼ 0 に近いです。
よく出会う落とし穴 #
1) denied: User: ... is not authorized to perform: ecr:...
#
権限不足。両方確認:
- ユーザー / ロールポリシーに ECR action が入っているか
- Repository Policy がブロックしていないか (通常は空 — 空なら IAM ポリシーだけで十分)
2) manifest unknown または repository ... not found
#
99% は リージョン / アカウント ID のタイポ。URI の ap-northeast-2 の位置、123456789012 の位置を再確認。
3) IMMUTABLE repo に同じタグを push
#
同じタグを 2 回 push しようとするとリジェクト。意図された動作 — 運用推奨。CI ジョブで commit SHA をタグにする形で回避。
4) マルチアーキテクチャ忘れ #
Mac (ARM) でビルド → push → x86_64 Fargate で exec format error。buildx で multi-arch または task definition を ARM に統一。
5) NAT Gateway コストの爆発 #
ECR pull が NAT を経由すれば GB 当たり課金。VPC Endpoint 3 つ (api / dkr / s3) を追加。
6) イメージの累積 #
ライフサイクルポリシーなしで 1 年運用 → 数千個のイメージが GB 単位コスト。最初の repo を作るときにライフサイクル も一緒に。
まとめ #
今回つかんだもの:
- ECR の役割 — AWS の中のイメージレジストリ。ECS / Lambda / EKS と IAM で自然に接続
- Private vs Public — 運用は Private。Public は OSS 配布用
- Repository = 1 つのアプリのイメージ集合。URI の形
<アカウント>.dkr.ecr.<リージョン>.amazonaws.com/<repo>:<タグ> - 認証 —
aws ecr get-login-password→ docker login。12 時間トークン - 権限 — push 権限と pull 権限 (
AmazonECSTaskExecutionRolePolicy) を分離 - タグ戦略 — Semver / Git SHA / 環境+シーケンス。運用は Git SHA + IMMUTABLE
- イメージスキャン — Basic (無料) / Enhanced (Inspector 統合、継続モニタリング)。CI ゲートで CRITICAL ブロック
- ライフサイクルポリシー — untagged 7 日、最新 N 個維持のようなルールで自動整理
- マルチアーキテクチャ — buildx で amd64 + arm64。または Fargate ARM に統一 (20% 安い)
- VPC Endpoint (api / dkr / s3) — NAT Gateway コスト回避。運用ではほぼ必須
- クロスアカウント — Repository Policy で prod ↔ dev pull を許可
- 落とし穴 — 権限不足、URI タイポ、IMMUTABLE 衝突、マルチアーキテクチャ忘れ、NAT コスト、イメージ累積
次回 — Lambda #
ECS / ECR は コンテナが常に立ち上がっているモデル です。今回はその反対側 — リクエストが来たときだけ関数が目を覚ます サーバーレスです。
#3 Lambda 基礎 では Lambda の動作 (vs ECS / EC2)、runtime / handler / イベントモデル、コールドスタート、並行性、ロギングまで — AWS のサーバーレスの最初の一歩を一気に整理します。