Cloud Deployment (Google Cloud Run)
What This Is
The cloud deployment runs SourceQuote on Google Cloud Platform as a fully managed, auto-scaling service. It uses:
- Cloud Run — serverless container hosting; scales to zero when idle, scales out automatically under load
- Cloud SQL (PostgreSQL 15) — managed PostgreSQL with automatic backups and CMEK encryption
- Google Cloud Storage (GCS) — object storage for all project files (audio, transcripts, embeds)
- Secret Manager — encrypted storage for all credentials; never stored as plain environment variables
- Artifact Registry — Docker image storage for Cloud Run deployments
- Cloud KMS — encryption key management for Cloud SQL and GCS
- Cloud Scheduler — runs daily analytics and usage rollover jobs
- Cloud Build — builds and pushes Docker images on each deploy
Key characteristics:
- Fully managed — no infrastructure to maintain; GCP handles scaling, patching, and availability
- Scales to zero — no idle costs when the app is not in use
- All secrets in Secret Manager —
SECRET_KEY, database credentials, Firebase, Stripe, and Modal tokens are never passed as plain text - CMEK encryption — Cloud SQL is encrypted with a KMS-managed key
- Multi-user — Firebase authentication, same as server mode
- Modal.com transcription — cloud transcription routes through Modal.com; local Whisper is not used
Prerequisites: Assumes the development environment is set up as described in ENVIRONMENT_SETUP.md. Requires gcloud CLI and Docker installed.
Architecture Overview
User browser
│
▼
Cloud Run (Flask app, 2 vCPU / 2 GiB RAM, up to 10 instances)
│
├── Cloud SQL (PostgreSQL 15, encrypted with Cloud KMS)
├── GCS bucket (<project-id>-userdata)
└── Secret Manager (all credentials)
Cloud Scheduler
├── Daily: POST /api/internal/collect-analytics (02:00 UTC)
└── Daily: POST /api/internal/usage-rollover (00:05 UTC)
Before You Start
You will need:
- A GCP project with billing enabled
- A Firebase project (can be the same GCP project, or separate)
- Firebase web API key, auth domain, and a service account JSON key (see
ENVIRONMENT_SETUP.md § 7) - Optional but recommended: Stripe keys, Modal token, SMTP credentials (see
ENVIRONMENT_SETUP.md § 9)
1. Authenticate with GCP
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
Also authenticate Docker to push images:
gcloud auth configure-docker REGION-docker.pkg.dev
Replace REGION with your intended region (e.g., us-central1).
2. Configure the Cloud Environment
Copy the example files if you haven't already:
cp config/cloud/.env.cloud.example config/cloud/.env.cloud
cp config/cloud/.secrets.cloud.example config/cloud/.secrets.cloud
Edit config/cloud/.env.cloud
Fill in all values:
# GCP
GOOGLE_CLOUD_PROJECT=your-gcp-project-id
GCS_REGION=us-central1
# Firebase
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_API_KEY=AIzaSy...
FIREBASE_AUTH_DOMAIN=your-project-id.firebaseapp.com
AUTH_PROVIDER=firebase
# User registration
# false = users must be manually activated (recommended for production)
ALLOW_OPEN_REGISTRATION=false
# Access control
ALLOWED_ORIGINS=https://yourdomain.com
ADMIN_UIDS=uid1,uid2
ADMIN_EMAIL=you@yourdomain.com
# Public URL (set to your Cloud Run URL or custom domain once deployed)
DEFAULT_SERVER=https://yourdomain.com
# SMTP (optional — for admin email notifications)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_FROM=you@gmail.com
SMTP_FROM_NAME=SourceQuote
# Stripe
STRIPE_BETA_MODE=0
# Modal.com token ID (non-secret half)
MODAL_TOKEN_ID=ak-...
# GitLab bug reporting (optional)
GITLAB_URL=https://gitlab.com
GITLAB_PROJECT_ID=
Do not set
DEPLOY_MODE,STORAGE_BACKEND,GCS_BUCKET_NAME,SECRET_KEY,DATABASE_URL,INTERNAL_TOKEN, orFLASK_DEBUG— these are managed automatically by the deploy scripts.
Edit config/cloud/.secrets.cloud
# Path to the Firebase service account JSON key
FIREBASE_SERVICE_ACCOUNT=/path/to/firebase-service-account.json
# SMTP password
SMTP_PASS=your_app_password
# Stripe keys
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# GitLab token (api scope)
GITLAB_TOKEN=
# HuggingFace token (for model downloads, if ever needed server-side)
HF_TOKEN=hf_...
# Modal.com secret half
MODAL_TOKEN_SECRET=as-...
These values are pushed to Secret Manager by the init and deploy scripts and are never written to Cloud Run environment variables.
3. Run One-Time GCP Infrastructure Setup
This step provisions all required GCP resources. It is safe to re-run — all steps are idempotent.
npm run gcs:init
The script:
- Enables GCP APIs — Cloud Run, Cloud Build, Artifact Registry, Cloud SQL Admin, Secret Manager, GCS, Cloud KMS, Cloud Scheduler
- Creates Artifact Registry repository
sourcequotein your region - Creates the Cloud Run service account
sourcequote-sa@PROJECT.iam.gserviceaccount.comwith roles:roles/storage.objectAdmin(GCS read/write)roles/logging.logWriter(Cloud Logging)roles/cloudsql.client(Cloud SQL access)roles/secretmanager.secretAccessor(Secret Manager read)roles/cloudkms.cryptoKeyEncrypterDecrypter(KMS operations, scoped tomaster-kek)
- Creates the Cloud Scheduler service account
sourcequote-scheduler@PROJECT.iam.gserviceaccount.comwithroles/run.invoker - Creates the GCS bucket
PROJECT_ID-userdatawith public access prevention enforced - Creates the KMS key ring
sourcequote-keysand master keymaster-kek - Auto-generates
SECRET_KEYandINTERNAL_TOKENand pushes them to Secret Manager - Syncs your secrets from
config/cloud/.secrets.cloudto Secret Manager - Grants Cloud Build permissions to deploy Cloud Run services
- Creates the Cloud SQL instance
sourcequote-db(PostgreSQL 15,db-f1-microtier, CMEK encrypted), creates thesourcequotedatabase and user, and initialises the schema
Note: Cloud SQL creation takes 5–10 minutes. The script waits for it automatically.
After gcs:init completes, DATABASE_URL is stored in Secret Manager. You do not need to configure it manually.
4. Push Secrets to Secret Manager
After filling in or changing any values in config/cloud/.secrets.cloud, sync them to Secret Manager:
npm run gcs:secrets
Run this any time a secret value changes (e.g., rotating a Stripe key or updating the Firebase service account).
5. Deploy to Cloud Run
npm run gcs:deploy
This script:
- Builds a Docker image from the
Dockerfilein the project root - Tags it with the current git commit SHA and
:latest - Pushes to Artifact Registry
- Deploys (or updates) the Cloud Run service using
cloud/cloudrun.yaml
Skip the Docker Build (If Image Already Exists)
If you've already built the image and only need to redeploy:
npm run gcs:deploy:skip-build
6. Get the Cloud Run URL
After the first deploy, get the service URL:
gcloud run services describe sourcequote --region=YOUR_REGION --format="value(status.url)"
Update DEFAULT_SERVER in config/cloud/.env.cloud to this URL (or your custom domain), then redeploy.
7. Configure a Custom Domain (Optional)
To use a custom domain instead of the *.run.app URL:
- In the GCP Console, go to Cloud Run → Manage Custom Domains
- Add your domain and follow the DNS verification steps
- Update your DNS records as instructed
- Update
ALLOWED_ORIGINSandDEFAULT_SERVERin.env.cloud - Run
npm run gcs:deployto apply the config change
8. Make Your First Admin User
After deploying and signing in for the first time:
- Find your Firebase UID from the Firebase Console → Authentication → Users
- Add the UID to
ADMIN_UIDSinconfig/cloud/.env.cloud - Redeploy:
npm run gcs:deploy
Or use the Cloud SQL console to set admin flags directly in the database.
9. Configure Stripe Webhook (Production)
For subscription events to reach your deployed app:
- In the Stripe Dashboard → Developers → Webhooks, create an endpoint:
https://yourdomain.com/api/stripe/webhook - Select the events you want to handle (at minimum:
customer.subscription.updated,invoice.payment_succeeded) - Copy the webhook signing secret (
whsec_...) - Add it to
config/cloud/.secrets.cloudunderSTRIPE_WEBHOOK_SECRET - Run
npm run gcs:secretsto push the updated secret to Secret Manager
10. Updating the Deployment
After any code change:
npm run gcs:deploy
After any secret change (credentials, keys):
npm run gcs:secrets
npm run gcs:deploy
After any non-secret config change in .env.cloud:
npm run gcs:deploy
(The deploy script reads .env.cloud and injects non-secret values into the Cloud Run service manifest automatically.)
Infrastructure Reference
| Resource | Name | Notes |
|---|---|---|
| Cloud Run service | sourcequote | 2 vCPU, 2 GiB RAM, min 0 / max 10 instances |
| Cloud SQL instance | sourcequote-db | PostgreSQL 15, db-f1-micro, CMEK |
| GCS bucket | PROJECT_ID-userdata | All project files |
| Artifact Registry | sourcequote | Docker images |
| KMS key ring | sourcequote-keys | Region-scoped |
| KMS key | master-kek | Encrypts Cloud SQL |
| Service account (app) | sourcequote-sa@PROJECT.iam.gserviceaccount.com | Cloud Run identity |
| Service account (scheduler) | sourcequote-scheduler@PROJECT.iam.gserviceaccount.com | Cloud Scheduler identity |
| Cloud Scheduler jobs | collect-analytics, usage-rollover | 02:00 UTC, 00:05 UTC daily |
Command Reference
| Command | Description |
|---|---|
npm run gcs:init | One-time GCP infrastructure provisioning |
npm run gcs:secrets | Sync secrets from .secrets.cloud to Secret Manager |
npm run gcs:deploy | Build image and deploy to Cloud Run |
npm run gcs:deploy:skip-build | Deploy without rebuilding the Docker image |