DEPLOY_CLOUD

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 ManagerSECRET_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:

  1. A GCP project with billing enabled
  2. A Firebase project (can be the same GCP project, or separate)
  3. Firebase web API key, auth domain, and a service account JSON key (see ENVIRONMENT_SETUP.md § 7)
  4. 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, or FLASK_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:

  1. Enables GCP APIs — Cloud Run, Cloud Build, Artifact Registry, Cloud SQL Admin, Secret Manager, GCS, Cloud KMS, Cloud Scheduler
  2. Creates Artifact Registry repository sourcequote in your region
  3. Creates the Cloud Run service account sourcequote-sa@PROJECT.iam.gserviceaccount.com with 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 to master-kek)
  4. Creates the Cloud Scheduler service account sourcequote-scheduler@PROJECT.iam.gserviceaccount.com with roles/run.invoker
  5. Creates the GCS bucket PROJECT_ID-userdata with public access prevention enforced
  6. Creates the KMS key ring sourcequote-keys and master key master-kek
  7. Auto-generates SECRET_KEY and INTERNAL_TOKEN and pushes them to Secret Manager
  8. Syncs your secrets from config/cloud/.secrets.cloud to Secret Manager
  9. Grants Cloud Build permissions to deploy Cloud Run services
  10. Creates the Cloud SQL instance sourcequote-db (PostgreSQL 15, db-f1-micro tier, CMEK encrypted), creates the sourcequote database 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:

  1. Builds a Docker image from the Dockerfile in the project root
  2. Tags it with the current git commit SHA and :latest
  3. Pushes to Artifact Registry
  4. 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:

  1. In the GCP Console, go to Cloud Run → Manage Custom Domains
  2. Add your domain and follow the DNS verification steps
  3. Update your DNS records as instructed
  4. Update ALLOWED_ORIGINS and DEFAULT_SERVER in .env.cloud
  5. Run npm run gcs:deploy to apply the config change

8. Make Your First Admin User

After deploying and signing in for the first time:

  1. Find your Firebase UID from the Firebase Console → Authentication → Users
  2. Add the UID to ADMIN_UIDS in config/cloud/.env.cloud
  3. 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:

  1. In the Stripe Dashboard → Developers → Webhooks, create an endpoint:
    https://yourdomain.com/api/stripe/webhook
    
  2. Select the events you want to handle (at minimum: customer.subscription.updated, invoice.payment_succeeded)
  3. Copy the webhook signing secret (whsec_...)
  4. Add it to config/cloud/.secrets.cloud under STRIPE_WEBHOOK_SECRET
  5. Run npm run gcs:secrets to 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

ResourceNameNotes
Cloud Run servicesourcequote2 vCPU, 2 GiB RAM, min 0 / max 10 instances
Cloud SQL instancesourcequote-dbPostgreSQL 15, db-f1-micro, CMEK
GCS bucketPROJECT_ID-userdataAll project files
Artifact RegistrysourcequoteDocker images
KMS key ringsourcequote-keysRegion-scoped
KMS keymaster-kekEncrypts Cloud SQL
Service account (app)sourcequote-sa@PROJECT.iam.gserviceaccount.comCloud Run identity
Service account (scheduler)sourcequote-scheduler@PROJECT.iam.gserviceaccount.comCloud Scheduler identity
Cloud Scheduler jobscollect-analytics, usage-rollover02:00 UTC, 00:05 UTC daily

Command Reference

CommandDescription
npm run gcs:initOne-time GCP infrastructure provisioning
npm run gcs:secretsSync secrets from .secrets.cloud to Secret Manager
npm run gcs:deployBuild image and deploy to Cloud Run
npm run gcs:deploy:skip-buildDeploy without rebuilding the Docker image