GCP Environment Reference #
Every project ID, cluster name, context, hostname, and endpoint. Start here, then follow a task link.
| I want to… | Go to |
|---|---|
| Set up my laptop | gcloud CLI setup |
| Read logs, find a pod | GKE diagnostics, debug backend logs |
| Look at a dashboard | Grafana (GCP) |
| Change infrastructure | Terraform on GCP |
| Change an env var or a secret | env vars, secrets |
Migration status (2026-08-11). Staging runs fully on GCP. Production runs on AWS ECS + Aurora. The production GCP stack is up and serves a view-only canary at
app-gcp.funnelstory.ai; its Cloud SQL instance is still a demoted replica of Aurora. Every production GCP row below is real but not yet customer-facing.
Who can do what #
Access is tiered on purpose. Database credentials and secret payloads stay with Vivek and Preetam. Everyone else gets read-only.
| Tier | Who | Can | Cannot |
|---|---|---|---|
| Platform admin | Vivek, Preetam | everything, including DB logins and secret values | — |
| Developer (read-only) | everyone else, on request | read pods, pod logs, ConfigMaps, Cloud SQL and Secret Manager metadata, Grafana | read secret values, log in to a database, exec into a pod, port-forward, restart a workload |
The developer tier is these four roles.
Ask in #back-end to be added:
roles/viewer # project-wide read
roles/gkehub.gatewayReader # read-only kubectl through Connect Gateway
roles/gkehub.viewer # list memberships, run get-credentials
roles/container.viewer # in-cluster RBAC read
roles/viewer deliberately excludes secretmanager.versions.access, cloudsql.instances.login, container.secrets.*, container.pods.exec, and container.pods.portForward.
That is what keeps secrets and the database out of reach while still allowing real debugging.
Developers also hold roles/aiplatform.user for Vertex AI, and roles/browser for org and folder traversal.
Granted on both projects as of 2026-08-11: Pulkit, Mamta, Nitin, Avadhut.
Members are Terraform-owned in gcp/envs/<env>/terraform.tfvars under developer_members.
Add someone by extending that list and applying, so the next plan keeps the binding instead of proposing to destroy it.
Two rules follow from this and are worth stating plainly:
- Logs → Grafana. Backend app logs live in the non-default
backend-app-logsbucket, and reading a custom bucket view needsroles/logging.viewAccessoron top ofroles/viewer. Grafana holds that grant through its own service account, so it needs no extra IAM for you. - Deploys → an
opsrepo Helm PR. Nobody deploys from a laptop. Config and image changes go throughk8s/deploy/<env>/values.yamland the deploy workflow — see update env vars.
Projects and regions #
| Staging | Production | |
|---|---|---|
| GCP project ID | funnelstory-infra-staging |
funnelstory-infra-production |
| Project number | see command below | 905900603049 |
| Region | us-west1 |
us-west1 |
| Zone (bastion) | us-west1-a |
us-west1-a |
gcloud projects describe funnelstory-infra-staging --format='value(projectNumber)'
GKE #
| Staging | Production | |
|---|---|---|
| Cluster | funnelstory-staging |
funnelstory-production |
| Mode | Autopilot, private control plane | Autopilot, private control plane |
| Release channel | REGULAR |
STABLE |
| Fleet membership | funnelstory-staging |
funnelstory-production |
| kubectl context | connectgateway_funnelstory-infra-staging_global_funnelstory-staging |
connectgateway_funnelstory-infra-production_global_funnelstory-production |
| App namespace | fs-apps |
fs-apps |
| Grafana namespace | observability |
observability |
| App hostname | app.staging.funnelstory.ai |
app-gcp.funnelstory.ai (view-only canary) |
The control plane has no public endpoint.
All kubectl access goes through GKE Connect Gateway — see gcloud setup.
Cloud SQL (PostgreSQL 15) #
Endpoints are listed so you can read dashboards and Terraform. Connecting needs a database role, which is admin-only — see psql to Cloud SQL.
| Staging | Production | |
|---|---|---|
| App instance | funnelstory-staging |
funnelstory-production |
| Writer private IP | 172.21.32.7 |
172.20.32.5 |
| Read replica instance | funnelstory-staging-replica |
not created yet |
| Read replica private IP | 172.21.32.13 |
— |
| Database | funnelstory |
funnelstory |
| Edition / tier | Enterprise, db-custom-2-8192 |
Enterprise Plus, db-c4a-highmem-4 |
| Automated backups | off | off — re-enable after promote |
| PITR | off | off — re-enable after promote |
| Micro instance | funnelstory-micro-staging (172.21.32.9, db sample_db) |
funnelstory-micro-production |
| Grafana metadata instance | grafana-staging (db grafana) |
grafana-production (db grafana) |
The production read replica is created at cutover. A DMS-managed destination cannot have replicas until it is promoted.
Private IPs can change. Re-check before you trust one:
gcloud sql instances describe funnelstory-staging \
--project=funnelstory-infra-staging --format='value(ipAddresses[0].ipAddress)'
Network ranges #
Every range is private. You reach them over Cloudflare WARP.
| Range | Staging | Production |
|---|---|---|
| Primary subnet (nodes, internal LBs) | 172.21.0.0/20 |
172.20.0.0/20 |
| Cloud SQL private services access | 172.21.32.0/20 |
172.20.32.0/20 |
| GKE private control plane endpoint | 172.21.48.0/28 |
172.20.48.0/28 |
| GKE pods | 10.8.0.0/16 |
10.16.0.0/16 |
| GKE Services | 10.9.0.0/20 |
10.17.0.0/20 |
VPC names are funnelstory-staging and funnelstory-production.
The CIDRs avoid AWS production (172.30.0.0/16) and AWS staging (172.31.0.0/16), because an HA VPN joins them during the migration.
Monitoring links #
| Staging | Production | |
|---|---|---|
| Grafana | grafana-staging.funnelstory.io | grafana-production.funnelstory.io |
| PgHero | gone — the Aurora cluster it read was deleted 2026-07-21 | pghero-primary-production.funnelstory.io (AWS Aurora) |
Both Grafana instances sit behind Cloudflare Access, which is the only gate — no GCP IAM needed. See Grafana (GCP) and PgHero.
Terraform #
| Staging | Production | |
|---|---|---|
| Directory | gcp/envs/staging |
gcp/envs/production |
| State bucket | funnelstory-tf-state-staging |
funnelstory-tf-state-production |
| State prefix | envs/staging |
envs/production |
See Terraform on GCP.
Other resources #
| Staging | Production | |
|---|---|---|
| App GCS bucket | fsio-staging-funnelstory |
fsio-production-funnelstory |
| Tunnel keys bucket | fs-tunnel-keys-staging |
fs-tunnel-keys-production |
| Cloudflare tunnel name | Staging GCP |
Production GCP |
| Cloudflare account | one account for both — see cloudflare/envs/*/terraform.tfvars |
same account |
Container images live in the staging project for both environments:
us-west1-docker.pkg.dev/funnelstory-infra-staging/apps/{back-end,front-end,highcharts-export}
Production GKE pulls from the staging Artifact Registry. Zero-copy promotion means production runs the same bytes staging validated. The production node service account holds a repository-scoped reader grant.
Deploys #
| Staging | Production | |
|---|---|---|
| Trigger | automatic — push to main, or repository_dispatch from the app repos |
manual workflow_dispatch only |
| Workflow | .github/workflows/helm-deploy.yml |
.github/workflows/helm-deploy-production.yml |
| Approval gate | none | production GitHub Environment reviewers |
All task guides #
Read-only, any developer:
- gcloud CLI setup — install, authenticate, switch contexts
- GKE diagnostics — find pods, read why one restarted
- Debug backend logs —
kubectl logs, Cloud Logging, BigQuery - Grafana (GCP) — dashboards and ad hoc SQL
Admin only:
- Terraform on GCP — plan and apply infrastructure
- psql to Cloud SQL — direct database access
- Update secrets — Secret Manager
- Update env vars — needs a merged PR, deploy is gated
- Backup and restore — clone and recover data
- Get pprof dumps — needs
port-forward