Deployment
Overview
| Target | Trigger | Pipeline |
|---|---|---|
| Cloud Run (staging) | Push to main | .github/workflows/deploy.yml |
| Cloud Run (production) | Published GitHub release tag with production deploy opt-in enabled | .github/workflows/deploy.yml |
| Cloudflare Pages (React applications) | Push to main | .github/workflows/cloudflare-pages.yml |
| Cloudflare Pages (Docs and Support Docs) | GitHub release or production-enabled manual dispatch | .github/workflows/cloudflare-pages.yml |
| Kotlin SDK (Maven) | Every push to main (dev-<commit>) and GitHub releases | .github/workflows/sdk-publish.yml |
| TypeScript SDK (npm) | GitHub release | .github/workflows/sdk-npm-publish.yml |
Cloud Run Deployment Flow
- Build: Bazel builds OCI images for all 8 services
- Push: Images pushed to Artifact Registry (
us-east1-docker.pkg.dev/pinpoint-gateway/gateway/{service}:{tag}) - Deploy:
gcloud run deployto Cloud Run with appropriate env vars and secrets
Image Tagging
| Environment | Tag | Example |
|---|---|---|
| Staging | dev | gateway-processing:dev |
| Production | Release tag | gateway-processing:v0.0.3 |
CRITICAL: Production must use a pinned immutable release tag. Never use latest or dev for production.
Cloud Run Services
Staging and production use the same service names; the environment is
distinguished by GCP project (peakgateway-staging vs pinpoint-gateway),
not by a -staging name suffix.
| Service | Name (both projects) |
|---|---|
| auth | gateway-auth |
| processing | gateway-processing |
| management | gateway-management |
| online-txn | gateway-online-txn |
| card-present | gateway-card-present |
| merchant-onboarding | gateway-merchant-onboarding |
| status | gateway-status |
| mcp | gateway-mcp |
Region: us-east1. Staging project: peakgateway-staging; production
project: pinpoint-gateway. Container images for both environments live in
pinpoint-gateway's Artifact Registry (staging pulls cross-project).
Scaling Configuration
Terraform defines Cloud Run scaling in infra/tf/gcp/variables.tf and applies
it from infra/tf/gcp/modules/cloud-run/main.tf:
| Setting | Terraform defaults |
|---|---|
| Warm services | Production: auth, management, merchant-onboarding, online-txn, processing; staging: auth |
| Warm min instances | 1 |
| Request-based min instances | 0 |
| Max instances | auth/management/online-txn 30; card-present/processing 20; merchant-onboarding/status 10; mcp falls back to 10 |
| CPU | 1 |
| Memory | 2Gi |
Deployment Verification and Rollout Evidence
A merge or push to main, a green validation run, or a successful image
build/push proves only that a candidate artifact was produced for the staging
path. It is not evidence that production was deployed.
Production requires a published GitHub release tag and the GitHub Actions repository
variable PRODUCTION_DEPLOY_ENABLED to be set to true; workflow dispatch alone
cannot enter the production deployment job.
Treat a rollout as verified only when all of the following are true:
- The target Cloud Run revision is Ready and runs the intended immutable image (prefer the image digest, not only a mutable tag).
- Service traffic points to that revision. A revision existing or being Ready is not enough.
- The appropriate health or surface check passes: use a public route smoke check for a public service, and an authenticated/internal health or surface check for an internal-only service such as processing.
Build, push, and workflow-success indicators are supporting evidence only. Inspect the service traffic and revision image after the rollout, then perform the environment-appropriate check:
# Inspect the revision receiving traffic (staging; use --project=pinpoint-gateway for prod)
gcloud run services describe gateway-processing \
--region=us-east1 \
--project=peakgateway-staging \
--format='yaml(status.latestReadyRevisionName,status.traffic)'
# Inspect the immutable image and readiness of that revision
gcloud run revisions describe REVISION \
--region=us-east1 \
--project=peakgateway-staging \
--format='yaml(spec.containers[0].image,status.conditions)'
# View logs when the health or surface check needs runtime context
gcloud logging read 'resource.labels.service_name="gateway-processing"' --project=peakgateway-staging --limit=20 --format='value(jsonPayload.message)'
For example, the public status route can be checked as follows. Processing is internal-only and has no public load-balancer route:
curl https://staging-api.peakgateway.co/status/health
Website Deployment
React application staging hostnames deploy automatically on pushes to main.
Production React hostnames, Docs, and Support Docs deploy only from a GitHub release
or a production-enabled manual dispatch. Docs and Support Docs have no staging
hostname.
| Website | Production URL | Staging URL |
|---|---|---|
| Portal | portal.peakgateway.co | staging-portal.peakgateway.co |
| Checkout | checkout.peakgateway.co | staging-checkout.peakgateway.co |
| Merchant Portal | merchant.peakgateway.co | staging-merchant.peakgateway.co |
| Marketing | peakgateway.co | staging.peakgateway.co |
| Docs | docs.peakgateway.co | N/A |
| Support Docs | support-docs.peakgateway.co | N/A |
Cloudflare Pages is a separate deployment surface from Cloud Run. Validate the
exact hostname for the intended environment: the staging-* hostnames (and
staging.peakgateway.co for Marketing) are distinct from their production
hostnames. A green Pages build, or a check against one hostname, does not prove
the other environment is deployed or serving the target site. Docs and Support
Docs currently have no staging hostname in this table.
Infrastructure Changes
Terraform manages all GCP infrastructure. Remote state and runs use Terrakube
(iac-api.peaksuite.dev) with separate gateway production and
gateway-staging workspaces. The checked-in backend selectors make local
read-only planning explicit:
cd infra/tf/gcp
# Staging
terraform init -reconfigure -backend-config=backend-staging.hcl
terraform plan
# Production (reinitialize before switching environments)
terraform init -reconfigure -backend-config=backend-prod.hcl
terraform plan
Apply through the authorized Terrakube workspace. Do not run a local apply against production.
Maintenance
Cleaning old Cloud Run revisions
# Dry run (default)
./infra/scripts/cleanup-cloud-run-revisions.sh \
--project pinpoint-gateway \
--region us-east1 \
--all-services \
--keep 3
# Apply
./infra/scripts/cleanup-cloud-run-revisions.sh \
--project pinpoint-gateway \
--region us-east1 \
--all-services \
--keep 3 \
--apply
This runs automatically hourly via .github/workflows/infra-maintenance.yml.
CI/CD Pipelines
| System / workflow | Trigger | Purpose |
|---|---|---|
BuildBuddy validation (buildbuddy.yaml) | PR, merge queue, push to main | Lint, drift, backend, website, SDK, schema, IaC, integration, package, and container validation |
BuildBuddy nightlies (buildbuddy.yaml) | Daily schedules | Manual-tagged integration tests and uncached Android consumer smoke |
deploy.yml | Push to main, release, manual | Build, sign, push, and deploy Cloud Run services |
cloudflare-pages.yml | Push to main, release, manual | Build and deploy all websites |
infra-maintenance.yml | Hourly | Clean old Cloud Run revisions and expired GitHub artifacts |
sdk-publish.yml | Push to main, release, manual | Publish Kotlin SDK development or release artifacts to Maven |
sdk-npm-publish.yml | Release, manual | Publish or dry-run the TypeScript SDK package |
apple-ci.yml | Apple SDK changes | Run macOS/Xcode validation unavailable to Linux BuildBuddy executors |