Live rollout checkpoint
As of 31 July 2026, the two production substrates have completed every setup stage, and neon-law-stg is a created but
unprovisioned project:
- Both production GKE Autopilot clusters are
RUNNINGinus-west4, and no deployment reads Postgres: ENG-22 moved the store to SurrealDB,/healthpings SurrealDB, andops gcp setupprovisions no Cloud SQL instance, so a deleted one is not recreated on the next run. An operator must export the two legacy production Postgres 15 instances to each deployment's own exports bucket and then delete them. Nothing has ever archived those instances — the nightly archive lane covers the SurrealDB tables only — so the export is what makes the deletion reversible, and it is not optional. neon-law-stgandghcrexist in theneonlaw.comorganization and are linked to billing, but neither has been through its provisioner yet. Runops gcp hub setupagainst the hub first, thenops gcp setupagainst staging — the environment provisioner grants against the hub repository, so the hub must exist before any environment names it.- Each provisioned row has five private assets, documents, exports, logs, and applications buckets.
- On those rows, both the deployment runtime identity and the active operator have bucket-scoped
roles/storage.objectAdmin; none of the buckets grantsallUsers.
The five-bucket list is the current checkpoint, not the target topology. Issue
#1103 coordinates the migration to exactly one private
object-storage bucket per deployment and never one bucket per Project. Project growth adds rows and logical key space,
with lanes such as {project-code}/documents/ and {project-code}/exports/ inside the deployment bucket; it does not
create cloud buckets. Marketing bytes remain available through the same-origin /assets/* application route while the
bucket itself stays private and does not grant allUsers. Until that atomic migration lands, setup reconciles the five
existing bucket resources recorded above.
New clusters are created with --enable-fleet; the subsequent idempotent reconciliation can therefore report Changing existing fleet membership is not supported. Navigator treats only that exact response as already reconciled and
continues; unrelated Fleet errors still stop setup.
Infrastructure is not deployment. The two production clusters currently have no application namespace, Deployment,
Service, Gateway, or HTTPRoute, so their public hosts do not yet answer TLS. An operator must ship one immutable release
to Neon production and Neon Law production, then prove /readyz, /version, certificate readiness, Restate
registration, and the browser surface before posting the #navigator handoff.
For a prefix <name>, set NAVIGATOR_GKE_CLUSTER_NAME=<name>, NAVIGATOR_GKE_CONTEXT=gke_<project>_<region>_<name>,
NAVIGATOR_K8S_NAMESPACE=<name>, NAVIGATOR_VPC_NAME=<name>-vpc, NAVIGATOR_SUBNETWORK_NAME=<name>-subnet,
NAVIGATOR_GATEWAY_IP_NAME=<name>-gateway-ip, NAVIGATOR_ASSETS_BUCKET=<name>-assets,
NAVIGATOR_DOCUMENTS_BUCKET=<name>-documents, NAVIGATOR_EXPORTS_BUCKET=<name>-exports,
NAVIGATOR_LOGS_BUCKET=<name>-logs, NAVIGATOR_APPLICATIONS_BUCKET=<name>-applications,
NAVIGATOR_GCP_SERVICE_ACCOUNT_ID=<name>-web, NAVIGATOR_DRIVE_GCP_SERVICE_ACCOUNT_ID=<name>-drive, and
NAVIGATOR_WEB_SECRET_NAME=<name>-web-secrets. Bucket names are global; add one stable organization prefix if a short
bucket name is already taken.
Also set NAVIGATOR_GCP_PROJECT_ID, NAVIGATOR_GCP_LOCATION, NAVIGATOR_PUBLIC_HOST, NAVIGATOR_WORKFLOWS_HOST,
NAV_BASE_URL=https://$NAVIGATOR_PUBLIC_HOST, NAVIGATOR_WORKFLOWS_URL=https://$NAVIGATOR_WORKFLOWS_HOST/,
NAVIGATOR_WEB_IMAGE, and NAVIGATOR_ASSET_BASE_URL=$NAV_BASE_URL/assets in that deployment's
deployments/<name>/config.toml. Set that row's own NAVIGATOR_SURREAL_* coordinates and credentials in its
secrets.enc.yaml.
Keep the mail rail complete in every deployment's secrets.enc.yaml: SENDGRID_API_KEY, SENDGRID_FROM_EMAIL,
SENDGRID_INBOUND_SECRET, SENDGRID_EVENTS_SECRET, and SENDGRID_EVENTS_PUBLIC_KEY; staging uses non-production
SendGrid credentials and production uses live credentials authenticated for its domain.
Provision each row with the same region-agnostic command — one run per deployments/ directory, its coordinates
exported from that directory's config.toml (populate the directory first; the config is the source of every coordinate
the provisioner reads, so a row is provisioned only once its directory exists). Each run creates five buckets and one
Autopilot cluster; re-running reconciles only that row:
set -a; eval "$(grep ' = "' deployments/neon-law-stg/config.toml | sed 's/ = /=/')"; set +a
navigator ops gcp setup --dry-run
Check every printed project, region, resource prefix, and the two API batches. Provision the registry hub once before the runtime stacks; it runs no workloads:
navigator ops gcp hub setup \
--project-id ghcr --region us-west4 --dry-run
navigator ops gcp hub setup \
--project-id ghcr --region us-west4
Then apply the already-reviewed plans one deployment at a time. Keep staging first, and do not begin production until its command finishes:
set -a; eval "$(grep ' = "' deployments/neon-law-stg/config.toml | sed 's/ = /=/')"; set +a
navigator ops gcp setup
That is the live resource boundary, per deployment: five buckets, one VPC/subnet pair, one reserved gateway address, one
Autopilot cluster, one KMS key, and two deployment service accounts. The command also enables the twenty-two required
APIs in that runtime project. A completed command is not yet a deployed website; ops ship, DNS, and the browser checks
later in this workshop remain required.
Setup provisions no database and generates no credential, so there is nothing printed to record. The store's own
credentials go into deployments/<name>/secrets.enc.yaml (sops set — the plaintext never touches disk), then
navigator ops secrets apply --deployment <name>; rotation happens at the provider first, per
docs/deployment-secrets.md.
Resolve every reserved address and record the public value as NAVIGATOR_GATEWAY_IP in the matching config.toml — an
IP is a public coordinate, so it is edited and committed like any other:
for name in $(ls deployments); do
set -a; eval "$(grep ' = "' "deployments/${name}/config.toml" | sed 's/ = /=/')"; set +a
echo "${name}:"
gcloud compute addresses describe "$NAVIGATOR_GATEWAY_IP_NAME" \
--global --project "$NAVIGATOR_GCP_PROJECT_ID" --format 'value(address)'
done
Populate the matching local Kubernetes context before shipping a row:
set -a; eval "$(grep ' = "' deployments/neon-law-stg/config.toml | sed 's/ = /=/')"; set +a
gcloud container clusters get-credentials \
"$NAVIGATOR_GKE_CLUSTER_NAME" --region "$NAVIGATOR_GCP_LOCATION" \
--project "$NAVIGATOR_GCP_PROJECT_ID"
The resulting context name must equal that config's NAVIGATOR_GKE_CONTEXT. Repeat once for each deployment you are
operating.
The canonical matrix is the compact reference for every row.