7.2 KiB
Production deployment
The server runs as Cloud Run service heliosian in project heliosian, region us-west1, at https://heliosian-489539474126.us-west1.run.app, with https://who2.heliosian.com mapped on top. Administration is driven with the gcloud CLI (brew install --cask gcloud-cli) authenticated as a project owner (gcloud auth login).
Build and deploy pipeline
Every push to main on github.com/heliosian/heliosian deploys production. The Cloud Build trigger build-main (region us-west1, attached through the github connection) carries its build config inline — nothing in the repo — and runs three steps:
docker buildof the repo's Dockerfile, tagged with the commit SHA andlatest- push the SHA tag to Artifact Registry (
us-west1-docker.pkg.dev/heliosian/heliosian/heliosian) gcloud run deploy heliosian --image …:<sha>— image only; every other service setting persists from the configuration below
The trigger names its build service account (the project's default compute service account) explicitly; trigger creation in this project refuses to infer one. Builds ship the pushed commit, never a working tree. To rebuild and redeploy current main without a push:
gcloud builds triggers run build-main --region=us-west1 --branch=main
The Dockerfile builds in two stages: a golang stage compiles the static binary (CGO_ENABLED=0), and gcr.io/distroless/static-debian12 — CA certificates and tzdata, nothing else — carries the binary plus web/, whose templates and static assets are read from disk at runtime. sampledata/ is deliberately excluded: production always sets DIRECTORY_SHEET, and a misconfigured server fails at startup rather than silently serving sample data. creds/ never enters the image (.dockerignore, .gcloudignore), and the image holds no credentials of any kind — the runtime identity below is the only Google identity in play.
Service
The service was created once with:
gcloud run deploy heliosian \
--image us-west1-docker.pkg.dev/heliosian/heliosian/heliosian:latest \
--region us-west1 --allow-unauthenticated \
--service-account directory@heliosian.iam.gserviceaccount.com \
--min-instances 1 --max-instances 1 --memory 4Gi --no-cpu-throttling \
--set-env-vars DIRECTORY_SHEET=<spreadsheet id>,GOOGLE_CLIENT_ID=<oauth client id> \
--set-secrets "SESSION_KEY=heliosian-session-key:latest,GOOGLE_MAPS_SERVER_KEY=heliosian-geocoding-key:latest,GOOGLE_MAPS_BROWSER_KEY=heliosian-maps-browser-key:latest" \
--quiet
Each flag is load-bearing:
--service-account— the runtime identity. Application-default credentials inside the container resolve todirectory@through the metadata server; there is no key file anywhere in the system.--min-instances 1— startup preloads every media file from Drive before listening (about two and a half minutes all told); far too slow for scale-to-zero, and close enough to Cloud Run's four-minute startup probe window to watch as the media set grows.--max-instances 1— the directory model and blob store live in per-instance memory with no cross-instance coherency; a self-service edit refreshes only the instance that handled it, so a second instance would serve stale data.--memory 4Gi— the blob store holds all media and thumbnails in RAM, and the preload peaks well above the steady-state footprint (a roughly 600 MB blob store OOMed a 2 GiB instance during preload). The startup log lineblob store: … MB in memoryreports the steady state; resize when boots start failing or the footprint approaches half the limit.--no-cpu-throttling— the directory model and blob store refresh on five-minute tickers between requests; default throttling would starve them.--allow-unauthenticated— the app enforces its own Google sign-in; Cloud Run must let everyone reach the login page.
Cloud Run injects PORT; the server honors it.
Configuration
Plain environment variables:
DIRECTORY_SHEET— the production spreadsheet id: theDirectorysheet living in the community shared drive.GOOGLE_CLIENT_ID— the OAuth web client id; not a secret (it is embedded in the login page).
Secret Manager secrets, delivered as environment variables per --set-secrets above. Values are used raw, so payloads must not carry trailing newlines:
heliosian-session-key— session-cookie HMAC key (any long random string); losing or rotating it signs everyone outheliosian-geocoding-key— the Geocoding API server key, mirrored locally ascreds/geocoding.keyheliosian-maps-browser-key— the Maps JavaScript browser key, mirrored locally ascreds/maps.key
IAM
directory@heliosian.iam.gserviceaccount.com is both the data identity and the runtime identity:
- Data access: content manager on the community shared drive — which covers editing the
Directorysheet inside it (self-service edits write cells and append to the Change Log tab) and managing media (uploads create files and archive old versions). Shared in Drive directly, never through project IAM. - Runtime: the Cloud Run service runs as it, and it holds Secret Manager Secret Accessor on each secret individually.
- Humans:
roles/iam.serviceAccountTokenCreatoron this account enables the local impersonation that real-data development uses (docs/dev.md).
The build service account (the default compute service account) holds cloudbuild.builds.builder, run.developer, and Service Account User on directory@ — the last because deploying a service that runs as an account requires permission to act as it.
The heliosian.com organization ships Google's secure-by-default org policies, two of which matter here:
iam.managed.disableServiceAccountKeyCreationstays enforced. The keyless design depends on it staying enforced: no key fordirectory@can exist.iam.allowedPolicyMemberDomainsis overridden to allow-all at the project scope only, because--allow-unauthenticatedrequires grantingrun.invokertoallUsers.
OAuth
The consent screen lives in this project, audience External and published to production. Internal is not an option: the app restricts sign-in to heliosschool.org accounts (internal/auth), and those live outside the heliosian.com org. Published-External keeps the basic sign-in scopes free of verification friction.
The web client's authorized JavaScript origins are http://localhost:8080, the run.app URL, and https://who2.heliosian.com; sign-in fails on any origin not listed, and edits take a few minutes to propagate. The browser maps key is rendered into every page, so it carries an HTTP-referer restriction for the same three origins.
Domain
who2.heliosian.com is a Cloud Run domain mapping on the service. DNS carries who2 CNAME ghs.googlehosted.com.; Google provisions and renews the certificate once the record resolves. The run.app URL stays live alongside it.
Verifying a deploy
The startup log (Cloud Run → Logs, or gcloud logging read) shows the full boot sequence: the blob store footprint line, geocoding count, directory model load, then listening — about two and a half minutes after the instance starts. After any deploy, an existing session should still work — if everyone got signed out, SESSION_KEY stopped reaching the server.