GCP
A starter recipe for self-hosted Flux on GCP — Cloud Run, Cloud SQL Postgres, Secret Manager.
A starter recipe for running Flux on Google Cloud. Cloud Run for compute, Cloud SQL for the database, Secret Manager for the bootstrap configuration. The same constraints from the Production checklist apply — most relevantly, PostgreSQL as the backend and session affinity if the server service scales past one instance.
Topology
- Cloud Run, two services:
flux-server—min-instances: 1,max-instances: 1as the starter. Raisingmax-instancesabove 1 is supported — replicas coordinate through Cloud SQL (see High availability) — but requires--session-affinityso each worker’s SSE stream keeps landing on the same instance, and instance count then follows request load rather than your availability intent. For a deliberately multi-replica server, GKE gives you explicit control.flux-worker—min-instances: 1(or more, depending on pickup latency tolerance),max-instancesset by your workload.
- Cloud SQL for PostgreSQL, regional with HA. Connect from Cloud Run via the Cloud SQL Auth Proxy sidecar (Cloud Run’s built-in Cloud SQL connector handles this).
- Secret Manager for the bootstrap token, encryption key, and Cloud SQL password.
- Cloud Load Balancing if you need multi-region or a custom domain; otherwise the Cloud Run service’s default URL works for internal-only deployments.
- Cloud Logging picks up stdout/stderr automatically.
Provisioning order
1. Networking and Cloud SQL
Create the Cloud SQL instance with private IP and connect it to a VPC. For the simplest setup, allow Cloud Run to reach it via the Cloud SQL connector (no VPC connector needed). For VPC-native traffic, add a Serverless VPC Access connector and configure both Cloud Run services to use it.
Create the flux database and the flux user with CREATE permission on the database.
2. Secret Manager
# Bootstrap token
gcloud secrets create flux-bootstrap-token --replication-policy=automatic
python -c 'import secrets; print(secrets.token_hex(32))' \
| gcloud secrets versions add flux-bootstrap-token --data-file=-
# Encryption key
gcloud secrets create flux-encryption-key --replication-policy=automatic
python -c 'import secrets; print(secrets.token_hex(32))' \
| gcloud secrets versions add flux-encryption-key --data-file=-
# Database URL (or build it from Cloud SQL Auth Proxy unix socket)
gcloud secrets create flux-database-url --replication-policy=automatic
echo -n "postgresql://flux:PASSWORD@/flux?host=/cloudsql/PROJECT:REGION:INSTANCE" \
| gcloud secrets versions add flux-database-url --data-file=-
Grant the Cloud Run service account roles/secretmanager.secretAccessor on each secret.
3. Container image
Build your image (see Docker for the Dockerfile) and push to Artifact Registry:
gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT/flux/flux:0.56.0 .
4. Cloud Run server service
gcloud run deploy flux-server \
--image REGION-docker.pkg.dev/PROJECT/flux/flux:0.56.0 \
--region REGION \
--platform managed \
--service-account flux-runtime@PROJECT.iam.gserviceaccount.com \
--min-instances 1 \
--max-instances 1 \
--add-cloudsql-instances PROJECT:REGION:INSTANCE \
--set-secrets FLUX_DATABASE_URL=flux-database-url:latest \
--set-secrets FLUX_WORKERS__BOOTSTRAP_TOKEN=flux-bootstrap-token:latest \
--set-secrets FLUX_SECURITY__ENCRYPTION__ENCRYPTION_KEY=flux-encryption-key:latest \
--set-env-vars FLUX_SECURITY__AUTH__API_KEYS__ENABLED=true \
--no-allow-unauthenticated # or --allow-unauthenticated, depending on your auth model
--max-instances 1 keeps the starter simple, not safe — server instances coordinate through PostgreSQL (the scheduler is an advisory-lock singleton per cycle, so two instances never double-fire a cron). If you raise it, add --session-affinity so each worker’s SSE connection sticks to one instance, and account for instances × 40 connections of pool ceiling against Cloud SQL. The --no-allow-unauthenticated flag forces IAM-based access; if you’re using Flux’s own API-key auth as the only layer, set --allow-unauthenticated and rely on Flux to reject unauthenticated requests at the application layer.
5. Cloud Run worker service
gcloud run deploy flux-worker \
--image REGION-docker.pkg.dev/PROJECT/flux/flux:0.56.0 \
--region REGION \
--command flux \
--args start,worker,worker-1 \
--service-account flux-runtime@PROJECT.iam.gserviceaccount.com \
--min-instances 1 \
--max-instances 10 \
--set-secrets FLUX_WORKERS__BOOTSTRAP_TOKEN=flux-bootstrap-token:latest \
--set-env-vars FLUX_WORKERS__SERVER_URL=https://flux-server-xxx.run.app
Workers scale freely on Cloud Run. The --args worker-1 is a starter — for multiple replicas you’ll want a unique name per instance, which Cloud Run doesn’t expose cleanly. In practice you may prefer GKE for worker fleets where naming and label-based routing matter.
Cloud Run caveats
Beyond scale-to-zero:
- Request timeout maxes at 60 minutes on Cloud Run. Long-running workflow steps that exceed that ceiling need a worker on GKE or Compute Engine, not Cloud Run.
- SSE connections Workers hold a long-poll SSE connection to the server. Cloud Run’s per-request timeout (default 5 minutes, max 60) applies; reconnects happen but introduce drift. For long worker uptimes, GKE is a cleaner fit.
- CPU allocation Default is “CPU only during requests.” Set
--cpu-throttling=false(request-throttling off) for the server, so the in-process scheduler keeps running between HTTP requests.
The honest summary: Cloud Run works for low-volume Flux deployments where you want serverless billing. For anything load-bearing, GKE is the better answer.
Secrets
Same separation as on AWS:
- Secret Manager holds the bootstrap configuration (bootstrap token, encryption key, DB password). Cloud Run injects them as env vars at container start.
- Flux’s
secretsprimitive stores application-level secrets inside the Flux database, encrypted with the encryption key from Secret Manager.
What can go wrong
Cold-start drops scheduled fires
Symptom. Cron schedules miss fires; Cloud Run logs show the instance count dropped to zero between cron times.
Cause. min-instances: 0 (default). The scheduler is a background task in the server process; if the instance is scaled to zero, nothing polls the schedule table.
Fix. gcloud run services update flux-server --min-instances 1.
Server unreachable from workers
Symptom. Worker logs show ConnectError to the server’s .run.app URL.
Cause. Worker service account lacks roles/run.invoker on the server service (when the server is --no-allow-unauthenticated), or the URL is wrong.
Fix. Grant roles/run.invoker to the worker service account on the server service, and verify FLUX_WORKERS__SERVER_URL matches the server’s current URL (it changes on first deploy).
Cloud SQL connections exhausted
Symptom. Server logs OperationalError: connection ... aborted under load.
Cause. Cloud Run instances can spin up many concurrent connections; the default Cloud SQL connection limit on small instance types is low.
Fix. Either size up the Cloud SQL instance, or front Cloud SQL with PgBouncer running on GKE. Tune FLUX_DATABASE_POOL_SIZE / FLUX_DATABASE_MAX_OVERFLOW (defaults 20/20 per instance) to fit within the per-instance budget.
This recipe is derived from GCP service documentation and Flux’s deployment requirements; the author hasn’t personally validated it end-to-end on GCP as of 2026-07. Treat IAM bindings, VPC connectors, and Cloud Run flag combinations as starting points to validate in a sandbox project before promoting.