Skip to main content

Backend Deployment Method - Current Practice

Summary

Primary Method: Manual Source Deploy (Option 2 from docs/DEPLOYMENT.md) Service: payroll-pipeline-cbs (Config Plane legacy/compat label; canonical map: payroll-pipeline-cbs-api in GCP_STACK_BASELINE) Status: This is the hardened, production-tested method

Current Deployment Method (PRIMARY)

Method: gcloud run deploy --source .

Why this method:
  • ✅ Builds and deploys in one step (no separate image build)
  • ✅ Uses Cloud Build automatically (no Docker required locally)
  • ✅ Fastest and most reliable method
  • This is what’s actually used in practice (per docs/DEPLOYMENT.md line 101)

Command (from docs/DEPLOYMENT.md):

# From repo root
FULL_SHA=$(git rev-parse HEAD)
SHORT_SHA=$(git rev-parse --short HEAD)

gcloud run deploy payroll-pipeline-cbs \
  --source . \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --service-account=sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --set-secrets=JWT_SECRET_KEY=jwt-secret:latest \
  --set-env-vars="GCP_PROJECT_ID=payroll-bi-gauntlet,CORS_ORIGINS=https://payroll-pipeline-cbs.vercel.app,GIT_COMMIT_SHA=${FULL_SHA}" \
  --update-labels="git-sha=${SHORT_SHA}"

Phase 8E Enhanced Version (adds resource limits):

# From repo root
FULL_SHA=$(git rev-parse HEAD)
SHORT_SHA=$(git rev-parse --short HEAD)

gcloud run deploy payroll-pipeline-cbs \
  --source . \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --service-account=sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --set-secrets=JWT_SECRET_KEY=jwt-secret:latest \
  --set-env-vars="GCP_PROJECT_ID=payroll-bi-gauntlet,CORS_ORIGINS=https://payroll-pipeline-cbs.vercel.app,GIT_COMMIT_SHA=${FULL_SHA}" \
  --update-labels="git-sha=${SHORT_SHA}" \
  --memory=1Gi \
  --cpu=1 \
  --max-instances=10 \
  --min-instances=0 \
  --timeout=300 \
  --concurrency=80
Key differences:
  • Adds explicit resource limits (memory, CPU, instances, timeout, concurrency)
  • Same core deployment method (--source .)

Alternative Methods (Not Primary)

Option 1: Cloud Build via cloudbuild.yaml

Status: May not be operational (per docs: “Git-triggered Cloud Build may not be operational”) Command:
gcloud builds submit . --config=cloudbuild.yaml \
  --project=payroll-bi-gauntlet \
  --substitutions=_SERVICE_NAME=payroll-pipeline-cbs,_PROJECT_ID=payroll-bi-gauntlet,_REGION=us-central1,_COMMIT_SHA=$(git rev-parse HEAD),_SHORT_SHA=$(git rev-parse --short HEAD)
Why not primary:
  • Requires Cloud Build triggers to be configured
  • More complex setup
  • Not the method actually used in practice

Option 3: Manual Image Build + Deploy (Fallback)

Status: Only if --source . is unavailable Command:
# Build image manually (requires Docker)
FULL_SHA=$(git rev-parse HEAD)
SHORT_SHA=$(git rev-parse --short HEAD)

docker build -f api/Dockerfile -t gcr.io/payroll-bi-gauntlet/payroll-pipeline-cbs:$SHORT_SHA .
docker push gcr.io/payroll-bi-gauntlet/payroll-pipeline-cbs:$SHORT_SHA

# Deploy
gcloud run deploy payroll-pipeline-cbs \
  --image=gcr.io/payroll-bi-gauntlet/payroll-pipeline-cbs:$SHORT_SHA \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --service-account=sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --set-secrets=JWT_SECRET_KEY=jwt-secret:latest \
  --set-env-vars="GCP_PROJECT_ID=payroll-bi-gauntlet,CORS_ORIGINS=https://payroll-pipeline-cbs.vercel.app,GIT_COMMIT_SHA=${FULL_SHA}" \
  --update-labels="git-sha=${SHORT_SHA}"
Why not primary:
  • Requires Docker locally
  • Two-step process (build then deploy)
  • Slower than --source .

Critical Requirements

1. MANDATORY: GIT_COMMIT_SHA for Audit-Grade Provenance

