Skip to main content

Deployment

GitLab CI orchestrates everything. The pipeline is project-portable (parameterized by RUN_PROJECT_ID, WIF_SERVICE_ACCOUNT, AR_HOST) so a fresh GCP project can be onboarded by reapplying the Terraform stack and updating those variables.

Stage diagram

Promotion model

ServiceMaster pushGit tag
nano-server (run)Build + deploy at 0% trafficPromote latest to 100%
nano-mcp-server (run)Build + deploy at 0% trafficPromote latest to 100%
Frontend (hosting)Preview channel per branchLive deploy
Docs (hosting)Preview channel per branchLive deploy

Operators promote a Cloud Run revision by pushing a tag (v3.x.y). The CICD template's RUN_update_traffic branch fires only when $CI_COMMIT_TAG is present.

Manual promote (if needed):

gcloud run services update-traffic nano-server --region=europe-west4 --project=cpl-gen-ai-marketing --to-latest
gcloud run services update-traffic nano-mcp-server --region=europe-west4 --project=cpl-gen-ai-marketing --to-latest

Firebase Hosting multi-site

Two sites under the same GCP project share Firestore, IAM, and billing:

TargetSite nameURL (Firebase fallback)Primary custom domain
appcpl-gen-ai-marketinghttps://cpl-gen-ai-marketing.web.apphttps://nano.cpl.ai
docscpl-gen-ai-marketing-docshttps://cpl-gen-ai-marketing-docs.web.apphttps://docs.nano.cpl.ai

The MCP Cloud Run service has its own custom domain mapping: https://mcp.nano.cpl.ainano-mcp-server (Cloud Run, region europe-west1).

CI deploys each via firebase deploy --only hosting:<target> driven by the FIREBASE_TOKEN GitLab variable (generated via firebase login:ci, stored Masked + Protected).

One-time setup for a new project

  1. Create the GCP project; enable Cloud Run, Cloud Tasks, Firestore, Firebase, Secret Manager, IAM APIs.

  2. Create the gitlab-ci@<project>.iam.gserviceaccount.com SA manually (Terraform later codifies it as a data block).

  3. Run terraform apply in terraform/bootstrap/, then terraform/runtime/.

  4. Seed secret values: gemini-api-key, mcp-private-key, mcp-google-oauth-client-id, mcp-google-oauth-client-secret.

  5. Create the Firebase hosting sites:

    firebase hosting:sites:create cpl-gen-ai-marketing --project cpl-gen-ai-marketing
    firebase hosting:sites:create cpl-gen-ai-marketing-docs --project cpl-gen-ai-marketing
    firebase target:apply hosting app cpl-gen-ai-marketing --project cpl-gen-ai-marketing
    firebase target:apply hosting docs cpl-gen-ai-marketing-docs --project cpl-gen-ai-marketing
  6. Seed the FIREBASE_TOKEN GitLab CI/CD variable (firebase login:ci).

  7. Configure Google OAuth client (Web type), add redirect URI https://<mcp-url>/auth/google/callback, store client id + secret in Secret Manager.

  8. Push to master. First pipeline builds ci-base, applies Terraform, builds + deploys Cloud Run services and a hosting preview channel per branch.

  9. Tag a release (vX.Y.Z) to promote traffic and deploy hosting live.

DNS for custom domains

Live domains: nano.cpl.ai, docs.nano.cpl.ai, mcp.nano.cpl.ai. DNS is managed manually in Cloud DNS (not in Terraform yet).

To add a new domain or re-bind one:

  1. Firebase Hosting site (or Cloud Run service) → "Add custom domain".
  2. Add the verification TXT record at the DNS provider.
  3. Wait for verification, then add the A / CNAME records the console prints. SSL provisioning is automatic (managed certificate).
  4. For Firebase Auth (Google sign-in) to work on the new domain, add it to Firebase Console → Authentication → Settings → Authorized domains. Missing this throws Firebase: Error (auth/unauthorized-domain) at sign-in.
  5. MCP_PUBLIC_URL on nano-mcp-server must match the public-facing URL (https://mcp.nano.cpl.ai) or every OAuth iss / aud check fails. CLOUD_RUN_URL continues to point at the internal Cloud Run service URL — see ADR-0003.
  6. CORS allowlist in packages/server/src/index.ts must contain the new frontend origin or browser fetches fail with CORS errors.