This document describes the Terraform infrastructure steps for deploying the Car Pulse Tracker API to GCP Cloud Run.
Ensure the following containers are running:
docker container ls
# Expected:
# con-bnc-cpt-conf-validator
# con-bnc-cpt-tf-runner
# con-bnc-cpt-tpl-gen
If not running, start them:
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
make do-setup-bnc-cpt-all
All commands require:
- ENV - Environment: inf, dev, tst, prd, or all
- STEP - The terraform step name
- ORG - Organization code (default: bnc)
- APP - Application code (default: cpt)
| Step | Name | Description |
|---|---|---|
| 000 | gcp-remote-bucket | Terraform state bucket |
| 001 | enable-gcp-services | Enable required GCP APIs |
| 003 | gcp-iam-users | IAM user assignments |
| 007 | dns | DNS zone setup and records |
| 015 | gcp-buckets-for-sites-static | GCS buckets + HTTPS LB + CDN + SSL for WUI |
| 025 | gcp-artifact-registry | Docker image repository with cleanup policies |
| 027 | gcp-memorystore-redis | Memorystore Redis for session/cache |
| 028 | gcp-vpc-connector | VPC connector for Cloud Run → Redis |
| 029 | create-gcp-secrets | Secret Manager secrets |
| 030 | gcp-cloud-run | Cloud Run API deployment |
| 120 | github-general-secrets | GitHub Actions secrets for CI/CD |
| 140 | gcp-cloud-monitor | Cloud Monitoring: log-based metrics, alert policies, health dashboard |
Enable required GCP APIs including artifactregistry.googleapis.com, run.googleapis.com, and secretmanager.googleapis.com.
for env in inf dev tst prd all; do ENV=$env STEP=001-enable-gcp-services ORG=bnc APP=cpt make do-generate-config-for-step; done
ENV=dev STEP=001-enable-gcp-services ORG=bnc APP=cpt make do-provision
ENV=dev STEP=001-enable-gcp-services ORG=bnc APP=cpt make do-deprovision
ENV=dev STEP=001-enable-gcp-services ORG=bnc APP=cpt make do-tf-plan
Creates Docker image repository in GCP Artifact Registry for storing API container images.
google_artifact_registry_repository - Docker repositoryfor env in inf dev tst prd all; do ENV=$env STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-generate-config-for-step; done
ENV=dev STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-provision
ENV=dev STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-deprovision
ENV=dev STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-tf-plan
After provisioning, the Docker image path will be:
europe-north1-docker.pkg.dev/bnc-cpt-{env}/bnc-cpt-api/cpt-api:latest
After provisioning the registry, push your Docker image:
# Authenticate Docker with Artifact Registry
gcloud auth configure-docker europe-north1-docker.pkg.dev
# Build and push image
docker build -t europe-north1-docker.pkg.dev/bnc-cpt-dev/bnc-cpt-api/cpt-api:latest \
-f /opt/bnc/bnc-cpt/bnc-cpt-utl/src/docker/cpt-api/Dockerfile.x86_64 \
/opt/bnc/bnc-cpt
docker push europe-north1-docker.pkg.dev/bnc-cpt-dev/bnc-cpt-api/cpt-api:latest
Creates Secret Manager secrets for sensitive configuration values. Note: This step creates the secret containers only - actual values must be added manually.
google_secret_manager_secret - Secret containers for:bnc-cpt-stripe-secret-keybnc-cpt-stripe-webhook-secretbnc-cpt-paypal-client-idbnc-cpt-paypal-client-secretbnc-cpt-jwt-secret-keybnc-cpt-admin-password-hashfor env in inf dev tst prd all; do ENV=$env STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-generate-config-for-step; done
ENV=dev STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-provision
ENV=dev STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-deprovision
ENV=dev STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-tf-plan
After provisioning, add actual secret values using gcloud CLI:
# Stripe Secret Key
echo -n "sk_test_your_stripe_key" | \
gcloud secrets versions add bnc-cpt-stripe-secret-key \
--data-file=- --project=bnc-cpt-dev
# Stripe Webhook Secret
echo -n "whsec_your_webhook_secret" | \
gcloud secrets versions add bnc-cpt-stripe-webhook-secret \
--data-file=- --project=bnc-cpt-dev
# PayPal Client ID
echo -n "your_paypal_client_id" | \
gcloud secrets versions add bnc-cpt-paypal-client-id \
--data-file=- --project=bnc-cpt-dev
# PayPal Client Secret
echo -n "your_paypal_client_secret" | \
gcloud secrets versions add bnc-cpt-paypal-client-secret \
--data-file=- --project=bnc-cpt-dev
# JWT Secret Key
echo -n "your_jwt_secret_key_min_32_chars" | \
gcloud secrets versions add bnc-cpt-jwt-secret-key \
--data-file=- --project=bnc-cpt-dev
# Admin Password Hash (bcrypt)
echo -n '$2b$12$your_bcrypt_hash_here' | \
gcloud secrets versions add bnc-cpt-admin-password-hash \
--data-file=- --project=bnc-cpt-dev
Or use GCP Console: 1. Go to https://console.cloud.google.com/security/secret-manager?project=bnc-cpt-dev 2. Click on each secret 3. Click "New Version" 4. Enter the secret value
Deploys the FastAPI backend to GCP Cloud Run.
google_service_account - Dedicated service account for Cloud Rungoogle_cloud_run_v2_service - Cloud Run servicegoogle_cloud_run_v2_service_iam_member - Public access (if enabled)| Setting | dev | tst | prd |
|---|---|---|---|
| Memory | 1Gi | 1Gi | 2Gi |
| CPU | 1 | 1 | 2 |
| Min Instances | 0 | 0 | 1 |
| Max Instances | 10 | 10 | 100 |
| DEBUG | True | False | False |
for env in inf dev tst prd all; do ENV=$env STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-generate-config-for-step; done
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-provision
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-deprovision
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-tf-plan
After successful deployment:
- Service URL: https://bnc-cpt-api-{env}-xxxxxxxxxx-lz.a.run.app
- Health Check: https://<service-url>/health
# Get the service URL
gcloud run services describe bnc-cpt-api-dev \
--region=europe-north1 \
--project=bnc-cpt-dev \
--format='value(status.url)'
# Test health endpoint
curl https://<service-url>/health
Provisions Cloud Monitoring resources: log-based metrics, alert policies, notification channels, and a health dashboard.
google_logging_metric — 4 log-based metrics:${org}-${app}-${env}-tesla-api-errors — Tesla API failure count${org}-${app}-${env}-payment-errors — Payment processing failure count${org}-${app}-${env}-pdf-errors — PDF generation failure count${org}-${app}-${env}-server-errors-5xx — HTTP 5xx error countgoogle_monitoring_notification_channel — Email notification channelgoogle_monitoring_alert_policy — 4 alert policies:google_monitoring_dashboard — 10-panel health dashboardmonitoring.googleapis.com and cloudtrace.googleapis.com must be enabled (step 001)cloud_run_revisionfor env in dev tst prd; do
ENV=$env STEP=140-gcp-cloud-monitor ORG=bnc APP=cpt make do-generate-config-for-step
done
ENV=dev STEP=140-gcp-cloud-monitor ORG=bnc APP=cpt make do-provision
ENV=dev STEP=140-gcp-cloud-monitor ORG=bnc APP=cpt make do-deprovision
ENV=dev STEP=140-gcp-cloud-monitor ORG=bnc APP=cpt make do-tf-plan
Alert thresholds and notification emails are configured in bnc-cpt-cnf/bnc-cpt/{env}.env.yaml under 140-gcp-cloud-monitor:
140-gcp-cloud-monitor:
sender_email: sys@carpulsetracker.com
recipient_email: yordan.georgiev@csitea.net
gcp_region: "europe-north1"
gcp_zone: "europe-north1-a"
After provisioning, the dashboard is visible in Google Cloud Console:
https://console.cloud.google.com/monitoring/dashboards?project=bnc-cpt-{env}
gcloud alpha monitoring policies list --project=bnc-cpt-dev --format="table(displayName,enabled)"
for step in 001-enable-gcp-services 025-gcp-artifact-registry 029-create-gcp-secrets 030-gcp-cloud-run 140-gcp-cloud-monitor; do
for env in inf dev tst prd all; do
ENV=$env STEP=$step ORG=bnc APP=cpt make do-generate-config-for-step
done
done
Deploy all infrastructure in order for a specific environment:
# Step 1: Enable GCP APIs
ENV=dev STEP=001-enable-gcp-services ORG=bnc APP=cpt make do-provision
# Step 2: Create Artifact Registry
ENV=dev STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-provision
# Step 3: Create Secrets
ENV=dev STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-provision
# Step 4: Add secret values (see 029 section above)
# Step 5: Build and push Docker image (see 025 section above)
# Step 6: Deploy to Cloud Run
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-provision
# Step 7: Provision Monitoring (alerts + dashboard)
ENV=dev STEP=140-gcp-cloud-monitor ORG=bnc APP=cpt make do-provision
Remove all infrastructure:
# Remove Cloud Run
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-deprovision
# Remove Secrets
ENV=dev STEP=029-create-gcp-secrets ORG=bnc APP=cpt make do-deprovision
# Remove Artifact Registry
ENV=dev STEP=025-gcp-artifact-registry ORG=bnc APP=cpt make do-deprovision
# Note: Usually keep 001-enable-gcp-services as other resources may depend on it
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-tf-state-list
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt TARGET="google_cloud_run_v2_service.api" make do-tf-state-show
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt TARGET="google_cloud_run_v2_service.api" make do-tf-replace-target
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt \
RESOURCE_ADDRESS="google_cloud_run_v2_service.api" \
ID="projects/bnc-cpt-dev/locations/europe-north1/services/bnc-cpt-api-dev" \
make do-tf-import
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=bnc-cpt-api-dev" \
--project=bnc-cpt-dev \
--limit=50 \
--format="table(timestamp,textPayload)"
# All environments for a single step
for env in inf dev tst prd all; do ENV=$env STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-generate-config-for-step; done
# All steps for all environments
for step in 001-enable-gcp-services 025-gcp-artifact-registry 029-create-gcp-secrets 030-gcp-cloud-run; do
for env in inf dev tst prd all; do ENV=$env STEP=$step ORG=bnc APP=cpt make do-generate-config-for-step; done
done
# Development
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-provision
# Test
ENV=tst STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-provision
# Production
ENV=prd STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-provision
# Development
ENV=dev STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-deprovision
# Test
ENV=tst STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-deprovision
# Production
ENV=prd STEP=030-gcp-cloud-run ORG=bnc APP=cpt make do-deprovision
The following bash functions provide streamlined deployment workflows from your local machine. All commands run from bnc-cpt-utl/.
| Function | Description |
|---|---|
do_gcp_build_and_push_api_image |
Build Docker image and push to Artifact Registry |
do_gcp_deploy_cloud_run |
Deploy Cloud Run service via Terraform |
do_gcp_delete_cloud_run |
Delete Cloud Run service (with production safety) |
do_gcp_deploy_api_full |
Full deployment: build, push, and deploy |
Build the API Docker image locally and push to GCP Artifact Registry.
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Build and push for dev environment
ENV=dev ./run -a do_gcp_build_and_push_api_image
# Build and push for production
ENV=prd ./run -a do_gcp_build_and_push_api_image
What it does:
1. Authenticates Docker with GCP Artifact Registry
2. Builds the image using --target production (multi-stage Dockerfile)
3. Tags and pushes to europe-north1-docker.pkg.dev/{project}/bnc-cpt-api/cpt-api:latest
Prerequisites:
- GCP service account key at ~/.gcp/.bnc/key-bnc-cpt-{env}.json
- Artifact Registry provisioned (step 025)
- Docker running locally
Deploy Cloud Run service using Terraform. Generates config and runs terraform apply.
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Deploy to dev
ENV=dev ./run -a do_gcp_deploy_cloud_run
# Deploy to test
ENV=tst ./run -a do_gcp_deploy_cloud_run
# Deploy to production
ENV=prd ./run -a do_gcp_deploy_cloud_run
What it does:
1. Generates Terraform config from YAML (do-generate-config-for-step)
2. Runs terraform apply via do-provision
Prerequisites: - Docker image already pushed to Artifact Registry - Secrets provisioned with values (step 029) - Terraform runner container running
Delete Cloud Run service and associated resources.
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Delete dev service
ENV=dev ./run -a do_gcp_delete_cloud_run
# Delete test service
ENV=tst ./run -a do_gcp_delete_cloud_run
# Delete production (requires confirmation)
ENV=prd ./run -a do_gcp_delete_cloud_run
Safety feature: Production environment requires explicit YES confirmation to prevent accidental deletion.
What it does:
1. Generates Terraform config
2. Runs terraform destroy via do-deprovision
Complete API deployment workflow: build, push, and deploy in one command.
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Full deployment to dev
ENV=dev ./run -a do_gcp_deploy_api_full
# Full deployment to production
ENV=prd ./run -a do_gcp_deploy_api_full
What it does:
1. Builds Docker image (do_gcp_build_and_push_api_image)
2. Pushes to Artifact Registry
3. Deploys to Cloud Run (do_gcp_deploy_cloud_run)
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Full deployment to all environments
for env in dev tst prd; do
ENV=$env ./run -a do_gcp_deploy_api_full
done
# Or just deploy (image already pushed)
for env in dev tst prd; do
ENV=$env ./run -a do_gcp_deploy_cloud_run
done
After deployment, verify each environment:
# Get service URLs
for env in dev tst prd; do
url=$(gcloud run services describe bnc-cpt-api-$env \
--region=europe-north1 \
--project=bnc-cpt-$env \
--format='value(status.url)' 2>/dev/null)
echo "$env: $url"
done
# Test health endpoints
curl https://bnc-cpt-api-dev-xxxxxxxxxx-lz.a.run.app/health
curl https://bnc-cpt-api-tst-xxxxxxxxxx-lz.a.run.app/health
curl https://bnc-cpt-api-prd-xxxxxxxxxx-lz.a.run.app/health
Each Cloud Run deployment exposes the following endpoints accessible via browser:
| Endpoint | Description |
|---|---|
/health |
Health check - returns {"status":"healthy","service":"cpt-api"} |
/docs |
Swagger UI - Interactive API documentation |
/redoc |
ReDoc - Alternative API documentation |
/openapi.json |
OpenAPI schema (JSON format) |
Environment URLs:
| Environment | Base URL |
|---|---|
| dev | https://bnc-cpt-api-dev-oa334vnbwq-lz.a.run.app |
| tst | https://bnc-cpt-api-tst-{hash}-lz.a.run.app |
| prd | https://bnc-cpt-api-prd-{hash}-lz.a.run.app |
Quick Links (dev environment):
Get Your Environment's URL:
# Get the base URL for an environment
ENV=dev
gcloud run services describe bnc-cpt-api-$ENV \
--region=europe-north1 \
--project=bnc-cpt-$ENV \
--format='value(status.url)'
# Open in browser (macOS)
open "$(gcloud run services describe bnc-cpt-api-$ENV \
--region=europe-north1 \
--project=bnc-cpt-$ENV \
--format='value(status.url)')/docs"
# Open in browser (Linux)
xdg-open "$(gcloud run services describe bnc-cpt-api-$ENV \
--region=europe-north1 \
--project=bnc-cpt-$ENV \
--format='value(status.url)')/docs"
API Endpoints (require authentication):
| Endpoint | Method | Description |
|---|---|---|
/api/v1/auth/login/json |
POST | JWT authentication |
/api/v1/payment/create-intent |
POST | Create payment intent |
/api/v1/payment/verify |
POST | Verify payment |
/api/v1/payment/receipt/{id}/pdf |
GET | Download receipt PDF |
/api/v1/tesla/oauth/initiate |
POST | Start Tesla OAuth flow |
Cloud Run settings per environment (from bnc-cpt-cnf/bnc-cpt/*.env.yaml):
| Setting | inf | dev | tst | prd |
|---|---|---|---|---|
| Memory | 512Mi | 1Gi | 1Gi | 2Gi |
| CPU | 1 | 1 | 1 | 2 |
| Min Instances | 0 | 0 | 0 | 1 |
| Max Instances | 5 | 10 | 10 | 100 |
| DEBUG | True | True | False | False |
| PAYPAL_MODE | sandbox | sandbox | sandbox | live |
To modify Cloud Run settings:
Edit the YAML config:
yaml
# bnc-cpt-cnf/bnc-cpt/dev.env.yaml
steps:
030-gcp-cloud-run:
cloud_run_memory: "2Gi"
cloud_run_cpu: "2"
Redeploy:
bash
ENV=dev ./run -a do_gcp_deploy_cloud_run
The API is deployed automatically via GitHub Actions when code is pushed to the main branch. The workflow is defined in bnc-cpt-api/.github/workflows/cd.yaml.
# Trigger deployment to dev
gh workflow run cd.yaml -f environment=dev --repo csitea/bnc-cpt-api
# Trigger deployment to all environments sequentially
for env in inf dev tst prd; do
gh workflow run cd.yaml -f environment=$env --repo csitea/bnc-cpt-api
sleep 30 # Wait between deployments
done
# Check workflow runs
gh run list --workflow=cd.yaml --repo csitea/bnc-cpt-api
# Watch latest run
gh run watch --repo csitea/bnc-cpt-api
# View run logs
gh run view --log --repo csitea/bnc-cpt-api
The CD workflow:
- Builds Docker image and pushes to Artifact Registry
- Deploys to Cloud Run with all secrets from config
- Dynamically reads secrets from bnc-cpt-cnf/{env}.env.json
- Handles custom domain mapping (non-blocking if domain not verified)
Secrets are read dynamically from config file:
# Secrets are defined in bnc-cpt-cnf/bnc-cpt/{env}.env.yaml
# Under: steps.030-gcp-cloud-run.secret_environment_variables
# After config change, regenerate JSON:
make do-generate-env-json ENV=dev
# Then trigger redeployment
gh workflow run cd.yaml -f environment=dev --repo csitea/bnc-cpt-api
All environments have custom domain mappings via Cloud Run:
| Environment | API Base URL | Swagger UI |
|---|---|---|
| inf | https://inf.api.carpulsetracker.com | https://inf.api.carpulsetracker.com/docs |
| dev | https://dev.api.carpulsetracker.com | https://dev.api.carpulsetracker.com/docs |
| tst | https://tst.api.carpulsetracker.com | https://tst.api.carpulsetracker.com/docs |
| prd | https://api.carpulsetracker.com | https://api.carpulsetracker.com/docs |
| prd (alias) | https://prd.api.carpulsetracker.com | https://prd.api.carpulsetracker.com/docs |
# Quick health check for all environments
for env in inf dev tst prd; do
if [ "$env" = "prd" ]; then
url="https://api.carpulsetracker.com"
else
url="https://$env.api.carpulsetracker.com"
fi
echo -n "$env: "
curl -s "$url/health" | jq -r '.status'
done
# Using uniform URL pattern (with prd.api alias)
for env in inf dev tst prd; do
echo -n "$env: "
curl -s "https://$env.api.carpulsetracker.com/health" | jq -r '.status'
done
for env in inf dev tst prd; do
echo "=== $env ==="
curl -s "https://$env.api.carpulsetracker.com/api" | jq '.'
done
The Vue frontend is deployed to GCS buckets and served via Cloud CDN Load Balancer.
| Environment | URL | GCS Bucket | CDN URL Map |
|---|---|---|---|
| inf | https://inf.carpulsetracker.com | gs://bnc-cpt-inf-site-static |
bnc-cpt-inf-wui-https-url-map |
| dev | https://dev.carpulsetracker.com | gs://bnc-cpt-dev-site-static |
bnc-cpt-dev-wui-https-url-map |
| tst | https://tst.carpulsetracker.com | gs://bnc-cpt-tst-site-static |
bnc-cpt-tst-wui-https-url-map |
| prd | https://carpulsetracker.com | gs://bnc-cpt-prd-site-static |
bnc-cpt-prd-wui-https-url-map |
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Deploy to single environment
ENV=dev ./run -a do_gcp_deploy_wui
# Deploy to all environments
for env in inf dev tst prd; do
ENV=$env ./run -a do_gcp_deploy_wui
done
What the deploy action does (5 steps):
1. Builds Vue app inside wui container (npm run build)
2. Verifies dist directory was created
3. Syncs dist to GCS bucket (gsutil rsync)
4. Invalidates CDN cache (gcloud compute url-maps invalidate-cdn-cache)
5. Verifies deployment (HTTP 200 check on index.html)
Prerequisites:
- WUI container running: make do-setup-wui
- GCP keys at ~/.gcp/.bnc/key-bnc-cpt-{env}.json
- GCS buckets provisioned (Terraform step 015-gcp-buckets-for-sites-static)
The deploy action automatically invalidates CDN cache. To manually invalidate:
# Single environment
gcloud auth activate-service-account --key-file=$HOME/.gcp/.bnc/key-bnc-cpt-dev.json
gcloud compute url-maps invalidate-cdn-cache bnc-cpt-dev-wui-https-url-map \
--path="/*" --project=bnc-cpt-dev
# All environments
for env in inf dev tst prd; do
gcloud auth activate-service-account --key-file=$HOME/.gcp/.bnc/key-bnc-cpt-${env}.json 2>/dev/null
gcloud compute url-maps invalidate-cdn-cache bnc-cpt-${env}-wui-https-url-map \
--path="/*" --project=bnc-cpt-${env}
done
The index.html file has Cache-Control: no-cache, no-store, must-revalidate to ensure users always get the latest version. Asset files (JS, CSS, images) use content hashing in filenames and can be cached.
To set cache headers manually:
gsutil setmeta -h "Cache-Control:no-cache, no-store, must-revalidate" \
gs://bnc-cpt-dev-site-static/index.html
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# By issue ID
ISSUE_ID=BNC-61 ./run -a do_linear_fetch_issue
# By URL
URL="https://linear.app/boxinom-csitea/issue/BNC-61/..." ./run -a do_linear_fetch_issue
# Close as Done (default)
ISSUE_ID=BNC-61 ./run -a do_linear_close_issue
# Close with different state
ISSUE_ID=BNC-61 STATE=Cancelled ./run -a do_linear_close_issue
# Close with comment
ISSUE_ID=BNC-61 COMMENT="Deployed to all envs" ./run -a do_linear_close_issue
Prerequisites: Linear API key at ~/.linear/api_key
See the full documentation in bnc-cpt-inf/doc/bnc-cpt.ISG.md under "GCP SECRETS SYNC FROM GOOGLE SHEET".
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
# Sync secrets for single environment
make do-gcp-sync-secrets ENV=dev
# Sync all environments
for env in inf dev tst prd; do
echo "=== Syncing secrets for $env ==="
make do-gcp-sync-secrets ENV=$env
done
# Dry run (preview only)
make do-gcp-sync-secrets ENV=dev DRY_RUN=1
Note: Empty values in the Google Sheet are automatically replaced with "n/a" to ensure Cloud Run can mount all configured secrets