CI/CD

GHCR / ECR へのプッシュ実践

GHCR と ECR それぞれへの安全な認証(OIDC)と、本番運用に耐えるタグ戦略(sha / semver / latest の使い分け)。

GHCR / ECR へのプッシュ実践 diagram

認証は長期キーを避ける

コンテナレジストリへの push で長年の定番だった「長期アクセスキーをシークレットに保存」は、今は推奨されません。

  • 漏洩したときの影響範囲が広い
  • 失効管理(ローテーション)が属人化しやすい
  • 誰がいつ使ったかの追跡がしづらい

短命な認証情報を都度発行する方式(OIDC による一時クレデンシャル)に切り替えるのが現代の標準です。

GHCR への push(GITHUB_TOKEN)

GHCR は GitHub Actions に標準で付いてくる GITHUB_TOKEN で認証できます。

ジョブごとに発行・失効する短命トークンなので、漏洩リスクが小さい上、設定の手間がありません。

packages: write 権限を付けるのを忘れずに。

プライベートリポジトリの場合は対象パッケージ側で「このリポジトリからの push を許可」を設定する必要があります。

bash
permissions:
  contents: read
  packages: write

jobs:
  push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

ECR への push(OIDC で IAM ロール引き受け)

AWS 側では GitHub Actions を OIDC プロバイダとして登録し、リポジトリや環境を条件にした IAM ロールを作成しておきます。

ワークフロー側では aws-actions/configure-aws-credentials でロールを引き受けると、AK/SK を保存せずに ECR にアクセスできます。

AWS コンソール / CDK / Terraform で設定する要点:

  • IAM OIDC プロバイダ: https://token.actions.githubusercontent.com
  • ロールの信頼ポリシー Conditiontoken.actions.githubusercontent.com:sub = repo:OWNER/REPO:ref:refs/heads/main などを指定し、対象リポジトリとブランチを限定 する
  • ロールの権限ポリシーは ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:InitiateLayerUpload, ecr:UploadLayerPart, ecr:CompleteLayerUpload, ecr:PutImage を対象リポジトリだけに付与
bash
permissions:
  id-token: write     # OIDC に必須
  contents: read

jobs:
  push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr-push
          aws-region: ap-northeast-1

      - uses: aws-actions/amazon-ecr-login@v2
        id: ecr

      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.ecr.outputs.registry }}/myapp:${{ github.sha }}

タグ戦略

どのタグを付けるかは運用の肝です。

用途ごとに分けましょう。

タグ 用途 可変性
sha-<commit>(例: sha-a1b2c3d 本番デプロイが参照する不変 ID。どのコミットから作ったか一意に分かる 不変(上書き禁止)
v1.2.3(完全 semver) リリース時のマーカー。通常 git タグと一致させる 不変
v1.2, v1 メジャー / マイナーで最新版を指すポインタ 可変(後方互換があれば)
latest デフォルトブランチの HEAD を指すポインタ 可変
pr-<number>, branch-<name> レビュー用の一時タグ 可変。古いものは自動削除設定が望ましい

本番環境は必ず不変タグ(sha-* または完全 semver)を参照 してください。

latest は便利ですが「いつ何が入るか分からない」ため本番参照は事故のもとです。

metadata-action でのタグ生成例

上記の方針を docker/metadata-action で表現すると次のようになります。

これを docker/build-push-actiontags: に繋げるだけで一気に必要なタグが付きます。

bash
- id: meta
  uses: docker/metadata-action@v5
  with:
    images: ghcr.io/${{ github.repository }}
    tags: |
      # 不変 ID: 本番はこれを指す
      type=sha,prefix=sha-,format=short

      # git タグからの完全 semver + ポインタ
      type=semver,pattern={{version}}
      type=semver,pattern={{major}}.{{minor}}
      type=semver,pattern={{major}}

      # 開発用ポインタ
      type=ref,event=branch
      type=ref,event=pr,prefix=pr-

      # デフォルトブランチのみ latest
      type=raw,value=latest,enable={{is_default_branch}}

その他の実務ポイント

  • 不変タグの上書き防止: GHCR はリポジトリ設定で immutable tag を有効化できる。ECR は「タグ変更不可」設定 + ライフサイクルポリシーで古い sha タグをクリーンアップ
  • 脆弱性スキャン結果と合わせる: push したイメージに対して aquasecurity/trivy-action を走らせ、問題があればタグごと削除する運用も可能
  • サイズと転送量: registry キャッシュ + 本番イメージを同じレジストリに置くと転送コストが下がる。ECR の場合はアカウントとリージョンも揃える
  • マルチアーキ配信: docker buildx imagetools create でマニフェストリストを作る(cicd-multi-arch-build 参照)。マニフェストリスト自体には脆弱性は含まれないので、スキャンは各アーキのイメージに対して行う
  • プライベート+プル時の認証: 本番クラスタから pull するときも GitHub Actions OIDC(EKS / ECS の IRSA や Task Role)で ECR 認証を回す。長期キーをノードに配らない