@jinbatrail/service、apps/web、およびブラウザー/ワーカーコンテナ)をビルド、テスト、デプロイする2つのGitHub Actionsワークフロー、.github/workflows/ci.yml と .github/workflows/deploy.yml を説明します。これはプロダクトを稼働させるインフラの運用ドキュメントであり、flow deploy モデル(Pack digestをfrontに公開すること)の説明ではありません。この2つは「deploy」の意味が異なります。このページはサービス自体を出荷する話です。
これは、
.github/workflows/ci.yml、.github/workflows/deploy.yml、deploy/terraform/github-oidc.tf と照合して確認した、ワークフローファイルが今日実際に行っていることを説明します。CI — 正しさのゲート
main へのすべてのPRとpushで実行。必須の gates jobと、情報提供のみの full-suite jobに分割。Deploy — ビルド・ゲート・ロール
3つのcontainer imageをビルドし、実Postgresに対して耐久性を証明し、4つのECS Fargate serviceを段階的にロール。
CI — 正しさのゲート(ci.yml)
CIは、main へのすべてのpull requestと、main へのすべてのpushで実行されます。パイプラインが、リポジトリに既知の既存のredがあることを曖昧にせず、正直に扱えるよう、意図的に2つのjobに分かれています。
| Job | 範囲 | マージをブロックするか |
|---|---|---|
gates | ツリー全体の biome check . に加え、@jinbatrail/service、@browserflow/web-api、@browserflow/core にスコープした typecheck + test | はい — これは必須のbranch protection checkです |
full-suite | turbo によるmonorepo全体の typecheck + test | いいえ — continue-on-error: true で、情報提供のみです |
full-suite には、今日時点で既知かつ許容されているredが1つあります。Playwright型付きのtypecheckです。これは playwright がoptional/peer dependencyであり、スリムなCIイメージにはその型がインストールされていないためです。gates はパッケージのallow-listです。パッケージは実際にgreenになったときだけそこへ昇格し、そのtypecheck/testがブロック対象になります。
Postgres conformance suite(
postgres-wrapper-store.spec.ts)は、ci.yml ではライブデータベースに対して実行されません。JT_TEST_DATABASE_URL が設定されていないため、live blockは自分でskipし、「fake client」モードのままになります。このsuiteを実Postgresで実行するのは、後述する deploy.yml のdurability gateです。また、ワークフロー全体に JT_ENV=ci も設定されているため、CI実行がruntimeのproduction boot gateに引っかかることはありません。env: で固定されています。pnpm 10.34.3、Node 22、Bun 1.3.14 です(serviceとweb-apiのtest suiteは bun test で実行されます)。concurrency によりbranch/PRごとに1 runのみが強制され、新しいpushは古い実行中runをキャンセルします。
Deploy — ビルド、ゲート、ロール(deploy.yml)
deploy.yml は3つのコンテナイメージをビルドし、実Postgresに対して耐久性を証明し、シークレットを同期し、4つのECS Fargateサービスをロールします。各stageが次のstageのゲートになります。
トリガーとenvironment
| トリガー | 対象environment |
|---|---|
main へのpush | staging(continuous delivery) |
v* に一致するtagのpush | production — guarded |
手動の workflow_dispatch | staging / production のchoice inputから選んだenvironment |
concurrency はtarget environmentごと(deploy-staging / deploy-production)にスコープされ、cancel-in-progress: false です。同じenvironmentへのdeployは重ならず、実行中のロールアウトは途中でキャンセルされません。rolling ECS updateをキャンセルすると、半分だけ昇格したserviceが取り残される可能性があるためです。
Auth: OIDC、長期キーなし
deploy.yml は permissions: id-token: write を要求し、AWSに触れるすべてのjobで aws-actions/configure-aws-credentials を使って認証します。OIDC経由で secrets.AWS_DEPLOY_ROLE_ARN をassumeします。GitHubには静的なAWS access keyは保存されていません。deploy/terraform/github-oidc.tf によると、次の通りです。
- trust policyは、audience
sts.amazonaws.comで、repo:carnot-tech/jinba-trail-app:*(任意のbranch/tag/environment)向けに発行されたtokenを受け入れます。 - roleができることは、最小権限でスコープされた次の3つだけです。
- 3つのproject ECR repo(
jinbatrail-api、jinbatrail-web、jinbatrail-browser)へのimageのpush/pull。 ecs:UpdateService(force-new-deployment)に加え、Project=jinbatrailとtag付けされたresourceにスコープされたread-onlyのDescribe/List。- ちょうど1つのsecret(Anthropic key、後述)に対する
secretsmanager:PutSecretValue— write-only です。このroleには、そのsecretにも他のどのsecretにもGetSecretValuegrantはなく、RDSや他のinfraにも触れられません。
- 3つのproject ECR repo(
Stage 1 — build-and-push
3-way matrix(api、web、browser)が、deploy/docker/ 配下の各Dockerfileをビルドし、ECRへpushします。
| Matrix image | Dockerfile | ECR repo var |
|---|---|---|
api | deploy/docker/Dockerfile.service | vars.ECR_REPO_API |
web | deploy/docker/Dockerfile.web | vars.ECR_REPO_WEB |
browser | deploy/docker/Dockerfile.browser | vars.ECR_REPO_BROWSER |
github.sha と、解決されたenvironmentに応じたmoving tagです。stagingでは :latest、productionでは :production です。これにより、staging buildが現在productionで稼働しているものを動かすことはありません。web imageはさらに、GitHub environment varsをsourceとして、NEXT_PUBLIC_API_BASE、JT_API_PROXY_TARGET、NEXT_PUBLIC_API_WS_BASE をDocker build-argsとして焼き込みます。
Stage 2 — durability-gate
このページが説明するために存在しているstageです。bun test src/postgres-wrapper-store.spec.ts(working directoryは packages/service)を、実在する一時的なPostgres、つまりstaging RDSではなく postgres:16-alpine GitHub Actions service containerに対して実行します。
packages/service/src/postgres-wrapper-store.spec.ts を直接読むと、このsuiteは2層構造です。
- Always-on(live DB不要): module importにside effectはなく(
pgはstore factoryの内部でだけ触られます)、共有のwrapperStoreConformancesuite全体が、注入されたfakePgClientにbackされたstoreに対して実行されます。すべてのwrite-through code path(DDL、upsert、delete、hydrationSELECT)が、ネットワークなしでexerciseされます。 - Guarded, live-Postgres block:
JT_TEST_DATABASE_URL(またはJINBA_TEST_PG_URL)が設定されているとき、つまりdeploy.ymlが一時service containerを指して設定しているとき、同じconformance suiteが実databaseに対して再実行されます。さらに、writeをflushし、新しいstore instanceを開き直して、rowが実際に永続化され正しくre-hydrateされることを証明するdurability assertionも実行されます。env varが設定されていない場合(ci.ymlの場合)、このblockはきれいにself-skipするため、DBなしのCIはgreenのままです。
ci.yml はfake clientに対してstoreがinterface-conformantであることを証明します。deploy.yml のdurability gateは、それに加えて、実Postgres round-tripを本当に生き残ることを証明します。そしてそれを、new imageにtrafficが届く前、すべてのdeployで毎回行います。
Stage 2b — sync-secrets(durability gateと並列)
ANTHROPIC_API_KEY GitHub secretをAWS Secrets Managerへpushします。これにより、authoring agentのClaude keyのrotationは「GitHub secretを更新 → deploy」で済み、手動のAWS手順は不要になります。target secret idは、vars.ANTHROPIC_SECRET_ID が設定されていればそこから解決し、そうでなければ vars.ECS_CLUSTER から導出します(${stack}-cluster → ${stack}/ANTHROPIC_API_KEY。Terraform管理のsecretと一致します)。ECSはcontainer start時に valueFrom を解決するため、新しくロールされたtaskは新しいsecret versionを自動的に拾います。
このjobは
continue-on-error: true ですが、意図的に目立つようにしています。GitHub secretが未設定なら、noticeを出してexit 0します(containerはTerraformでseedされたplaceholder keyのままです)。書き込みが拒否された場合(例: IAM grantがまだ適用されていない場合)は失敗し、checks tabでredのままになります。ただし、container blue/greenはこのkeyがfreshであることに依存しないため、deploy-ecs はブロックしません。Stage 3 — deploy-ecs
setup、build-and-push、durability-gate、および sync-secrets が実行済みである必要があります(durability gateはhard blockです。ここで失敗するとロールアウトは止まります。sync-secrets は continue-on-error なので、失敗しても止まりません)。
4-way matrixで、1度に1serviceずつ実行します(max-parallel: 1。悪いimageがあれば、4つすべてのserviceを同時に悪い状態へrollするのではなく、早い段階でロールアウトを止めます)。
ECS_SERVICE_APIECS_SERVICE_WEBECS_SERVICE_BROWSERECS_SERVICE_WORKER
新しいdeploymentを強制する
すでにregistered済みのtask definitionに対して
aws ecs update-service --force-new-deployment を実行します。ECSはStage 1でpushされたばかりのmoving image tag(:latest または :production)を再pullします。これはcontainerのロールアウトだけです。frontでliveにするflow digestをrepointすること(デプロイとロールバック ページで扱う概念)は、plane APIを通じた別のruntime actionです。このCI/CDパイプラインのcontainer imageをロールすることとは無関係です。
必須のGitHub varsとsecrets
repository-levelと明記されていない限り、GitHub environment(staging、production)ごとに設定します。
Vars — 全般
Vars — 全般
| Var | 使用箇所 | 目的 |
|---|---|---|
AWS_REGION | AWSに触れるすべてのjob | target AWS region |
ECR_REGISTRY | build-and-push | ECR registry host |
PROD_READY | setup | v* tagがproductionに解決されるには "true" である必要があります。それ以外の場合、jobは即座に失敗します |
Vars — image build
Vars — image build
| Var | 使用箇所 | 目的 |
|---|---|---|
ECR_REPO_API / ECR_REPO_WEB / ECR_REPO_BROWSER | build-and-push | imageごとのECR repo name |
NEXT_PUBLIC_API_BASE | build-and-push (web) | Docker build-argとしてweb imageに焼き込まれます |
JT_API_PROXY_TARGET | build-and-push (web) | Docker build-argとしてweb imageに焼き込まれます |
NEXT_PUBLIC_API_WS_BASE | build-and-push (web) | Docker build-argとしてweb imageに焼き込まれます |
Vars — ECS rollout
Vars — ECS rollout
| Var | 使用箇所 | 目的 |
|---|---|---|
ECS_CLUSTER | sync-secrets, deploy-ecs | target ECS cluster name |
ECS_SERVICE_API / ECS_SERVICE_WEB / ECS_SERVICE_BROWSER / ECS_SERVICE_WORKER | deploy-ecs | serviceごとのECS service name。各matrix legに1つずつ対応します |
ANTHROPIC_SECRET_ID | sync-secrets | Secrets Manager secret idの任意の明示override。未設定の場合は ECS_CLUSTER から導出されます |
関連
デプロイとロールバック
flow-digest deployモデル(Packを公開し、frontに公開すること)です。このページのcontainer CI/CDパイプラインとは別の概念です。
マネージドシークレット
planeのsecrets brokerがruntimeで
secret.* referenceを解決する方法です。上で説明した ANTHROPIC_API_KEY のGitHub → Secrets Manager同期とは別です。アクセス制御と監査
RBAC、content-addressed audit log、tenancyです。このpipelineのOIDC roleが並んで位置するgovernance layerです。
サービス(コントロールプレーン)
このpipelineがデプロイするcoordinatorです。Stage 3でロールされるECS serviceの背後で動くAPI、UI、scheduler、workersです。