fix(gcp): unblock self-hosted deploys (Cloud SQL, pgvector, non-Marketplace)#1
Merged
Conversation
…tplace)
Five gaps found while running the canonical self-host path end-to-end in
GCP Cloud Shell — every one of them blocks `terraform apply` for a real
self-host customer (pgvector vector store, Cloud SQL Postgres, no
Marketplace transaction). All fixed in this PR plus README + tfvars.example
updates so customers don't have to figure out the surrounding gcloud
gotchas themselves.
Gap 1: terraform required_version pinned to >= 1.6
Cloud Shell ships terraform 1.5.7, so the canonical zero-install path
errors at `terraform init`. Lowered the pin to >= 1.5.
Gap 2: Cloud SQL not attached to the Cloud Run service
No `volumes { cloud_sql_instance {} }` block and no volume_mounts, so
Cloud SQL customers had to use public IP + authorized networks (terrible
UX with Cloud Run's rotating egress IPs). Added a new
`cloud_sql_instance_connection_names` variable that, when set, attaches
the instance and mounts the Unix socket at `/cloudsql/...`. Customers
can then use `host=/cloudsql/PROJECT:REGION:INSTANCE` in database_url.
Gap 3: EMBEDDING_PROVIDER and EMBEDDING_MODEL not exposed
App's `env.ts` defaults EMBEDDING_PROVIDER to `upstash`, so self-host
customers who pick `vector_provider=pgvector` still ended up calling
Upstash for embeddings (defeats the purpose of self-hosting). Added
`embedding_provider` (default `local`) and `embedding_model` (default
`BGESmallENV15`) as plain env vars.
Gap 4: Secret Manager rejects empty `secret_data`
But `terraform.tfvars.example` told pgvector customers to set
`upstash_vector_url = ""` and `upstash_vector_token = ""`. Apply failed
at the secrets module. Fixed by filtering empty values in the secrets
module's for_each, and emitting the cloud-run env blocks for those
optional secrets only when the secret was actually created (via a new
`local.active_optional_envs` lookup).
Gap 5: GCP Marketplace vars required without defaults
`gcp_service_account_json`, `gcp_marketplace_account_id`, and
`gcp_marketplace_service_name` had no defaults, so non-Marketplace
self-host customers (the majority) couldn't even pass `terraform plan`.
Combined with gap 4, completely blocked. Made all three optional
(default ""); they flow through the same active-secret filter.
Docs:
- README: added zero-install Cloud Shell call-out, prereq note about
terraform >= 1.5, full Quick start with Cloud SQL + Memorystore
provisioning that uses the correct gcloud invocations
(`gcloud beta sql ... --labels`, `--edition=ENTERPRISE` for shared-core
tiers on Postgres 16, pgvector via `CREATE EXTENSION` not a database
flag, `cloud-sql-proxy` for PGPASSWORD-friendly scripted setup).
- terraform.tfvars.example: documents the three Postgres options
(Cloud SQL Unix socket, Neon, Cloud SQL public IP), explicit empty
values for the optional secrets, embedding provider section.
Validated end-to-end against a real GCP project (semanticguard-prod)
via Cloud Shell. Live findings log:
https://github.com/KB-AI-LLC/sg-ai/blob/main/GCP_EXPERIMENT_GAPS.md
…dmin seed Second wave of self-host fixes found running PR #1 end-to-end on a fresh project (KB-AI-LLC/sg-ai#790). #2 apply-chain gaps: - modules/cloud-run/variables.tf: rewrite 8 single-line 2-argument variable blocks as multi-line (invalid HCL; broke `terraform init`). - modules/secrets/main.tf: for_each on a nonsensitive keyset; look up values per-resource (sensitive map values can't drive for_each). - main.tf: add cloudresourcemanager.googleapis.com to the enabled APIs (chicken-and-egg on refresh). #3 cloud SQL: grant roles/cloudsql.client to the Cloud Run SA, conditional on cloud_sql_instance_connection_names being set (Neon-only deploys skip it). #4 DRS-safe access: replace the hardcoded allUsers invoker binding with allow_public_access (bool, default true) + invoker_members (list). Default behavior unchanged; DRS orgs set allow_public_access=false + principals. #5 ghcr pull-through: add a REMOTE_REPOSITORY Artifact Registry repo mirroring ghcr.io (enable_ghcr_mirror, default true) + ghcr_mirror_url output, so Cloud Run pulls the self-hosted image with no manual Cloud Build mirror step. Also wires the terraform side of the admin bootstrap (part of KB-AI-LLC/sg-ai#972): admin_email (plain env) + admin_password (Secret Manager) -> ADMIN_EMAIL / ADMIN_PASSWORD on Cloud Run, both optional. Closes #2 Closes #3 Closes #4 Closes #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five gaps found while running the canonical self-host path end-to-end in GCP Cloud Shell — every one blocks
terraform applyfor a real self-host customer (pgvector vector store, Cloud SQL Postgres, no Marketplace transaction).Gaps fixed
required_versionpinned to >= 1.6 — Cloud Shell ships 1.5.7, breaks zero-install path. Lowered to >= 1.5.cloud_sql_instance_connection_namesvar + conditionalvolumes/volume_mountsblocks. Customers can now usehost=/cloudsql/...Unix socket URLs.EMBEDDING_PROVIDER/EMBEDDING_MODELnot exposed — app silently defaulted to Upstash for embeddings even whenvector_provider=pgvector. Added both as plain env vars (defaultlocal+BGESmallENV15).secret_databut the example told pgvector customers to set Upstash Vector vars to"". Apply failed. Secrets module now filters empty values; cloud-run module emits optional-secret env blocks only when the secret was created.gcp_service_account_json+gcp_marketplace_*were required vars without defaults — non-Marketplace customers (most self-hosters) couldn't eventerraform plan. Made all three optional.Docs
beta sql --labels,--edition=ENTERPRISEfor shared-core tiers on Postgres 16, pgvector viaCREATE EXTENSIONonly,cloud-sql-proxyfor PGPASSWORD-friendly scripted setup).terraform.tfvars.example: documents three Postgres options (Cloud SQL Unix socket, Neon, Cloud SQL public IP), explicit empty values for optional secrets, embedding provider section.Validation
Branch is being validated end-to-end right now via Cloud Shell against
semanticguard-prod. Draft until the liveterraform applysucceeds and Cloud Run answers/healthz.Test plan
terraform initsucceeds with terraform 1.5.7 (Cloud Shell)terraform applysucceeds for pgvector + Cloud SQL + non-Marketplace/healthzreturns 200Second wave (2026-07-09) — new gaps from the fresh-project dry run
Running this branch end-to-end on a clean project surfaced five more issues, all fixed in
8da33e2:modules/cloud-run/variables.tf— 8 single-line 2-argumentvariableblocks are invalid HCL and broketerraform init. Rewritten multi-line.modules/secrets/main.tf—for_eachon a sensitive map value is rejected. Now iterates anonsensitivekeyset and looks up values per-resource (values stay sensitive).main.tf— addedcloudresourcemanager.googleapis.comto the enabled APIs (chicken-and-egg on refresh).roles/cloudsql.client(conditional oncloud_sql_instance_connection_names), so the Cloud SQL socket connects on first boot.allUsersinvoker binding replaced withallow_public_access(defaulttrue) +invoker_members. Default behavior unchanged; DRS-restricted orgs can now apply cleanly.ghcr.io(enable_ghcr_mirror, defaulttrue) +ghcr_mirror_urloutput. Removes the manual Cloud Build mirror step and the:lateststaleness class of bug.Also wires the terraform side of the admin bootstrap (part of KB-AI-LLC/sg-ai#972):
admin_email(plain env) +admin_password(Secret Manager) →ADMIN_EMAIL/ADMIN_PASSWORDon Cloud Run, both optional.Closes #2
Closes #3
Closes #4
Closes #5
Test plan (updated)
terraform init+validateclean (HCL fixes)terraform apply100% green on a fresh project: pgvector + Cloud SQL + non-Marketplace, no manual gcloud recoveryadmin_email+admin_password→ working dashboard loginallow_public_access=false+invoker_membersproduces an invocable service