Skip to content

fix(gcp): unblock self-hosted deploys (Cloud SQL, pgvector, non-Marketplace)#1

Merged
guyko merged 2 commits into
mainfrom
fix/self-host-experiment-gaps
Jul 9, 2026
Merged

fix(gcp): unblock self-hosted deploys (Cloud SQL, pgvector, non-Marketplace)#1
guyko merged 2 commits into
mainfrom
fix/self-host-experiment-gaps

Conversation

@guyko

@guyko guyko commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Five gaps found while running the canonical self-host path end-to-end in GCP Cloud Shell — every one blocks terraform apply for a real self-host customer (pgvector vector store, Cloud SQL Postgres, no Marketplace transaction).

Gaps fixed

# Severity Gap
1 P0 terraform required_version pinned to >= 1.6 — Cloud Shell ships 1.5.7, breaks zero-install path. Lowered to >= 1.5.
2 P0 Cloud Run service did not attach Cloud SQL instances. Added cloud_sql_instance_connection_names var + conditional volumes/volume_mounts blocks. Customers can now use host=/cloudsql/... Unix socket URLs.
3 P1 EMBEDDING_PROVIDER / EMBEDDING_MODEL not exposed — app silently defaulted to Upstash for embeddings even when vector_provider=pgvector. Added both as plain env vars (default local + BGESmallENV15).
4 P0 Secret Manager rejects empty secret_data but 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.
5 P0 gcp_service_account_json + gcp_marketplace_* were required vars without defaults — non-Marketplace customers (most self-hosters) couldn't even terraform plan. Made all three optional.

Docs

  • README: added zero-install Cloud Shell call-out, prereq note about terraform >= 1.5, full Quick start with Cloud SQL + Memorystore provisioning using correct gcloud invocations (beta sql --labels, --edition=ENTERPRISE for shared-core tiers on Postgres 16, pgvector via CREATE EXTENSION only, cloud-sql-proxy for 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 live terraform apply succeeds and Cloud Run answers /healthz.

Test plan

  • terraform init succeeds with terraform 1.5.7 (Cloud Shell)
  • terraform apply succeeds for pgvector + Cloud SQL + non-Marketplace
  • Cloud Run boots cleanly with Cloud SQL Unix socket attached
  • /healthz returns 200
  • Dashboard loads
  • Proxy round-trip works with real LLM + SG keys
  • After validation, mark ready for review

Second 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:

Issue Severity Fix
#2 A P0 modules/cloud-run/variables.tf — 8 single-line 2-argument variable blocks are invalid HCL and broke terraform init. Rewritten multi-line.
#2 B P0 modules/secrets/main.tffor_each on a sensitive map value is rejected. Now iterates a nonsensitive keyset and looks up values per-resource (values stay sensitive).
#2 C P0 main.tf — added cloudresourcemanager.googleapis.com to the enabled APIs (chicken-and-egg on refresh).
#3 P0 Cloud Run SA now gets roles/cloudsql.client (conditional on cloud_sql_instance_connection_names), so the Cloud SQL socket connects on first boot.
#4 P0 Hardcoded allUsers invoker binding replaced with allow_public_access (default true) + invoker_members. Default behavior unchanged; DRS-restricted orgs can now apply cleanly.
#5 P1 New pull-through Artifact Registry remote repo mirroring ghcr.io (enable_ghcr_mirror, default true) + ghcr_mirror_url output. Removes the manual Cloud Build mirror step and the :latest staleness 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_PASSWORD on Cloud Run, both optional.

Closes #2
Closes #3
Closes #4
Closes #5

Test plan (updated)

  • terraform init + validate clean (HCL fixes)
  • terraform apply 100% green on a fresh project: pgvector + Cloud SQL + non-Marketplace, no manual gcloud recovery
  • Cloud SQL socket connects on first boot (cloudsql.client granted by terraform)
  • Image pulled via the ghcr pull-through mirror (no Cloud Build mirror step)
  • admin_email + admin_password → working dashboard login
  • DRS path: allow_public_access=false + invoker_members produces an invocable service

…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
@guyko
guyko marked this pull request as ready for review July 9, 2026 14:19
@guyko
guyko merged commit ff3e70a into main Jul 9, 2026
@guyko
guyko deleted the fix/self-host-experiment-gaps branch July 9, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment