Neon Law
  • Services
  • Book Consultation opens in a new tab
  • Sign in

← Operating Neon Law Navigator

Prepare Google Cloud

Chapter 2 of 7 · Section 5 of 45

Sections

1. Intro

  • 1. Deploy your own
  • 2. Agenda

2. Prepare Google Cloud

  • 3. Bring your own project
  • 4. Dry-run first
  • 5. Private assets and domain restricted sharing
  • 6. The Navigator deployment matrix
  • 7. The `/app` mount and HTTP route ownership
  • 8. `neon` — the whole brand seam
  • 9. Live rollout checkpoint
  • 10. Set one site to one version

3. Provision the Infrastructure

  • 11. The APIs that light up
  • 12. Network and storage buckets
  • 13. How a Project portal reaches a client
  • 14. A document's bytes and authorization are separate
  • 15. A private image registry
  • 16. The cluster comes up
  • 17. What setup owns — and what follows it

4. Environment Matrix

  • 18. Three operating modes, two deployment profiles
  • 19. The deployment that says its matters are sample
  • 20. Configuration precedence: the first source wins
  • 21. Local dev controls: inputs read by `navigator dev`
  • 22. Local runtime: what `.devx/env` generates
  • 23. The store: SurrealDB
  • 24. Where SurrealDB authorization lives
  • 25. Deployed runtime: core web and worker wiring
  • 26. Deployed runtime: identity and access
  • 27. Deployed runtime: email, signatures, and billing
  • 28. Deployed runtime: repositories, content, AI, and scheduled work
  • 29. Provision and ship: variables read by the operator CLI
  • 30. Ancillary operations and opt-in test controls
  • 31. When sample data appears

5. Configure the Trust Boundaries

  • 32. Secrets: the invariants that gate the boot
  • 33. Sign-in: bring an OIDC provider; passwords live there, not here
  • 34. Role rings: who can do what
  • 35. Provider signup and parity across the deployments
  • 36. The external surface — every third party, in one place
  • 37. The two service deployments
  • 38. Security architecture

6. Ship the Instance

  • 39. Ship and verify
  • 40. Post the verified handoff in `#navigator`
  • 41. Point your domain at the instance (optional)
  • 42. Drive it from the CLI
  • 43. Make it yours — white-label under your own brand
  • 44. This is how we set up our production deployment

7. Wrap Up

  • 45. Canonical references

Private assets and domain restricted sharing

Every deployment bucket remains private. navigator ops gcp setup grants the deployment Google service account roles/storage.objectAdmin on its assets, documents, exports, logs, and applications buckets, then maps both Kubernetes service accounts (navigator-web and workflows-service) to that Google identity through Workload Identity. It never adds allUsers and never changes constraints/iam.allowedPolicyMemberDomains. The applications bucket holds each Project's published client-portal bundle at {project-code}/portal/, which web streams same-origin through /app/projects/{code}/portal so the session and Project-participation gate stay on every request.