Every deployment MUST include:
--set-env-vars="...GIT_COMMIT_SHA=${FULL_SHA}"
Why: Required for audit-grade provenance. Post-deploy verification checks this. Verification:
# Get latest revision
REVISION=$(gcloud run services describe payroll-pipeline-cbs \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --format="value(status.latestReadyRevisionName)")

# Verify GIT_COMMIT_SHA matches HEAD
DEPLOYED_SHA=$(gcloud run revisions describe "$REVISION" \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --format="yaml(spec.containers[0].env)" | awk '
    $1=="-"{next}
    $1=="name:" && $2=="GIT_COMMIT_SHA"{found=1}
    found && $1=="value:"{print $2; exit}
  ')

HEAD_SHA=$(git rev-parse HEAD)

if [ "$DEPLOYED_SHA" != "$HEAD_SHA" ]; then
  echo "❌ ERROR: Provenance mismatch"
  exit 1
fi

echo "✅ Provenance verified: GIT_COMMIT_SHA=$DEPLOYED_SHA"

2. Required Environment Variables

  • GCP_PROJECT_ID=payroll-bi-gauntlet (must be exact, no spaces)
  • CORS_ORIGINS=https://payroll-pipeline-cbs.vercel.app
  • GIT_COMMIT_SHA=${FULL_SHA} (mandatory)

3. Required Secrets

  • JWT_SECRET_KEY=jwt-secret:latest

4. Service Account

  • sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com

Post-Deploy Verification

1. Verify Revision Changed

gcloud run services describe payroll-pipeline-cbs \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --format="value(status.latestReadyRevisionName,status.url)"
Expected:
  • latestReadyRevisionName should change after deploy
  • status.url should be https://payroll-pipeline-cbs-evndxpcirq-uc.a.run.app

2. Verify Provenance (MANDATORY)

Run the provenance verification script above.

3. Health Check

# Check router status
curl https://payroll-pipeline-cbs-evndxpcirq-uc.a.run.app/debug/router-status | jq

# Check routes
curl https://payroll-pipeline-cbs-evndxpcirq-uc.a.run.app/debug/routes | jq '.routes[] | select(.path | contains("onboarding"))'

# Verify OpenAPI schema
curl https://payroll-pipeline-cbs-evndxpcirq-uc.a.run.app/openapi.json | jq '.paths | keys | length'

Comparison: Hardened vs Phase 8E Script

AspectHardened MethodPhase 8E Script
Core Method--source .--source . ✅ Same
GIT_COMMIT_SHA✅ Included✅ Included
Resource Limits❌ Not specified✅ Memory, CPU, instances
Service Account✅ Correct✅ Correct
Secrets✅ Correct✅ Correct
Env Vars✅ Correct✅ Correct
Conclusion: Phase 8E script uses the same hardened method with additional resource limits. Both are valid, Phase 8E version is more explicit.

Recommendation for Phase 8E Deployment

Use the Phase 8E script version (with resource limits) because:
  1. ✅ Uses the same hardened --source . method
  2. ✅ Includes all mandatory flags (GIT_COMMIT_SHA, secrets, env vars)
  3. ✅ Adds explicit resource limits (better for production)
  4. ✅ Includes service URL verification
  5. ✅ Captures previous revision for rollback
Command:
bash scripts/phase8e_deploy_backend.sh
Or manually:
FULL_SHA=$(git rev-parse HEAD)
SHORT_SHA=$(git rev-parse --short HEAD)

gcloud run deploy payroll-pipeline-cbs \
  --source . \
  --region=us-central1 \
  --project=payroll-bi-gauntlet \
  --service-account=sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --set-secrets=JWT_SECRET_KEY=jwt-secret:latest \
  --set-env-vars="GCP_PROJECT_ID=payroll-bi-gauntlet,CORS_ORIGINS=https://payroll-pipeline-cbs.vercel.app,GIT_COMMIT_SHA=${FULL_SHA}" \
  --update-labels="git-sha=${SHORT_SHA}" \
  --memory=1Gi \
  --cpu=1 \
  --max-instances=10 \
  --min-instances=0 \
  --timeout=300 \
  --concurrency=80

Last Updated: 2026-01-18 Source: docs/DEPLOYMENT.md (lines 99-118) + scripts/phase8e_deploy_backend.sh