このページでは、ホスト型Jinba Trailサービス@jinbatrail/serviceapps/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.ymldeploy/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-suiteturbo による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のゲートになります。
sync-secretsdurability-gate はどちらも setup + build-and-push だけに依存するため、並列に実行されます。deploy-ecs は3つすべてを待ちます。

トリガーとenvironment

トリガー対象environment
main へのpushstaging(continuous delivery)
v* に一致するtagのpushproductionguarded
手動の workflow_dispatchstaging / production のchoice inputから選んだenvironment
Productionはguardされています。setup jobでは、production GitHub environment上の vars.PROD_READY == "true" の場合に限り、v* tagが environment=production に解決されます。それ以外の場合、jobはprod stackをprovisionしてそのvarを設定するよう明示するエラーで即座に失敗します。これは、v* tagが、provisionされていない、または誤設定されたproduction varsへ誤ってデプロイされることが絶対にないようにするためです。
concurrency はtarget environmentごと(deploy-staging / deploy-production)にスコープされ、cancel-in-progress: false です。同じenvironmentへのdeployは重ならず、実行中のロールアウトは途中でキャンセルされません。rolling ECS updateをキャンセルすると、半分だけ昇格したserviceが取り残される可能性があるためです。

Auth: OIDC、長期キーなし

deploy.ymlpermissions: 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-apijinbatrail-webjinbatrail-browser)へのimageのpush/pull。
    • ecs:UpdateService(force-new-deployment)に加え、Project=jinbatrail とtag付けされたresourceにスコープされたread-onlyの Describe / List
    • ちょうど1つのsecret(Anthropic key、後述)に対する secretsmanager:PutSecretValuewrite-only です。このroleには、そのsecretにも他のどのsecretにも GetSecretValue grantはなく、RDSや他のinfraにも触れられません。

Stage 1 — build-and-push

3-way matrix(apiwebbrowser)が、deploy/docker/ 配下の各Dockerfileをビルドし、ECRへpushします。
Matrix imageDockerfileECR repo var
apideploy/docker/Dockerfile.servicevars.ECR_REPO_API
webdeploy/docker/Dockerfile.webvars.ECR_REPO_WEB
browserdeploy/docker/Dockerfile.browservars.ECR_REPO_BROWSER
これらは意図的に別々のimageとしてビルドされます。browser imageは約500MBのChromium layerを含むため、API imageを肥大化させてはいけません。 各imageは2つのtagでpushされます。不変の github.sha と、解決されたenvironmentに応じたmoving tagです。stagingでは :latest、productionでは :production です。これにより、staging buildが現在productionで稼働しているものを動かすことはありません。web imageはさらに、GitHub environment varsをsourceとして、NEXT_PUBLIC_API_BASEJT_API_PROXY_TARGETNEXT_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に対して実行します。
これは意図的に、実際のstaging databaseに対しては絶対に実行されません。postgres-wrapper-store.spec.ts は分離のためにtest case間でtableを TRUNCATE しますし、そもそもstaging RDSはprivateでhosted runnerから到達できません。service containerはjobとともに作成され、破棄されます。このcontainerが触ったものはrun後に何も残りません。
packages/service/src/postgres-wrapper-store.spec.ts を直接読むと、このsuiteは2層構造です。
  1. Always-on(live DB不要): module importにside effectはなく(pg はstore factoryの内部でだけ触られます)、共有の wrapperStoreConformance suite全体が、注入されたfake PgClient にbackされたstoreに対して実行されます。すべてのwrite-through code path(DDL、upsert、delete、hydration SELECT)が、ネットワークなしでexerciseされます。
  2. 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

setupbuild-and-pushdurability-gateおよび sync-secrets が実行済みである必要があります(durability gateはhard blockです。ここで失敗するとロールアウトは止まります。sync-secretscontinue-on-error なので、失敗しても止まりません)。 4-way matrixで、1度に1serviceずつ実行します(max-parallel: 1。悪いimageがあれば、4つすべてのserviceを同時に悪い状態へrollするのではなく、早い段階でロールアウトを止めます)。
  • ECS_SERVICE_API
  • ECS_SERVICE_WEB
  • ECS_SERVICE_BROWSER
  • ECS_SERVICE_WORKER
それぞれについて、jobは次を行います。
1

新しいdeploymentを強制する

すでにregistered済みのtask definitionに対して aws ecs update-service --force-new-deployment を実行します。ECSはStage 1でpushされたばかりのmoving image tag(:latest または :production)を再pullします。
2

安定化を待つ

aws ecs wait services-stable は、新しいtaskがECS health checkに合格し、古いtaskがdrainされるまでblockします。これはcontainer blue/greenであり、flow-digestのrepointではありません。
これはcontainerのロールアウトだけです。frontでliveにするflow digestをrepointすること(デプロイとロールバック ページで扱う概念)は、plane APIを通じた別のruntime actionです。このCI/CDパイプラインのcontainer imageをロールすることとは無関係です。

必須のGitHub varsとsecrets

repository-levelと明記されていない限り、GitHub environmentstagingproduction)ごとに設定します。
Secret使用箇所目的
AWS_DEPLOY_ROLE_ARNAWSに触れるすべてのjobassumeするOIDC role(repository-level。同じroleで、trust policyは任意のbranch/tagを許可します)
ANTHROPIC_API_KEYsync-secrets任意 — authoring agentのClaude key。未設定の場合、sync-secrets はきれいにskipします
Var使用箇所目的
AWS_REGIONAWSに触れるすべてのjobtarget AWS region
ECR_REGISTRYbuild-and-pushECR registry host
PROD_READYsetupv* tagがproductionに解決されるには "true" である必要があります。それ以外の場合、jobは即座に失敗します
Var使用箇所目的
ECR_REPO_API / ECR_REPO_WEB / ECR_REPO_BROWSERbuild-and-pushimageごとのECR repo name
NEXT_PUBLIC_API_BASEbuild-and-push (web)Docker build-argとしてweb imageに焼き込まれます
JT_API_PROXY_TARGETbuild-and-push (web)Docker build-argとしてweb imageに焼き込まれます
NEXT_PUBLIC_API_WS_BASEbuild-and-push (web)Docker build-argとしてweb imageに焼き込まれます
Var使用箇所目的
ECS_CLUSTERsync-secrets, deploy-ecstarget ECS cluster name
ECS_SERVICE_API / ECS_SERVICE_WEB / ECS_SERVICE_BROWSER / ECS_SERVICE_WORKERdeploy-ecsserviceごとのECS service name。各matrix legに1つずつ対応します
ANTHROPIC_SECRET_IDsync-secretsSecrets 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です。