The public website receives only marketing bytes through GET /assets/*. web reads that object from NAVIGATOR_ASSETS_BUCKET with the deployment identity and returns it with its stored content type, X-Content-Type-Options: nosniff, and a one-hour public cache. Unsafe keys and missing objects return 404; storage failures return 502. There is no parallel anonymous route for documents, exports, or logs. This is an application delivery boundary, not a Navigator persons.role or Project-participation grant.

Set NAVIGATOR_ASSET_BASE_URL=$NAV_BASE_URL/assets in each deployment's config.toml — it is a plaintext coordinate the ops ship preflight requires. Setup also gives the active gcloud identity the same bucket-scoped object CRUD role so the operator and the Kubernetes runtime can inspect and repair objects without a public or project-wide grant.

Publish the public asset inventory before ops ship. A full or image-only roll reads NAVIGATOR_ASSETS_BUCKET and stops before any rollout when a required object is missing or the bucket cannot be read. The preflight and the post-roll check use one inventory. On a source checkout that inventory is every markdown image under server/content, every responsive gallery variant, the brand media keys, and every licensed webfont face. On a deploy-only checkout (--deployments-dir with no server/content directory) blog references are absent, and the inventory is the workshop images embedded in the CLI plus those same gallery variants, brand media keys, and fonts. Restart-only skips both checks.


navigator ops assets build --src /path/to/source-jpegs
navigator ops assets upload
navigator ops assets fonts upload --family eb-garamond --dir /path/to/woff2
navigator site asset upload --host <host> img/<slug>/<file>

assets build then assets upload publishes the gallery variants. assets fonts upload publishes one licensed family. site asset upload publishes one finished image, including brand media and a workshop slide. Run the publish that covers the inventory, then ops ship.

After setup, verify both identities and reject an anonymous principal:


runtime_gsa="$NAVIGATOR_GCP_SERVICE_ACCOUNT_ID@$NAVIGATOR_GCP_PROJECT_ID.iam.gserviceaccount.com"
operator_account="$(gcloud config get-value account)"
case "$operator_account" in
  *.gserviceaccount.com) operator_member="serviceAccount:$operator_account" ;;
  *) operator_member="user:$operator_account" ;;
esac

for bucket_name in \
  "$NAVIGATOR_ASSETS_BUCKET" \
  "$NAVIGATOR_DOCUMENTS_BUCKET" \
  "$NAVIGATOR_EXPORTS_BUCKET" \
  "$NAVIGATOR_LOGS_BUCKET"
do
  iam_rows="$(
    gcloud storage buckets get-iam-policy "gs://$bucket_name" \
      --flatten='bindings[].members[]' \
      --format='value(bindings.role,bindings.members)'
  )"
  printf '%s\n' "$iam_rows" |
    awk -v runtime="serviceAccount:$runtime_gsa" -v operator="$operator_member" '
      $1 == "roles/storage.objectAdmin" && $2 == runtime { runtime_ok = 1 }
      $1 == "roles/storage.objectAdmin" && $2 == operator { operator_ok = 1 }
      $2 == "allUsers" { public = 1 }
      END { if (!runtime_ok || !operator_ok || public) exit 1 }
    '
done

Presenter notes

No output and a zero exit status prove that both principals have roles/storage.objectAdmin and allUsers is absent. The dogfood driver adds the active operator binding when it is absent; the Rust provisioner owns the runtime binding.


Pause on the trust boundary: the organization policy correctly rejects allUsers, so Navigator never solves asset delivery by making a bucket public. Walk the verification output for both named principals, then contrast the one public /assets/* application route with the absence of anonymous document, export, or log routes. The important result is private object CRUD for the operator and workload identity, not a project-wide storage role.


The certificates are the part worth dwelling on. A classic Google-managed certificate is validated by a CA calling the load balancer, so DNS has to point there first — moving a live hostname takes it down for the length of issuance, and Cloud CDN and the HTTP-to-HTTPS redirect both sit in the validation path. Certificate Manager with a DNS authorization avoids that: Google returns a CNAME, the CA reads that record, and the certificate reaches ACTIVE while the hostname still serves its current site. Publish the CNAME first, wait for ACTIVE, then move the A record — a cutover done that way carries no TLS gap. The GKE ingress here owns its own certificates, so it is the classic path above rather than this one.


Name the collision before someone discovers it. A hostname serves one thing, and the matrix on the next slide gives www.neonlaw.com to the neon-law-prod deployment — its NAVIGATOR_PUBLIC_HOST, certificate, and authorized OAuth redirect URI are all issued for that name. A marketing site held it first, and the conflict was settled by retiring that site rather than rehoming it: the load balancer and certificate chain are gone, and only the bucket remains, as an archive nothing routes to. That is the cost worth naming — a static site is cheap to publish and awkward to unpublish, because the hostname is the part two things want.

www.neonlaw.com was the same collision, and it has since been settled the same way: the firm's Navigator deployment now holds that exact name and serves it, and the marketing site no longer routes there. neonlaw.com redirects to it and serves nothing itself.

View all slidesOpen display
← PreviousNext →
Neon Law
  • X opens in a new tab
  • LinkedIn opens in a new tab
  • YouTube opens in a new tab
  • API opens in a new tab
  • Blog opens in a new tab
  • Contact
  • Glossary opens in a new tab
  • Navigator opens in a new tab
  • Notations opens in a new tab
  • Presentations opens in a new tab
  • Privacy opens in a new tab
  • Team opens in a new tab
  • Terms opens in a new tab
  • Testimonials
  • UX opens in a new tab
  • contact@neonlaw.com
  • +1 510 800 2080
  • Nevada
    5150 Mae Anne AveSte 405-9002Reno, NV 89523
  • New York
    12 E 49th St18th FloorNew York, NY 10017
  • Justice Technology AssociationMission-Aligned Partner opens in a new tab

Attorney advertisement. Nothing here is legal advice without a signed retainer for an active project. Past results do not guarantee future outcomes.

© 2026 Shook Law PLLC

NEON LAW® is a registered trademark of Shook Law PLLC, U.S. Reg. No. 6,325,650 opens in a new tab

Powered by Neon Law Navigator 26.10.4

Everyone deserves to be seen. Made with ❤️ in 🗽.