Cloud Build¶
Four triggers, one service account, two output repos. The triggers fire on activity in the tomoda-labs/tomoda GitHub repo and push Docker images to Artifact Registry.
Code: infrastructure/gcp/cloudbuild.tf, infrastructure/gcp/cloudbuild_iam.tf.
Pipeline definitions (cloudbuild-backend.yaml, cloudbuild-frontend.yaml) live in the tomoda repo, not here — see the tomoda CI docs.
Trigger matrix¶
| Trigger | Fires on | Target repo | Approval |
|---|---|---|---|
backend-push-trigger |
Push to main, paths backend/** or cloudbuild-backend.yaml |
tomoda-dev-repo |
Manual |
frontend-push-trigger |
Push to main, paths frontend/** or cloudbuild-frontend.yaml |
tomoda-dev-repo |
Manual |
backend-release-trigger |
Tag matching ^v[0-9]+\.[0-9]+\.[0-9]+$ |
tomoda-prod-repo |
Auto |
frontend-release-trigger |
Tag matching ^v[0-9]+\.[0-9]+\.[0-9]+$ |
tomoda-prod-repo |
Auto |
flowchart LR
subgraph GH["GitHub: tomoda-labs/tomoda"]
MAIN[Push to main]
TAG[Push tag v*.*.*]
end
MAIN -->|backend/**| BPT[backend-push-trigger<br/>approval required]
MAIN -->|frontend/**| FPT[frontend-push-trigger<br/>approval required]
TAG -->|backend/**| BRT[backend-release-trigger]
TAG -->|frontend/**| FRT[frontend-release-trigger]
BPT --> DEV[(tomoda-dev-repo<br/>tag: latest)]
FPT --> DEV
BRT --> PROD[(tomoda-prod-repo)]
FRT --> PROD
Tag-based prod = single source of truth
The only way to get an image into tomoda-prod-repo is to push a semver-shaped tag to main. There is no manual docker push, no per-environment branch, and no release/* branch — the tag itself is the release artifact.
Substitutions¶
Triggers pass environment-specific values into the YAML via substitutions. Both YAML files live in the tomoda repo; this Terraform code only sets the variables.
Common to all four triggers¶
| Substitution | Source |
|---|---|
_REGION |
var.region (always asia-east1) |
_REPO_NAME |
dev triggers → tomoda-dev-repo; release triggers → tomoda-prod-repo |
Dev push triggers add¶
| Substitution | Value |
|---|---|
TAG_NAME |
latest (push triggers re-tag latest on every successful build) |
Frontend triggers add¶
| Substitution | Dev value | Prod value |
|---|---|---|
_WS_URL |
wss://api-dev.tomoda.life/ws |
wss://api.tomoda.life/ws |
_FRONTEND_API_URL |
https://api-dev.tomoda.life/api/v1 |
https://api.tomoda.life/api/v1 |
_GOOGLE_WEB_CLIENT_ID |
dev web client | prod web client |
_GOOGLE_IOS_CLIENT_ID |
dev iOS client | placeholder (YOUR_PROD_IOS_CLIENT_ID) |
_GOOGLE_ANDROID_CLIENT_ID |
dev Android client | placeholder (YOUR_PROD_ANDROID_CLIENT_ID) |
Prod mobile client IDs are placeholders
frontend_release_trigger ships with _GOOGLE_IOS_CLIENT_ID = "YOUR_PROD_IOS_CLIENT_ID" and the equivalent for Android. Any prod frontend build will bake those literal strings into the bundle, breaking Google sign-in on mobile. Fill these in before cutting a prod release.
Service account & IAM¶
All four triggers use a single dedicated SA: cloudbuild-worker-sa@${project_id}.iam.gserviceaccount.com. The default Cloud Build SA (287267207777@cloudbuild.gserviceaccount.com) is granted roles/iam.serviceAccountUser on it so the platform can impersonate it when running the build.
| Role | What it enables |
|---|---|
roles/logging.logWriter |
Builds can stream logs to Cloud Logging |
roles/artifactregistry.writer |
Push image layers to both dev and prod repos |
roles/container.developer |
Reserved for future post-build kubectl steps (not currently used) |
The roles/container.developer binding is forward-looking — today, deployment is done via Argo CD pulling from Argo CD Image Updater watching the registry, not by Cloud Build kubectl-applying. Until we add a kubectl step to one of the YAMLs, that role is unused.
Approvals¶
Dev triggers require manual approval (approval_config { approval_required = true }). Push a commit to main, Cloud Build queues a pending build, and someone with roles/cloudbuild.builds.approver must click "approve" in the console before it runs. Prod triggers fire automatically because pushing a semver tag is itself the human decision.
Build outputs¶
Every successful build pushes a Docker image to the configured Artifact Registry repo. From there:
- Dev: Argo CD Image Updater detects the new
:latestdigest and patches the dev Argo CD application's image tag. - Prod: same flow, but pinned to the semver tag the trigger built.
See Artifact Registry for the repo layout and Argo CD for the GitOps side.
When triggers don't fire¶
Common reasons a push to main doesn't queue a build:
- No matching files changed.
backend-push-triggerhasincluded_files = ["backend/**", "cloudbuild-backend.yaml"]. A PR that only touchesdocs/orfrontend/will not start a backend build. - GitHub App permissions. The Cloud Build GitHub App must remain installed on the
tomoda-labs/tomodarepo. Removing it silently disables every trigger. - Approval queue full. Pending-approval builds sit forever — they don't time out. Look in Cloud Build → History → "Filtered by status: Pending".