Skip to content

Engine on Google Cloud Run

Cloud Run runs the engine image as a request-driven service that can scale to zero. Its free monthly allotment of requests, CPU and memory covers a low-traffic engine.

  • A Google Cloud project with Cloud Run, Artifact Registry and Secret Manager turned on, and the gcloud CLI signed in to it.
  • A Postgres database: Cloud SQL, or Supabase or Neon for a free one.
  • An Artifact Registry remote repository whose upstream is https://ghcr.io. Cloud Run deploys images from Artifact Registry or Docker Hub, not from ghcr.io directly.

Choose the port. --port=3001 publishes the Admin API, which you need to create the first administrator and define content types. --port=3002 publishes the Content API for your sites and apps.

Terminal window
gcloud run deploy lyeve-engine \
--image=<region>-docker.pkg.dev/<project>/<remote-repo>/lyeve-labs/lyeve-core:latest \
--port=3001 \
--allow-unauthenticated \
--min-instances=0 \
--max-instances=1 \
--set-env-vars="SECURE_COOKIE=true,RATE_LIMIT_RPS=100,DATABASE_MAX_CONNECTIONS=5" \
--set-secrets="DATABASE_URL=lyeve-database-url:latest,JWT_SECRET=lyeve-jwt-secret:latest,ENCRYPTION_KEY=lyeve-encryption-key:latest,LYEVE_AUDIT_HMAC_KEY=lyeve-audit-hmac-key:latest"

Keep --max-instances=1, and do not deploy a second service from the image for the other port. Each instance signs with a key of its own, so a token from one is refused by another. --min-instances=1 removes cold starts but keeps an instance running, which is billed.

Pin a release tag instead of latest in production. See Docker images.

Store the four secrets in Secret Manager and pass them with --set-secrets, as above, so they stay out of the service's visible configuration:

Terminal window
printf %s "$(openssl rand -hex 32)" | gcloud secrets create lyeve-jwt-secret --data-file=-
printf %s "$(openssl rand -hex 32)" | gcloud secrets create lyeve-encryption-key --data-file=-
printf %s "$(openssl rand -hex 32)" | gcloud secrets create lyeve-audit-hmac-key --data-file=-
printf %s "<your connection string>" | gcloud secrets create lyeve-database-url --data-file=-

The service's account needs permission to read them. Production refuses to start without these secrets, SECURE_COOKIE=true and a non-zero RATE_LIMIT_RPS. Configuration lists every check.

  • Cloud SQL for PostgreSQL keeps the database in the same cloud. Connect it with --add-cloudsql-instances or the Cloud SQL Auth Proxy. Cloud SQL is billed separately.
  • Supabase or Neon connect over the internet. Both have a free plan, which makes them the path for a $0 stack. Each page says which connection string to use.

Keep DATABASE_MAX_CONNECTIONS small on a free database plan.

By default Cloud Run checks that the container accepts connections on --port. For HTTP checks, set a startup probe on /startup and a liveness probe on /healthz. Both listeners serve /healthz, /readyz and /startup without authentication and over plain HTTP. Do not use /api/admin/health or /api/v1/health: they need authentication, so a probe gets 401 and marks a healthy instance down. Health endpoints says what each one checks.

With --port=3001:

Terminal window
curl https://lyeve-engine-<hash>-<region>.a.run.app/api/admin/setup

A new install answers {"setup_required":true,"token_source":"log"}. The setup token is the one-time setup_token in the service's log, or LYEVE_SETUP_TOKEN when you set it. The quickstart continues from step 2.

With --port=3002, fetch /.well-known/jwks.json instead. Only the Content API serves it.