Cloud Run Deployment (Alternate Host)

Reading Progress55%

Appendix C: Cloud Run Deployment (Alternate Host)

Primary production hosts are Vercel + Fly + FORJD (Chapter 22). This appendix is the checklist for an alternate Google Cloud Run control-plane residency when required.

Env templates: backend/.env.example, frontend/.env.example, marketing/.env.example.

Every hostname, FORJD API URL, and cross-site URL is env-driven — never hardcode domains in application code. Cloud Run must not become a parallel DEML stream plane; sealed ingest, status, projections, and analytics remain on FORJD.

Pre-Deploy Checklist

Before creating services, prepare:

  1. Secrets in Cloud Run Variables or Infisical (recommended for SOC 2 / CMMC): SECRET_KEY, FIREBASE_SERVICE_ACCOUNT_JSON, GCP_SERVICE_ACCOUNT_JSON, Stripe, Resend, threat-intel API keys, HF_TOKEN, SENTRY_DSN.
  2. Cross-site URL trio (same names on backend, frontend, and marketing builds):
Variable Production value Purpose
FRONTEND_URL https://deml.app Angular app, widgets, status
BACKEND_URL https://backend.deml.app Django API, OAuth callbacks
MARKETING_URL https://dataengineeringformachinelearning.com Astro site, auth handoff, CORS
  1. CORS / CSRF must list every public origin (app + marketing + backend + local dev). Copy from backend/.env.example and extend for your domains.
  2. Production guards: DEBUG=False, unique SECRET_KEY. On Cloud Run, the backend fails fast if these are insecure (backend/utils/env.py).
  3. Privacy defaults: SENTRY_SEND_PII=false, STRUCTURED_LOGS=true (JSON logs with correlation IDs).

