AWS App Runner + GitHub Actions で環境別CI/CDパイプラインを構築した話
手動デプロイで運用していたAPIサービスに、ブランチ戦略を導入してCI/CDパイプラインを構築しました。本記事では、その手順と学びを共有します。
背景・課題
Before
- デプロイは担当者が手動で実施
- どのコードがどの環境にデプロイされているか不明確
- 本番・開発環境が同じDockerイメージタグ(
:latest)を参照 - ブランチ戦略なし、mainブランチに直接push
After
- ブランチにマージするだけで自動デプロイ
- 環境ごとに異なるイメージタグで完全分離
- develop → staging → main の明確なフロー
構成
ブランチ戦略
| ブランチ | ECRタグ | 環境 | 用途 |
|---|---|---|---|
main |
:prod |
本番 | 本番リリース |
staging |
:stg |
ステージング | リリース前検証 |
develop |
:dev |
開発 | 開発中の検証 |
アーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│ GitHub Repository │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ develop │ │ staging │ │ main │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼───────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions │
│ - Docker build │
│ - ECR push (環境別タグ) │
└───────┼────────────┼────────────┼───────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Amazon ECR │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ :dev │ │ :stg │ │ :prod │ │
│ └────┬───┘ └────┬───┘ └────┬───┘ │
└───────┼───────────┼───────────┼─────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ AWS App Runner (自動デプロイ有効) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ dev │ │ stg │ │ prod │ │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────────┘
実装手順
1. ブランチ作成
mainブランチから staging / develop を作成。
# mainブランチの最新コミットSHAを取得
gh api repos/{owner}/{repo}/git/refs/heads/main --jq '.object.sha'
# ブランチ作成
gh api repos/{owner}/{repo}/git/refs \
-f ref="refs/heads/staging" \
-f sha="{main_sha}"
gh api repos/{owner}/{repo}/git/refs \
-f ref="refs/heads/develop" \
-f sha="{main_sha}"
2. GitHub Actions用IAMユーザー作成
最小権限の原則に従い、ECR pushのみ許可するIAMユーザーを作成。
# ユーザー作成
aws iam create-user --user-name github-actions-ecr-push
# ポリシー作成
cat > ecr-push-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuth",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "arn:aws:ecr:ap-northeast-1:{account_id}:repository/{repo_name}"
}
]
}
EOF
aws iam create-policy \
--policy-name GitHubActionsECRPushPolicy \
--policy-document file://ecr-push-policy.json
# ポリシーをアタッチ
aws iam attach-user-policy \
--user-name github-actions-ecr-push \
--policy-arn arn:aws:iam::{account_id}:policy/GitHubActionsECRPushPolicy
# アクセスキー作成
aws iam create-access-key --user-name github-actions-ecr-push
3. GitHub Secrets 設定
リポジトリの Settings → Secrets and variables → Actions で追加。
| Name | Value |
|---|---|
AWS_ACCESS_KEY_ID |
IAMユーザーのアクセスキー |
AWS_SECRET_ACCESS_KEY |
IAMユーザーのシークレットキー |
4. GitHub Actions ワークフロー作成
ブランチごとに異なるタグをpushするワークフローを作成。
name: Deploy to ECR
on:
push:
branches:
- main
- staging
- develop
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: my-api
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set image tag based on branch
id: tag
run: |
case "${{ github.ref_name }}" in
main) echo "image_tag=prod" >> $GITHUB_OUTPUT ;;
staging) echo "image_tag=stg" >> $GITHUB_OUTPUT ;;
develop) echo "image_tag=dev" >> $GITHUB_OUTPUT ;;
esac
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
ポイント: 各ブランチにこのワークフローファイルを配置する必要があります。
5. App Runner 設定変更
各環境が異なるタグを参照するように変更し、自動デプロイを有効化。
aws apprunner update-service \
--service-arn {service_arn} \
--source-configuration '{
"ImageRepository": {
"ImageIdentifier": "{account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/{repo}:dev",
"ImageRepositoryType": "ECR",
"ImageConfiguration": {
"Port": "8080"
}
},
"AutoDeploymentsEnabled": true,
"AuthenticationConfiguration": {
"AccessRoleArn": "arn:aws:iam::{account_id}:role/service-role/AppRunnerECRAccessRole"
}
}'
ハマったポイント
1. App Runnerのソースタイプは変更不可
ECRで作成したApp RunnerサービスをGitHub直接連携に変更することはできません。
解決策: ECR経由のまま、GitHub Actions でECRにpush → App Runnerの自動デプロイで連携。
2. 設定変更とイメージpushのタイミング問題
App Runnerのタグ設定変更と、GitHub ActionsによるECR pushが同時に走ると、イメージが存在しない状態で参照してロールバックが発生。
解決策: GitHub Actionsが完了してからApp Runnerの設定変更を行う、または手動で再デプロイをトリガー。
3. :latest タグの危険性
全環境が :latest を参照していると、developの変更が本番に影響する可能性がある。
解決策: 環境別タグ(:prod, :stg, :dev)で完全分離。
結果
開発フローの改善
Before: 手動でECRにpush → 手動でApp Runnerデプロイ
After: ブランチにマージ → 自動でデプロイ完了
環境の分離
| 環境 | タグ | 自動デプロイ |
|---|---|---|
| 開発 | :dev |
✅ |
| ステージング | :stg |
✅ |
| 本番 | :prod |
✅ |
チーム開発の基盤
- PRベースの開発フロー確立
- develop → staging → main の段階的リリース
- 「どのコードがどの環境にあるか」が明確に
ECS Fargateの場合
App Runnerではなく ECS Fargate を使う場合も、基本的な流れは同じです。
違いは以下の点:
- IAM権限の追加: ECSデプロイ権限が必要
-
ワークフローの変更:
aws ecs update-service --force-new-deploymentでデプロイ
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster my-cluster \
--service ${{ steps.env.outputs.service }} \
--force-new-deployment
- name: Wait for deployment
run: |
aws ecs wait services-stable \
--cluster my-cluster \
--services ${{ steps.env.outputs.service }}
まとめ
手動デプロイからCI/CDパイプラインへの移行は、以下の効果がありました:
- デプロイの自動化 - 手作業の削減、ヒューマンエラーの防止
- 環境の分離 - 開発の変更が本番に影響しない安心感
- トレーサビリティ - どのコードがどの環境にあるか明確
- チーム開発の基盤 - PRベースの開発フロー確立
AWS App Runner + GitHub Actions の組み合わせは、比較的シンプルにCI/CDを実現できるのでおすすめです。
Discussion