Cloud Run Deployment (Alternate Host)
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:
- 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. - 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 |
- CORS / CSRF must list every public origin (app + marketing + backend + local dev). Copy from
backend/.env.exampleand extend for your domains. - Production guards:
DEBUG=False, uniqueSECRET_KEY. On Cloud Run, the backend fails fast if these are insecure (backend/utils/env.py). - Privacy defaults:
SENTRY_SEND_PII=false,STRUCTURED_LOGS=true(JSON logs with correlation IDs).
How to Deploy in One Project
- Create a New Project: GCP dashboard → New Project → Empty Project (name:
deml). - Add Postgres: New → Database → PostgreSQL. Note the internal
DATABASE_URL. - Add Services: For each component below, New Service → GitHub Repo → select this repository.
- 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.
- Variables tab: Set env vars per service (see per-service sections). Workers share nearly the same bundle as
deml-backend. - Redeploy frontend after changing build-time vars (
FRONTEND_URL,BACKEND_URL,MARKETING_URL,FIREBASE_*,SENTRY_DSN) soset-env.jsregeneratesenvironment.ts. - Marketing site (Astro) is hosted outside this Cloud Run project (Firebase Hosting). Set the same URL trio at build time on that host.
- 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.
- Set up an Infisical organization and create a project for
dataengineeringformachinelearning. - Connect your Cloud Run services to Infisical via the official Cloud Run Infisical Integration.
- 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-debian12runtime; multi-stage fromnode: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.jsruns at deploy and writessrc/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_TOKENvia secret ref (tenant-bound opaquefjsvc_)- 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):
- Angular seals an envelope and calls Django with a Firebase JWT.
- Django verifies the JWT, resolves
account → forjd_tenant → secret_ref, and forwards to FORJDPOST /api/v1/ingestwithAuthorization: Bearer fjsvc_…. - FORJD materializes durable projections; Angular reads product paths via Django BFF adapters.
- 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.
- Product path: Angular → Django BFF → FORJD analytics/projection APIs with
fjsvc_ - Ops: docs/FORJD_INTEGRATION.md, docs/PRODUCTION_DEPLOY.md
- DEML only needs
FORJD_API_URL+ tenant-boundFORJD_SERVICE_TOKEN(secret ref)
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/ingestwith tenant-boundfjsvc_ - 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/011–012), 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 (
mainbranch) - 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 (
mainbranch) - Root Directory:
/infrastructure/tor-proxy - Builder: Dockerfile (Minimal
alpineimage running as non-roottoruser) - 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 (
mainbranch) - 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
- Prefer setting in GCP dashboard (Variables tab per service) or via CLI.
- After changing build-time vars (
MARKETING_URL,BACKEND_URL,FIREBASE_*) for frontend, trigger a new deploy soset-env.jsruns. - Keep
backend/.env.example,frontend/.env.example, andmarketing/.env.examplein 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
mainbranch of thedataengineeringformachinelearningrepository. - Pushes to the
mainbranch 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.