How to Deploy in One Project

  1. Create a New Project: GCP dashboard → New ProjectEmpty Project (name: deml).
  2. Add Postgres: NewDatabasePostgreSQL. Note the internal DATABASE_URL.
  3. Add Services: For each component below, New ServiceGitHub Repo → select this repository.
  4. Configure each service (Settings tab):
  • Root Directory as specified below.
  • Start Command when overridden.
  • Watch Paths (e.g. /frontend/**, /backend/**) so unrelated changes do not trigger rebuilds.
  1. Variables tab: Set env vars per service (see per-service sections). Workers share nearly the same bundle as deml-backend.
  2. Redeploy frontend after changing build-time vars (FRONTEND_URL, BACKEND_URL, MARKETING_URL, FIREBASE_*, SENTRY_DSN) so set-env.js regenerates environment.ts.
  3. Marketing site (Astro) is hosted outside this Cloud Run project (Firebase Hosting). Set the same URL trio at build time on that host.
  4. Firebase is Auth + marketing Hosting only — Cloud Functions and Firestore are retired; data storage and retrieval run through FORJD + Supabase.

Infisical Integration

To satisfy strict secret management guidelines (SOC 2, CMMC 2.0, NIST SP 800-171 Rev. 3 CC6.1/CC6.2), all secret keys, passwords, and API credentials are kept out of raw service settings and stored inside Infisical.

  1. Set up an Infisical organization and create a project for dataengineeringformachinelearning.
  2. Connect your Cloud Run services to Infisical via the official Cloud Run Infisical Integration.
  3. For local development, run tasks using the Infisical CLI:
infisical run -- python manage.py runserver

Services Overview

1. Web Frontend (deml-frontend)

Angular SPA — dashboard, status pages, widgets.

  • Root Directory: /frontend
  • Builder: Dockerfile (gcr.io/distroless/nodejs22-debian12 runtime; multi-stage from node:22-alpine)
  • Start Command: node dist/frontend/server/server.mjs (Angular SSR; Dockerfile default)
  • Public URL: https://deml.app
  • Private Internal DNS: deml-frontend.internal
  • Build step: set-env.js runs at deploy and writes src/environments/environment.ts.

Required build-time variables (see frontend/.env.example):

Variable Example Notes
FRONTEND_URL https://deml.app Widget + status links
BACKEND_URL https://backend.deml.app API base
MARKETING_URL https://dataengineeringformachinelearning.com Auth handoff
FIREBASE_API_KEY (secret) Web auth
FIREBASE_PROJECT_ID demldotcom
FIREBASE_APP_ID (from Firebase console)
FIREBASE_AUTH_DOMAIN demldotcom.firebaseapp.com
FIREBASE_STORAGE_BUCKET demldotcom.firebasestorage.app
FIREBASE_MESSAGING_SENDER_ID (from Firebase console)
SANITY_PROJECT_ID hj5wtuct CMS content
SANITY_DATASET production
SENTRY_DSN (optional) Client error reporting; omit to disable

2. Web Backend (deml-backend)

Django + Ninja API — auth, billing, consent, FORJD BFF adapters, monitor.

  • Root Directory: /backend
  • Builder: Dockerfile (gcr.io/distroless/python3-debian12)
  • Start Command: /opt/venv/bin/python start.py
  • Public URL: https://backend.deml.app
  • Private Internal DNS: deml-backend.internal

Required variables (see backend/.env.example):

Category Variables
Core SECRET_KEY, DEBUG=False, ALLOWED_HOSTS, DATABASE_URL
Cross-site URLs FRONTEND_URL, BACKEND_URL, MARKETING_URL
CORS / CSRF CORS_ALLOWED_ORIGINS, CSRF_TRUSTED_ORIGINS, CORS_ALLOW_CREDENTIALS=True
Event bus FORJD_API_URL=backend.forjd.co, DRAGONFLY_HOST=Dragonfly (FORJD).internal
Firebase FIREBASE_SERVICE_ACCOUNT_JSON, FIREBASE_PROJECT_ID, GOOGLE_CLOUD_PROJECT
OAuth / AI GOOGLE_API_KEY, GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, GOOGLE_OAUTH_REDIRECT_URI
Threat intel ABUSEIPDB_API_KEY, IPINFO_API_KEY, OTX_API_KEY, ISAC_API_KEY, CISA_TAXII_ENDPOINT
Email / alerts RESEND_API_KEY, ALERT_EMAIL_TARGET, ALERT_EMAIL_FROM, DISCORD_WEBHOOK_URL
Observability SENTRY_DSN, SENTRY_SEND_PII=false, STRUCTURED_LOGS=true, GCP_LOGGING_ENABLED
Billing STRIPE_PUBLIC_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
ML / encryption HF_TOKEN, HF_REPO_ID, GCP_KMS_*, GCP_SERVICE_ACCOUNT_JSON
CVE pipeline SCANNER_SERVICE_URL, CPE_GUESSER_URL, FORJD_API_URL, CVE_DICT_DB_URL
Dark web OSINT TOR_PROXY_URL=socks5h://FORJD OSINT routing.internal:9050

CORS example (production):

CORS_ALLOWED_ORIGINS=https://deml.app,https://dataengineeringformachinelearning.com,https://backend.deml.app,https://backend.dataengineeringformachinelearning.com
CSRF_TRUSTED_ORIGINS=https://deml.app,https://dataengineeringformachinelearning.com,https://backend.deml.app,https://backend.dataengineeringformachinelearning.com

3. FORJD streaming engine (exclusive data plane)

FORJD is the exclusive universal secure streaming engine. DEML does not run a local message broker. Production path:

Angular (Vercel) → Django BFF (Fly) → FORJD API (fjsvc_ + sealed envelopes) → Supabase / Dragonfly / engine

Concern Owner
Product UI Vercel Angular (deml.app)
Auth termination, billing, consent, learning, BFF adapters Fly deml-backend (Django)
Sealed ingest, workflows, projections, analytics, ML, replay/DLQ FORJD on Fly + Supabase + Dragonfly
End-user identity Firebase Auth (tokens terminate at Django)

Required Django secrets (never commit plaintext):

  • FORJD_API_URL (e.g. https://backend.forjd.co)
  • FORJD_SERVICE_TOKEN via secret ref (tenant-bound opaque fjsvc_)
  • Account → FORJD tenant mapping via map_forjd_tenant

Angular never calls FORJD directly. Body/query tenant_id must equal the mapped tenant or fail closed. If FORJD is unavailable, steady data-plane calls return typed degraded 503 responses while the DEML control plane remains available; empty envelopes exist only during explicit off or dual cutover modes. See docs/FORJD_INTEGRATION.md and docs/PRODUCTION_DEPLOY.md.

4. Django BFF adapters (not a local projection worker)

DEML does not run a local topic consumer or projection worker. Django adapts established Angular product paths to FORJD-native routes that exist (ingest, projections, analytics, replay/DLQ, vulnerabilities, exports, ML latest, tenant erase). Missing FORJD capabilities fail closed as FORJD dependencies.

  • Root Directory: /backend
  • Start Command: per Fly Dockerfile (gunicorn / uvicorn)
  • Public URL: https://backend.deml.app

4b. FORJD engine (data plane roles)

Stream processing, sealed pipeline execution, and engine roles (FORJD_ROLE) run in the FORJD repository and Fly apps—not as DEML Cloud Run broker sidecars. Django remains authoritative for authentication lifecycle, billing, consent, learning, and BFF adaptation. Operators deploy and monitor FORJD separately; DEML only needs HTTPS reachability and a valid fjsvc_.

5. DEML background tasks (control plane only)

DEML may still run Django management tasks for billing reconciliation, retention of identity-adjacent hot data, and KMS/key rotation. These tasks must not ingest, project, or analyze sealed telemetry—that work belongs to FORJD.

  • Root Directory: /backend
  • Variables: Core backend bundle (DATABASE_URL, SECRET_KEY, DEBUG=False), FORJD_API_URL, Stripe, Firebase, KMS

Shared environment bundle

Control-plane services inherit configuration from deml-backend settings. Use Fly secrets or Infisical to avoid drift. Centralized reads go through backend/utils/env.py.

Event flow (for operators):

  1. Angular seals an envelope and calls Django with a Firebase JWT.
  2. Django verifies the JWT, resolves account → forjd_tenant → secret_ref, and forwards to FORJD POST /api/v1/ingest with Authorization: Bearer fjsvc_….
  3. FORJD materializes durable projections; Angular reads product paths via Django BFF adapters.
  4. Replay/DLQ and analytics live in FORJD; DEML does not host a parallel broker or OLAP cluster.

Firebase: Auth (and optional Hosting for marketing) deploy via Firebase workflows. Product telemetry uses the Django BFF → FORJD HTTPS sealed path—not a raw broker TCP proxy and not a Firebase-to-FORJD trust bridge.

Marketing Site (not a Cloud Run service)

Hosted separately (Firebase Hosting / static). Build with the same URL trio:

Variable Example
FRONTEND_URL https://deml.app
BACKEND_URL https://backend.deml.app
MARKETING_URL https://dataengineeringformachinelearning.com

See marketing/.env.example. Legacy PUBLIC_MAIN_APP_URL / PUBLIC_API_BASE still work but are deprecated.

7. FORJD analytics (data plane, not a DEML service)

OLAP-style analytics, traces, and durable projections are owned by FORJD. DEML does not deploy a local columnar analytics cluster.

8. OpenTelemetry Collector (Retired — historical Cloud Run note)

Do not deploy. Product telemetry no longer uses an OpenTelemetry Collector → ClickHouse (or "FORJD analytics" TCP) path. The sealed FORJD ingest lane replaced that topology; see Chapter 34.

The bullets below are retained only so alternate-host cost models and old runbooks remain interpretable:

  • Former root: /infrastructure/otel-collector (OTLP HTTP :4318)
  • Former public hostname: telemetry.deml.app / deml-telemetry-collector.internal
  • Replacement: Angular/API sealed envelopes → Django BFF → FORJD /api/v1/ingest with tenant-bound fjsvc_
  • Ops: docs/FORJD_INTEGRATION.md

9. Vulnerability Scanning (FORJD)

Vulnerability and technology scanning runs in FORJD, not as a DEML microservice. Domain scanners are FORJD-owned, tenant-scoped tables (FORJD sql/011012), and the FORJD engine deploys from the FORJD repository — there is no /infrastructure/scanner service in this repository. DEML surfaces scanner findings through the Django BFF adapters (backend/forjd/) using the tenant-bound fjsvc_ token; see docs/FORJD_INTEGRATION.md.

10. CPE Indexer and Rust Lookup Service

The CPE plane converts raw technology strings into CPE 2.3 identifiers. A pinned Python package runs only as an offline importer, downloading the NVD dictionary and materializing its word/rank index into Dragonfly database 8. The request-time FORJD CPE lookup service is the Rust cpe role; it executes one atomic Lua ranking operation per lookup and returns the scanner-compatible { "cpe_2_3": ... } contract.

  • Source: GitHub repository (main branch)
  • Root Directory: /infrastructure/cpe-guesser
  • Importer: infrastructure/cpe-guesser/Dockerfile (pinned commit, one-shot job)
  • Runtime: FORJD CPE lookup capability (engine role in FORJD; not a DEML broker)
  • Target Port: 8080
  • Private Internal DNS: FORJD CPE lookup.internal
  • Public URL: None (Strictly an internal service)
  • Compute Limits: 1 vCPU / 1 GB Memory
  • Deployment Trigger: Auto-deploys when changes are pushed to GitHub.
  • Environment Variables: CPE_REDIS_URL=redis://Dragonfly (FORJD).internal:6379/8

The importer must complete successfully before the Rust service becomes ready. Dictionary refreshes do not restart or block request-serving replicas.

11. Tor Proxy (Dark Web Scanner)

A lightweight proxy that allows the security worker to anonymously scrape dark web search engines (e.g., Ahmia) for brand mentions.

  • Source: GitHub repository (main branch)
  • Root Directory: /infrastructure/tor-proxy
  • Builder: Dockerfile (Minimal alpine image running as non-root tor user)
  • Target Port: 9050
  • Private Internal DNS: FORJD OSINT routing.internal
  • Environment Variables: None on the proxy itself.

Consumers (deml-backend, deml-security-worker) must set:

TOR_PROXY_URL=socks5h://FORJD OSINT routing.internal:9050

13. Dragonfly (Redis Replacement for WebSockets)

This service provides the in-memory pub/sub message broker required by Django Channels to route real-time WebSocket traffic. We use a custom, highly secure distroless image to minimize the attack surface.

  • Source: GitHub repository (main branch)
  • Root Directory: /infrastructure/dragonfly
  • Builder: Dockerfile (Multi-stage build using Google Distroless cc-debian12:nonroot)
  • Target Port: 6379
  • Private Internal DNS: Dragonfly (FORJD).internal
  • Public URL: None (Strictly an internal service)
  • Environment Variables: None required by default.

(Once deployed, set DRAGONFLY_HOST=Dragonfly (FORJD).internal on deml-backend, all workers, and any service using Channels or rate limiting.)

Internal Networking

All inter-service traffic uses private mesh DNS where applicable. Never expose database or cache traffic over public URLs; FORJD is reached only over HTTPS with fjsvc_.

Service Internal address
Backend API deml-backend.internal:8080
Frontend deml-frontend.internal:8080
Postgres Via DATABASE_URL (internal connection string from Cloud Run)
FORJD API https://backend.forjd.co (HTTPS + fjsvc_; no DEML-local :8123 analytics DSN)
Dragonfly FORJD-owned Redis-protocol cache (not a DEML service)
Scanner / CPE FORJD-owned surfaces (proxied via Django BFF)

Local Development (docker-compose.yml)

Local parity focuses on the Django control plane, Postgres, and calls to a running FORJD API (local or backend.forjd.co). DEML does not compose ClickHouse, Redpanda, or an OTel Collector for product telemetry (Chapter 34). Copy the environment examples and use localhost overrides:

FORJD_API_URL=https://backend.forjd.co
DRAGONFLY_HOST=dragonfly
FRONTEND_URL=http://localhost:4200
BACKEND_URL=http://localhost:8000
MARKETING_URL=http://localhost:4321
TOR_PROXY_URL=socks5h://tor-proxy:9050

Run docker compose up from the repo root. Frontend: cd frontend && npm start. Marketing: cd marketing && npm run dev.

Updating Environment Variables

  1. Prefer setting in GCP dashboard (Variables tab per service) or via CLI.
  2. After changing build-time vars (MARKETING_URL, BACKEND_URL, FIREBASE_*) for frontend, trigger a new deploy so set-env.js runs.
  3. Keep backend/.env.example, frontend/.env.example, and marketing/.env.example in sync with reality.

Security Notes

  • Never commit real .env.
  • Secrets (Stripe, Resend, Firebase SA, KMS, HF) should use Cloud Run secret variables or Infisical integration.
  • CORS/CSRF lists are the primary control for cross-origin auth handoff.

See also: backend/.env.example, frontend/.env.example, marketing/.env.example, BOOK.md (sealed FORJD projections chapter), and AGENTS.md (CORS rule: never hardcode domains).

Cloud Run CLI Quick Reference

gcloud config set project
gcloud run services update deml-backend --set "MARKETING_URL=https://dataengineeringformachinelearning.com"
gcloud run services update deml-frontend --set "SENTRY_DSN=<your-dsn>"
gcloud run services update deml-backend

After any build-time variable change on deml-frontend, trigger a redeploy.

CI/CD Pipeline

  • All services are linked to the main branch of the dataengineeringformachinelearning repository.
  • Pushes to the main branch will automatically trigger new builds and deployments for the affected services.
  • Automated security testing via Socket.dev and Checkov pre-commit hooks runs on every push.
  • Watch Paths: You can set gitignore-style rules (e.g., /frontend/** or /backend/**) in the Cloud Run settings to ensure that a service only rebuilds when its specific directory changes.

Reliability and Scaling

  • Restart Policy: All services are configured to restart "On Failure" with a maximum of 10 retries, ensuring automatic recovery from temporary crashes.
  • Region: US East (Virginia, USA)
  • Replicas: 1 replica per service.