はじめに
対象: Rails アプリケーション / Amazon ECR / Amazon ECS Fargate / RDS PostgreSQL / GitHub Actions
目的: mainブランチにマージされたら、GitHub Actions から Rails アプリを AWS ECS Fargate に自動デプロイし、デプロイ前にrails db:migrate` を実行する。
今回実施した作業内容を、今後の参考のために備忘録としてまとめています。
この手順で実現すること
Pull Request を main に merge
↓
GitHub Actions が main への push を検知
↓
Docker image を build
↓
Amazon ECR に push
↓
新しい image を使う ECS task definition を作成
↓
ECS Fargate の一時 task で rails db:migrate を実行
↓
migrate 成功時だけ ECS Service を更新
↓
ALB 配下の Rails アプリが新バージョンに切り替わる
この手順では、AWS access key / secret access key を GitHub に保存せず、GitHub OIDC + IAM Role で AWS に接続する。
前提
既存の手動デプロイ手順で、以下のような AWS 構成が作成済みであること。
Route53 → ALB → ECS Fargate → RDS PostgreSQL ECS → S3 ECS → SES
手動デプロイの流れ ローカルファイル修正 → ECRにpush → タスク定義作成 → サービス更新 ※サービス更新時、新しいデプロイの強制のチェックボックスにチェック
今回の例では、元の手動デプロイ記事に合わせて以下の名前を使う。
AWS_REGION: ap-northeast-1 ECR_REPOSITORY: twitterclone ECS_CLUSTER: twitterclone-ecs-cluster ECS_CONTAINER_NAME: twitterclone-web
実際の環境で名前が違う場合は、自分の AWS コンソールに表示されている値に置き換える。
1. AWS コンソールで最初に控える値
自動デプロイの workflow を書く前に、AWS コンソールから必要な値を控える。
1-1. リージョンを確認する
AWS コンソール右上のリージョンを確認する。
東京リージョンで作成している場合は以下。
ap-northeast-1
以降、ECR / ECS / RDS / ALB を確認するときは、必ず同じリージョンを選択する。
1-2. AWS アカウント ID を確認する
AWS コンソール右上のアカウント名またはアカウントメニューを開く。
控える値:
AWS_ACCOUNT_ID = 12桁の AWS アカウント ID
後で IAM Policy や IAM Role の ARN に使う。
1-3. ECR repository 名と URI を確認する
AWS コンソールで以下へ進む。
Amazon ECR → Private registry → Repositories → twitterclone
控える値:
ECR_REPOSITORY = twitterclone ECR Repository URI = <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/twitterclone
もし twitterclone repository がまだ無い場合は、以下で作成する。
Amazon ECR → Private registry → Repositories → Create repository
設定例:
Visibility settings: Private Repository name: twitterclone Image tag mutability: Mutable で可 Encryption configuration: AES-256 で可
今回の GitHub Actions では latest 固定ではなく Git の commit SHA を image tag にする。
1-4. ECS cluster / service / task definition / container 名を確認する
AWS コンソールで以下へ進む。
Amazon ECS → Clusters → twitterclone-ecs-cluster
Services に表示されている service 名を控える。
ECS_CLUSTER = twitterclone-ecs-cluster ECS_SERVICE = AWS コンソールに表示されている service 名
次に Task Definition を確認する。
Amazon ECS → Task definitions → 対象の task definition family
控える値:
ECS_TASK_DEFINITION_FAMILY = task definition family 名 ECS_CONTAINER_NAME = twitterclone
例:
twitterclone:12
上記のように表示されている場合、family 名は twitterclone。
1-5. ECS Service の subnet / security group を確認する
GitHub Actions で rails db:migrate を実行する一時 Fargate task は、ECS Service と同じ VPC / subnet / security group で動かす。
AWS コンソールで以下へ進む。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Services → 対象 service をクリック → Configuration または Networking
控える値:
VPC ID Subnet IDs Security Group IDs Public IP が ON / OFF のどちらか
元の構成どおり Public Subnet ×2 / Public IP ON で動かしている場合、GitHub Secrets には以下のような値を入れる。
ECS_SUBNET_IDS = subnet-xxxxxxxx,subnet-yyyyyyyy ECS_SECURITY_GROUP_IDS = sg-xxxxxxxx ECS_ASSIGN_PUBLIC_IP = ENABLED
ECS_SUBNET_IDS と ECS_SECURITY_GROUP_IDS は、カンマ区切り・スペースなしで控える。
1-6. RDS security group を確認する
migration task が RDS に接続できる必要がある。
AWS コンソールで以下へ進む。
EC2 → Security Groups → RDS 用 security group → Inbound rules
以下の inbound rule があることを確認する。
Type: PostgreSQL Port: 5432 Source: ECS 用 security group
例:
RDS Security Group: twitterclone-rds-sg Inbound rule: PostgreSQL / 5432 / Source: twitterclone-ecs-sg
2. IAM OIDC Provider を作る
GitHub Actions から AWS に入るために、IAM の OIDC Provider を作成する。
AWS コンソールで以下へ進む。
IAM → Access management → Identity providers → Add provider
入力内容:
Provider type: OpenID Connect Provider URL: https://token.actions.githubusercontent.com Audience: sts.amazonaws.com
入力したら Add provider をクリックする。
これにより、GitHub Actions が AWS の IAM Role を引き受けるための入口ができる。
3. GitHub Actions 用 IAM Policy を作る
GitHub Actions が ECR / ECS を操作できるように、IAM Policy を作成する。
AWS コンソールで以下へ進む。
IAM → Access management → Policies → Create policy → JSON
以下の JSON を貼り付ける。
置き換える値:
<AWS_ACCOUNT_ID> 自分の AWS アカウント ID <ECS_SERVICE_NAME> ECS Service 名 <TASK_FAMILY> ECS Task Definition family 名 <TASK_EXECUTION_ROLE_NAME> ECS Task Execution Role 名 <TASK_ROLE_NAME> ECS Task Role 名
IAM Policy:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ECRAuthorization", "Effect": "Allow", "Action": ["ecr:GetAuthorizationToken"], "Resource": "*" }, { "Sid": "ECRPushImage", "Effect": "Allow", "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:BatchGetImage", "ecr:CompleteLayerUpload", "ecr:DescribeImages", "ecr:DescribeRepositories", "ecr:GetDownloadUrlForLayer", "ecr:InitiateLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart" ], "Resource": "arn:aws:ecr:ap-northeast-1:<AWS_ACCOUNT_ID>:repository/twitterclone" }, { "Sid": "ECSRegisterTaskDefinition", "Effect": "Allow", "Action": ["ecs:DescribeTaskDefinition", "ecs:RegisterTaskDefinition"], "Resource": "*" }, { "Sid": "ECSDeployService", "Effect": "Allow", "Action": ["ecs:DescribeServices", "ecs:UpdateService"], "Resource": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:service/twitterclone-ecs-cluster/<ECS_SERVICE_NAME>" }, { "Sid": "ECSRunMigrationTask", "Effect": "Allow", "Action": ["ecs:RunTask"], "Resource": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:task-definition/<TASK_FAMILY>:*", "Condition": { "ArnEquals": { "ecs:cluster": "arn:aws:ecs:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/twitterclone-ecs-cluster" } } }, { "Sid": "ECSDescribeMigrationTask", "Effect": "Allow", "Action": ["ecs:DescribeTasks"], "Resource": "*" }, { "Sid": "PassEcsTaskRoles", "Effect": "Allow", "Action": ["iam:PassRole"], "Resource": [ "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<TASK_EXECUTION_ROLE_NAME>", "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<TASK_ROLE_NAME>" ], "Condition": { "StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" } } } ] }
貼り付け後、以下のように進める。
Next → Policy name → twitterclone-github-actions-deploy-policy → Create policy
Task execution role / Task role の確認場所
AWS コンソールで以下へ進む。
Amazon ECS → Task definitions → 対象 family → 最新 revision → Task roles
よくある例:
Task execution role: ecsTaskExecutionRole Task role: ecs-task-s3-role
S3 や SES を Rails アプリから使っている場合は、既存の Task Role を iam:PassRole の対象に含める。
4. GitHub Actions 用 IAM Role を作る
GitHub Actions が assume する IAM Role を作成する。
AWS コンソールで以下へ進む。
IAM → Access management → Roles → Create role
4-1. Trusted entity を選ぶ
以下を選択する。
Trusted entity type: Web identity Identity provider: token.actions.githubusercontent.com Audience: sts.amazonaws.com
画面に GitHub 用の入力欄が表示される場合は、以下も入力する。
GitHub organization: GitHub の owner 名 GitHub repository: Rails アプリの repository 名 GitHub branch: main
表示されない場合でも、Role 作成後に Trust policy を直接編集するので問題ない。
4-2. 作成した IAM Policy を attach する
Permission policy の選択画面で、先ほど作った policy を検索する。
TwitterCloneGitHubActionsEcsDeployPolicy
チェックを入れて Next に進む。
4-3. Role name を付ける
Role name:
TwitterCloneGitHubActionsEcsDeployRole
Create role をクリックする。
4-4. Trust policy を確認・修正する
Role 作成後、以下へ進む。
IAM → Roles → TwitterCloneGitHubActionsEcsDeployRole → Trust relationships → Edit trust policy
以下の形になっているか確認する。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", "token.actions.githubusercontent.com:sub": "repo:<GITHUB_OWNER>/<GITHUB_REPO>:ref:refs/heads/main" } } } ] }
置き換える値:
<AWS_ACCOUNT_ID> AWS アカウント ID <GITHUB_OWNER> GitHub の owner 名 <GITHUB_REPO> GitHub repository 名
例:
"token.actions.githubusercontent.com:sub": "repo:yourname/twitterclone:ref:refs/heads/main"
これにより、対象 repository の main ブランチの workflow だけが、この IAM Role を assume できる。
保存後、Role の Summary 画面に戻って ARN をコピーする。
arn:aws:iam::<AWS_ACCOUNT_ID>:role/twitterclone-github-actions-deploy-role
この ARN は GitHub Secret の AWS_ROLE_TO_ASSUME に登録する。
5. ECS Task Definition の環境変数を確認する
GitHub Actions の migration task は、既存の ECS Task Definition を使って実行する。
そのため、Task Definition に本番 Rails が起動できる環境変数が入っている必要がある。
AWS コンソールで以下へ進む。
Amazon ECS → Task definitions → 対象 task definition family → 最新 revision → Container definitions → twitterclone → Environment variables
必要な環境変数の例:
RAILS_ENV=production DATABASE_HOST=<RDS Endpoint> MYAPP_DATABASE_PASSWORD=<password> SECRET_KEY_BASE=<rails secret> RAILS_SERVE_STATIC_FILES=true
これらが Task Definition に入っていれば、GitHub Actions 側に DB password や SECRET_KEY_BASE を直接入れる必要はない。
ただし、本番 DB パスワードや SECRET_KEY_BASE を平文の environment variables に置くより、ECS の Secrets から AWS Secrets Manager または SSM Parameter Store を参照する構成の方が安全。
6. ECS Console で rails db:migrate の Run Task を手動確認する
自動化する前に、AWS コンソールから一度 rails db:migrate を手動実行して、ネットワークや環境変数が正しいことを確認する。
AWS コンソールで以下へ進む。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Tasks tab → Run task
設定例:
Existing cluster: twitterclone-ecs-cluster Compute configuration: Launch type Launch type: FARGATE Platform version: LATEST Task definition: twitterclone:<latest revision> Desired tasks: 1
Networking は ECS Service と同じにする。
VPC: ECS Service と同じ VPC Subnets: ECS Service と同じ subnet Security group: ECS Service と同じ security group Public IP: ECS Service と同じ設定
元の構成どおり Public Subnet / Public IP ON の場合は、Public IP を Turned on または Enabled にする。
次に、画面下の方にある以下を開く。
Container Overrides → twitterclone → Command override
Command override に以下を入力する。
bundle,exec,rails,db:migrate
最後に Create または Run task をクリックする。
実行後、以下で task の結果を確認する。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Tasks → Desired task status: Stopped → 該当 task をクリック
成功時の目安:
Last status: STOPPED Exit code: 0
ここで成功していれば、GitHub Actions の自動 migrate でも同じ subnet / security group / task definition を使うことで成功しやすい。
7. ECS Service の手動更新方法も確認しておく
自動化後は GitHub Actions が ECS Service を更新するが、AWS コンソールでは以下の操作に相当する。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Services → 対象 service にチェック → Update → Task definition で新 revision を選択 → Force new deployment → Update
GitHub Actions では、この作業を以下の action が代行する。
aws-actions/amazon-ecs-deploy-task-definition@v2
8. GitHub に Secrets を登録する
ここからは GitHub 側の GUI 作業。
GitHub repository で以下へ進む。
GitHub repository → Settings → Secrets and variables → Actions → Secrets → New repository secret
登録する secret:
AWS_ROLE_TO_ASSUME ECS_SUBNET_IDS ECS_SECURITY_GROUP_IDS
値の例:
AWS_ROLE_TO_ASSUME=arn:aws:iam::<AWS_ACCOUNT_ID>:role/twitterclone-github-actions-deploy-role ECS_SUBNET_IDS=subnet-aaaaaaaa,subnet-bbbbbbbb ECS_SECURITY_GROUP_IDS=sg-cccccccc
注意:
ECS_SUBNET_IDS はカンマ区切り、スペースなし ECS_SECURITY_GROUP_IDS もカンマ区切り、スペースなし
9. GitHub で main を default branch にする
GitHub repository で以下へ進む。
GitHub repository → Settings → General → Default branch → main を選択 → Update
これで以下の仕様を満たす。
main ブランチを default branch とする
main への直接 push を避けたい場合は、Branch protection rule も設定する。
GitHub repository → Settings → Rules → Rulesets または → Settings → Branches → Branch protection rules
推奨設定:
Target branch: main Require a pull request before merging: ON Require approvals: ON Require status checks to pass: 必要に応じて ON
10. GitHub Actions workflow を作成する
Rails アプリの repository に以下のファイルを作成する。
.github/workflows/deploy.yml
内容:
name: Deploy Rails to Amazon ECS Fargate on: push: branches: - main workflow_dispatch: permissions: id-token: write contents: read concurrency: group: production-deploy cancel-in-progress: false env: AWS_REGION: ap-northeast-1 ECR_REPOSITORY: twitterclone ECS_CLUSTER: twitterclone-ecs-cluster ECS_SERVICE: <ECS_SERVICE_NAME> ECS_TASK_DEFINITION_FAMILY: <TASK_FAMILY> ECS_CONTAINER_NAME: twitterclone ECS_ASSIGN_PUBLIC_IP: ENABLED jobs: deploy: name: Build, migrate, and deploy runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7 - name: Configure AWS credentials by OIDC uses: aws-actions/configure-aws-credentials@v6.1.0 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag, and push Docker image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker build --platform linux/amd64 \ -t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" . docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> "$GITHUB_OUTPUT" - name: Download current ECS task definition run: | aws ecs describe-task-definition \ --task-definition "$ECS_TASK_DEFINITION_FAMILY" \ --query taskDefinition > task-definition.json jq 'del( .taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy )' task-definition.json > task-definition.cleaned.json mv task-definition.cleaned.json task-definition.json - name: Render ECS task definition with new image id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: task-definition.json container-name: ${{ env.ECS_CONTAINER_NAME }} image: ${{ steps.build-image.outputs.image }} - name: Run rails db:migrate, then deploy to Amazon ECS uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: task-definition: ${{ steps.task-def.outputs.task-definition }} cluster: ${{ env.ECS_CLUSTER }} service: ${{ env.ECS_SERVICE }} wait-for-service-stability: true wait-for-minutes: 30 run-task: true wait-for-task-stopped: true run-task-launch-type: FARGATE run-task-subnets: ${{ secrets.ECS_SUBNET_IDS }} run-task-security-groups: ${{ secrets.ECS_SECURITY_GROUP_IDS }} run-task-assign-public-IP: ${{ env.ECS_ASSIGN_PUBLIC_IP }} run-task-container-overrides: | [ { "name": "${{ env.ECS_CONTAINER_NAME }}", "command": ["bundle", "exec", "rails", "db:migrate"] } ]
置き換える箇所:
ECS_SERVICE: <ECS_SERVICE_NAME> ECS_TASK_DEFINITION_FAMILY: <TASK_FAMILY>
例:
ECS_SERVICE: twitterclone-service ECS_TASK_DEFINITION_FAMILY: twitterclone
workflow の重要ポイント
main に merge されたときだけ実行する。
on: push: branches: - main
OIDC を使うために id-token: write を設定する。
permissions: id-token: write contents: read
Docker image は commit SHA で tag 付けする。
IMAGE_TAG: ${{ github.sha }}
デプロイ前に migration task を実行する。
run-task: true wait-for-task-stopped: true run-task-container-overrides: | [ { "name": "${{ env.ECS_CONTAINER_NAME }}", "command": ["bundle", "exec", "rails", "db:migrate"] } ]
これにより、rails db:migrate が成功した場合だけ ECS Service が新しい task definition に更新される。
11. 動作確認
11-1. Pull Request を main に merge する
作業 branch を作成する。
git checkout -b feature/test-auto-deploy
変更を commit する。
git add . git commit -m "Test auto deploy to ECS" git push origin feature/test-auto-deploy
GitHub で Pull Request を作成し、base branch を main にする。
Pull Request を merge する。
11-2. GitHub Actions を確認する
GitHub repository で以下へ進む。
Actions → Deploy Rails to Amazon ECS Fargate
以下の step が順番に成功することを確認する。
Configure AWS credentials by OIDC Login to Amazon ECR Build, tag, and push Docker image to Amazon ECR Download current ECS task definition Render ECS task definition with new image Run rails db:migrate, then deploy to Amazon ECS
11-3. AWS コンソールで ECR を確認する
AWS コンソールで以下へ進む。
Amazon ECR → Private registry → Repositories → twitterclone → Images
github.sha の image tag が増えていることを確認する。
例:
1a2b3c4d5e6f...
11-4. AWS コンソールで migration task を確認する
AWS コンソールで以下へ進む。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Tasks
Stopped task を表示し、GitHub Actions が実行した一時 task を開く。
確認する値:
Last status: STOPPED Exit code: 0
Exit code: 0 なら rails db:migrate は成功。
11-5. AWS コンソールで ECS Service を確認する
AWS コンソールで以下へ進む。
Amazon ECS → Clusters → twitterclone-ecs-cluster → Services → 対象 service
確認する箇所:
Deployments → 新しい task definition revision になっている Tasks → 新しい revision の task が RUNNING になっている
最後に ALB または Route53 のドメインで Rails アプリにアクセスし、画面が表示されることを確認する。
補足: 今回作成した GitHub Actions が AWS コンソール作業の何を自動化しているか
Login to Amazon ECR = ECR に docker push するための認証 Build, tag, and push Docker image = 手元で docker build / docker push していた作業 Download current ECS task definition = ECS → Task definitions → 最新 revision を確認する作業 Render ECS task definition with new image = ECS → Task definitions → Create new revision で Image URI を差し替える作業 Run rails db:migrate = ECS → Clusters → Tasks → Run task → Container Overrides で migrate 実行する作業 Deploy to Amazon ECS = ECS → Clusters → Services → Update → 新しい task definition revision を選ぶ作業
