Next.js × Rails|ECS/FargateをGitHub Actionsで自動デプロイ
自作アプリを手動でECS/Fargateにデプロイできました。
GitHub Actionsを使って、デプロイを自動化していきます。
自動デプロイというのは、どの部分の自動化なのかを解説します。
- GitHub で main ブランチにマージ
- GitHub Actions が起動
- Docker イメージをビルドし、タグ付け
- ECR に push
- ECS Task Definition を更新
- ECS Service を更新(新しいタスクが起動)
このように自動化することで、
プルリクエストを通過し、ブランチをマージするたびに、
自動で変更が反映されるようになります。
GitHub Actions と AWS を OIDC で連携する
GitHub Actions から AWS にアクセスする方法として、
アクセスキーIDとシークレットアクセスキーを GitHub Secrets に保存する方法があります。
しかしこの方法には問題があります。
- Secrets に保存したキーは「長期的に有効」で、漏れると危険
- 期限切れ・権限変更のメンテナンスが必要
- チーム開発の際にキー管理が煩雑になる
そこで、より安全でモダンな方法として OIDC(OpenID Connect) を使います。
OIDC(OpenID Connect)とは?
OIDC を使うと、
GitHub Actions が AWS にアクセスするための
一時的な認証情報を自動で取得できる仕組み が使えるようになります。
ポイントは以下の2つ:
- アクセスキーを GitHub に保存しなくていい
- 毎回その場だけ有効な“短期間のトークン”でアクセスできる
つまり、セキュリティが強化され、デプロイの運用が安全で楽になるのが OIDC です。
IDプロバイダー
OIDCを使って、GitHub ActionsとAWSを連携させるために、
まずはIDプロバイダーというものを設定します。
IDプロバイダーとは
IDプロバイダーとは、
「その人(またはサービス)が本物であることを証明する役割を持つ仕組み」です。
GitHub Actions の場合は GitHub がトークン(JWT)を発行し、
AWS はそれを信頼して権限を付与します。
これにより、秘密鍵を保存せずに安全に GitHub Actions と AWS を連携できるようになります。
IDプロバイダーの登録
AWSのコンソールのIAMから、IDプロバイダーを選びます。
プロバイダーを追加を選びます。
OpenID Connectを選びます。
https://token.actions.githubusercontent.com
プロバイダのURLは固定値でこれを入れます。
sts.amazonaws.com
対象者にはこれを固定値で入れます。
これでIDプロバイダーの設定ができました。
どういう意味?
ここで入力した 2 つの値には、それぞれ明確な役割があります。
https://token.actions.githubusercontent.com は、
「トークンの発行者(Issuer)」を示します。
GitHub Actions が発行する JWT(身分証明トークン)は、
必ずこの URL を「発行元」として持っています。
AWS はこの値を確認することで、
「このトークンは GitHub Actions が本物として発行したものだ」と判断できます。
sts.amazonaws.com は「トークンの宛先(Audience)」を示します。
このトークンが AWS に渡されたとき、
「これは STS(Security Token Service)に使われるためのトークンです」という印になります。
AWS STS は、この宛先が正しい場合にだけ、
一時的な権限(AssumeRole)を GitHub Actions に発行します。
つまり、この 2 つの値を設定することで、
“GitHub Actions が発行した正しいトークン” が “AWS STS によって受け付けられ、
一時的な権限を得られる”
という OIDC の認証フローが成立します。
ポリシーを定義する
IAMからポリシーを新規作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EcrPush",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
},
{
"Sid": "EcsDeploy",
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "PassTaskRolesToEcs",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskRole",
"arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskExecRole"
]
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
]
}
AdministratorAccessは、権限が強すぎるので、
必要な権限だけを定義して、新しいポリシーを作って、
DigitalIchibaGithubActionsDeployPolicyとします。
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
}
Docker イメージを ECR にアップロード(push)するための権限を与えています。
Effect: Allow
このステートメントに書かれた Action を「許可する」と AWS に伝える。
Resource: *
このステートメントの Action を 全ての対象リソースに対して適用する という意味。
ecr:GetAuthorizationToken
ECR にログインするための認証トークンを取得する。
ecr:BatchCheckLayerAvailability
アップロードしようとしているレイヤーが ECR に既に存在するか確認する。
ecr:InitiateLayerUpload
レイヤーのアップロード処理の開始を ECR に知らせ、アップロード ID を取得する。
ecr:UploadLayerPart
レイヤーのデータを分割して ECR に送信する。
ecr:CompleteLayerUpload
全てのレイヤーパートが送信完了したことを ECR に伝える。
ecr:PutImage
アップロード済みのレイヤーを 1 つの Docker イメージとして ECR に登録する。
{
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:UpdateService"
],
"Resource": "*"
}
新しいタスク定義を登録し、
ECS サービスを更新してデプロイを反映させるための権限を与えています。
ecs:RegisterTaskDefinition
新しいタスク定義を ECS に登録する。(=更新されたイメージを使うタスク定義を作る)
ecs:DescribeServices
対象の ECS サービスの状態(稼働タスク数、デプロイ状況など)を取得する。
ecs:DescribeTaskDefinition
既存のタスク定義の内容(コンテナ設定・イメージ名など)を確認する。
ecs:UpdateService
サービスを新しいタスク定義で更新し、ECS が新しいタスクを起動できるようにする。
{
"Sid": "PassTaskRolesToEcs",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskRole",
"arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskExecRole"
]
}
“Action”: “iam:PassRole”
GitHub Actions が ECS にタスクを起動させる際に、
指定した IAM ロールを ECS に引き渡すことを許可します。
“Resource”
ECS タスク起動時に使用を許可するロールを指定します。
ここでは、アプリケーションが使う タスクロール と、
ECS/Fargate が内部処理で使う タスク実行ロール を指定しています。
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
このブロックでは、ECS/Fargate 上で動くタスクが CloudWatch Logs に
ログを出力するための最小限の権限を付与しています。
logs:CreateLogGroup
ロググループ(/ecs/your-service-name など)を新しく作成できるようにする。
logs:CreateLogStream
ロググループ内に、新しいログストリーム(コンテナ単位のログ出力先)を作成できるようにする。
logs:PutLogEvents
アプリのログ(console.log や Rails のログ)を CloudWatch Logs へ書き込むことを許可する。
logs:DescribeLogStreams
既存のログストリームの情報を取得するための権限。ログの書き込み先を特定する際に必要。
ロールを定義する
IAMから新規でロールを作ります。
ウェブアイデンティティを選択します。
先程作ったプロバイダーを選択して、
オーディエンスもsts.amazonaws.comを選択します。
GitHub organizationに自分のGitHubのIDをいれます。
GitHub repositoryとGitHub branchは*のままにしておきます。
先程作ったDigitalIchibaGitHubActionsDeployPolicyをアタッチします。
これでロールを作成することができました。
あとでワークフローで呼び出して使います。
ワークフローを書く
プロジェクトのバックエンド直下に、
.github/workflows/deploy_fargate.ymlを作成します。
name: ECS Fargate Deploy
on:
push:
branches: [ main ]
env:
AWS_REGION: ap-northeast-1
ECS_CLUSTER_NAME: digital-ichiba-cluster
ECS_SERVICE_NAME: digital-ichiba-service
ECS_TASK_DEFINITION_FILE: .aws/task_definition.json
ECR_REGISTRY: 575357958164.dkr.ecr.ap-northeast-1.amazonaws.com
ECR_REGISTRY_NAME: digital-ichiba-ecr
AWS_ROLE_TO_ASSUME: arn:aws:iam::575357958164:role/DigitalIchibaGitHubActionsDeployRole
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Deploy Backend to ECS Fargate
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Get source code
uses: actions/checkout@v5
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ env.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 and push image to ECR
run: |
docker build -t $ECR_REGISTRY_NAME .
docker tag $ECR_REGISTRY_NAME:latest $ECR_REGISTRY/$ECR_REGISTRY_NAME:latest
docker push $ECR_REGISTRY/$ECR_REGISTRY_NAME:latest
- name: Render Amazon ECS task definition
id: render-digital-ichiba-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.ECS_TASK_DEFINITION_FILE }}
container-name: digital-ichiba
image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REGISTRY_NAME }}:latest
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-digital-ichiba-container.outputs.task-definition }}
service: ${{ env.ECS_SERVICE_NAME }}
cluster: ${{ env.ECS_CLUSTER_NAME }}
wait-for-service-stability: true
force-new-deployment: true
解説します。
name: ECS Fargate Deploy
このnameは、ワークフロー全体の名前です。
Fargateにデプロイするためのワークフローであることを示しています。
on:
push:
branches: [ main ]
メインブランチにマージするか、直接pushしたときに、動作します。
env:
AWS_REGION: ap-northeast-1
ECS_CLUSTER_NAME: digital-ichiba-cluster
ECS_SERVICE_NAME: digital-ichiba-service
ECS_TASK_DEFINITION_FILE: .aws/task_definition.json
ECR_REGISTRY: 575357958164.dkr.ecr.ap-northeast-1.amazonaws.com
ECR_REGISTRY_NAME: digital-ichiba-ecr
AWS_ROLE_TO_ASSUME: arn:aws:iam::575357958164:role/DigitalIchibaGitHubActionsDeployRole
このファイルの下の方で使う予定の環境変数です。
AWS_REGION
GitHub Actions が操作を行う AWS のリージョンを指定する。
ECS_CLUSTER_NAME
デプロイ先として使用する ECS クラスターの名前を指定する。
ECS_SERVICE_NAME
更新対象となる ECS サービスの名前を指定する。
ECS_TASK_DEFINITION_FILE
GitHub Actions が読み込んで使うタスク定義 JSON ファイルのパスを指定する。
ECR_REGISTRY
ECR のレジストリ URL を指定し、ここをイメージの push 先として使用する。
ECR_REGISTRY_NAME
Docker イメージを push するために使用する ECR のリポジトリ名を指定する。
AWS_ROLE_TO_ASSUME
GitHub Actions が OIDC を通して引き受ける IAM ロールの ARN を指定する。
permissions:
id-token: write
contents: read
GitHub Actions が AWS に安全にアクセスするために必要な最小限の権限をここで宣言します。
id-token: write
GitHub Actions が OIDC 用の ID トークンを発行できるようにするための権限。
contents: read
リポジトリ内のコードを読み取って actions/checkout を実行できるようにするための権限。
jobs:
deploy:
name: Deploy Backend to ECS Fargate
runs-on: ubuntu-latest
timeout-minutes: 10
jobs:
ジョブを定義します。jobsと書くことは決まりごとです。
deploy:
こちらはこのファイルの中でのジョブの名前で、自分でつけられます。
name: Deploy Backend to ECS Fargate
GitHub上で見るジョブの名前です。
runs-on: ubuntu-latest
Ubuntuの最新版を使います。
これをランナーと言います。
timeout-minutes: 10
ジョブが働ける最大時間の分です。
これを超えると失敗として扱われます。
設定しないと、いつまで立っても終わらない場合があります。
steps:
- name: Get source code
uses: actions/checkout@v5
steps:
ジョブを細分化した具体的な作業です。
name: Get source code
ここのnameはステップの名前です。
uses: actions/checkout@v5
GitHub Actionsが公式で用意してくれたactionsを使います。
ここではactions/checkoutのv5を使って、
リポジトリにあるコードを先ほど用意したUbuntuのランナーにコピーします。
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}
aws-actions/configure-aws-credentials@v5というアクションを使って、
GitHubにAWSの権限を与えています。
自分が先ほどOIDCのために作ったロールからARNを取ってきて、
ここで使っています。
リージョンは東京を与えています。
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
aws-actions/amazon-ecr-login@v2というアクションを使って、
ECRにログインします。
idというのは、このステップにつける識別子で、
このファイルの下の方で、このステップを指定するのに使います。
- name: Build and push image to ECR
run: |
docker build -t $ECR_REGISTRY_NAME .
docker tag $ECR_REGISTRY_NAME:latest $ECR_REGISTRY/$ECR_REGISTRY_NAME:latest
docker push $ECR_REGISTRY/$ECR_REGISTRY_NAME:latest
run:を使ってランナーの中で、コマンドを実行します。
Dockerイメージをビルドして、
ECRのためにタグをつけて、
ECRにプッシュしています。
- name: Render Amazon ECS task definition
id: render-digital-ichiba-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ env.ECS_TASK_DEFINITION_FILE }}
container-name: digital-ichiba
image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REGISTRY_NAME }}:latest
このステップは、タスク定義ファイルをもとに、
コンテナのイメージをECRにプッシュした最新のものに差し替えて、
タスク定義を更新することをやっています。
タスク定義ファイルの詳細は後で説明します。
プロジェクト直下に.aws/task_definition.jsonに置きます。
idを定義しているのは、
このステップで更新した後のタスク定義ファイルを後で使うからです。
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-digital-ichiba-container.outputs.task-definition }}
service: ${{ env.ECS_SERVICE_NAME }}
cluster: ${{ env.ECS_CLUSTER_NAME }}
wait-for-service-stability: true
force-new-deployment: true
このステップでは、ECSサービスに対して、
更新したタスク定義ファイルを使って、新しいコンテナをデプロイさせます。
task-definition: ${{ steps.render-digital-ichiba-container.outputs.task-definition }}
前のステップで、タスク定義ファイルのイメージを最新に書き換えたものが、
outputsとして出力されるので、それを使います。
wait-for-service-stability: true
デプロイだけで終わらせず、
新タスクが起動、oldタスクの停止、ヘルスチェックなど、
安定するまで、Actionsを待機させ、安定性を高めます。
force-new-deployment: true
デプロイを強制させます。
これで更新させます。
ワークフローは以上です、
次はタスク定義ファイルを見ていきます。
タスク定義ファイル
本題のタスク定義ファイルに入る前に、
タスクロールとタスク実行ロールのおさらいをしておきます。
タスクロールのおさらい
タスクロールというのは、
コンテナの中で動くアプリケーションに付与するロールです。
この場合コンテナの中で動くRailsに付与するロールになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::aws-training-shu",
"arn:aws:s3:::aws-training-shu/*"
]
}
]
}
フルアクセスは危険なので、
DigitalIchibaS3CRUDというS3を操作できるポリシーを作りました。
これをさらにアタッチした、DigitalIchibaEcsTaskRoleというロールを作成しました。
このロールを後で、タスク定義ファイルで指定して使います。
タスク実行ロールのおさらい
タスク実行ロールは、ECS/Fargateに付与するロールです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:575357958164:secret:digital-ichiba-9eAozm"
}
]
}
DigitalIchibaAccessSecretManagerという名前のポリシーを定義します。
コンテナにシークレットマネージャーにあるシークレットを渡すのは、
タスク実行ロール側です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
]
}
更に、ログの操作を許可するポリシーを作って、
DigitalIchibaExecRoleLogsと名前をつけます。
DigitalIchibaEcsTaskExecRoleという名前で、タスク実行ロールを作成します。
AWS管理のAmazonECSTaskExecutionRolePolicyというポリシーと、
自作のDigitalIchibaAccessSecretManagerとDigitalIchibaExecRoleLogsをアタッチします。
タスク定義ファイル本体
プロジェクトのバックエンド直下に、
.aws/task_definition.jsonを作ります。
{
"family": "digital-ichiba-task",
"taskRoleArn": "arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskRole",
"executionRoleArn": "arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskExecRole",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "digital-ichiba",
"image": "575357958164.dkr.ecr.ap-northeast-1.amazonaws.com/digital-ichiba-ecr:latest",
"essential": true,
"memory": 384,
"memoryReservation": 128,
"portMappings": [
{
"containerPort": 3000,
"hostPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{ "name": "RAILS_ENV", "value": "production" }
],
"secrets": [
{
"name": "SECRET_KEY_BASE",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:575357958164:secret:digital-ichiba-9eAozm:SECRET_KEY_BASE::"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/digital-ichiba",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "digital-ichiba",
"awslogs-create-group": "true",
"mode": "non-blocking",
"max-buffer-size": "25m"
}
}
}
]
}
環境変数とシークレットがそれなりにあって、冗長なので省略しています。
解説します。
{
"family": "digital-ichiba-task",
"taskRoleArn": "arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskRole",
"executionRoleArn": "arn:aws:iam::575357958164:role/DigitalIchibaEcsTaskExecRole",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
ここはECSのタスク全体の設定です。
family は ECS のタスク定義をグルーピングするための名前です。
同じ family 名のもとで、デプロイのたびにリビジョンが増えていきます。
executionRoleArnでタスク実行ロールをARNで指定します。
taskRoleArnでタスクロールをARNで指定します。
Fargate を使用するため、
networkMode は awsvpc、requiresCompatibilities には FARGATE を指定します。
cpuとmemoryはタスク全体に割り当てるサイズです。
今回は動作確認用の最小構成を指定しています。
"containerDefinitions": [
{
"name": "digital-ichiba",
"image": "575357958164.dkr.ecr.ap-northeast-1.amazonaws.com/digital-ichiba-ecr:latest",
"essential": true,
"memory": 384,
"memoryReservation": 128,
"portMappings": [
{
"containerPort": 3000,
"hostPort": 3000,
"protocol": "tcp"
}
]
}
]
ここからは、コンテナの定義です。
nameはコンテナの名前です。
imageは使うイメージですが、ここはActionsで書き換えられるように設定しました。
essentialは必須なので、trueにします。
memoryはこのコンテナの使用できるメモリの上限値、
これを超えるとコンテナは強制終了します。
memoryReservationは、最低限確保されるメモリです。
ポートはロードバランサーのターゲットグループで、
Railsのために3000に送るようにしているので、
3000:3000とします。
"environment": [
{
"name": "RAILS_ENV",
"value": "production"
}
],
"secrets": [
{
"name": "SECRET_KEY_BASE",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:575357958164:secret:digital-ichiba-9eAozm:SECRET_KEY_BASE::"
}
]
環境変数とシークレットは、
複数ありますが、省略しました。
環境変数はnameとvalueを使って定義します。
シークレットは見られたら困るものをシークレットマネージャーに登録して
valueFromにARN:キー名::のように書くと渡すことができます。
ポリシーを作って、ロールにアタッチしたので、
セキュアに取り出すことができるようになります。
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/digital-ichiba",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "digital-ichiba",
"awslogs-create-group": "true",
"mode": "non-blocking",
"max-buffer-size": "25m"
}
}
この設定は、ECS/Fargate 上で動くコンテナのログを
CloudWatch Logs に出力するための設定です。
logDriver: awslogs
ECS の標準ログドライバを使い、CloudWatch Logs にログを送ります。
awslogs-group: /ecs/digital-ichiba
ログを出力する CloudWatch Logs のロググループ名です。
awslogs-region: ap-northeast-1
ログを送信するリージョンを指定します。
awslogs-stream-prefix: digital-ichiba
タスク・コンテナごとに作られるログストリームの接頭辞です。
awslogs-create-group: true
ロググループが存在しない場合、自動で作成します。
mode: non-blocking
ログ送信で詰まってアプリが止まらないように、非同期でログを送ります。
max-buffer-size: 25m
ログ送信のためのバッファサイズを指定します。
これでタスク定義ファイルの設定が終わりました。
自動デプロイをテストする
get "up" => "rails/health#show", as: :rails_health_check
get "health" => "rails/health#show", as: :health_check
Railsのルーティングには元々upがあるのですが、
healthというルーティングを作って同じように緑の画面が出るか試します。
GitHubにプッシュして、メインにマージします。
ジョブが成功すると、新しいコンテナがデプロイされます。
URLでhttps://api.aws-training-shu.com/healthで、
緑の画面になったので、
自動デプロイができるようになりました。
まとめ
この記事では、ECS/Fargate への手動デプロイを、
GitHub Actions + OIDC を使って自動化しました。
- main ブランチにマージすると自動でデプロイ
- Docker イメージをビルドして ECR に push
- タスク定義と ECS サービスを更新
- アクセスキーを使わず、安全に認証
/health エンドポイントで動作確認もでき、
マージ=即反映の CI/CD が完成しました。
これにより、
「デプロイ作業に頭を使う時間」を減らし、
「アプリの実装と改善」に集中できる状態を作れました。
個人開発でもチーム開発でも、
壊れにくく・安全で・再現性のあるデプロイ基盤を
最小構成で作れたのが一番の収穫です。