Skip to content

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 :latest digest 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:

  1. No matching files changed. backend-push-trigger has included_files = ["backend/**", "cloudbuild-backend.yaml"]. A PR that only touches docs/ or frontend/ will not start a backend build.
  2. GitHub App permissions. The Cloud Build GitHub App must remain installed on the tomoda-labs/tomoda repo. Removing it silently disables every trigger.
  3. Approval queue full. Pending-approval builds sit forever — they don't time out. Look in Cloud Build → History → "Filtered by status: Pending".