Skip to content

Environments & Infrastructure

Three environments — Dev, Test, Prod (plus local). Each maps to a branch in both repos. Verified against commit history on 2026-06-24.

Environment Frontend — branch → host Backend — branch → API
Dev dev → dev.consumr.ai dev → dev-api.consumr.ai
Test test → test.consumr.ai test → test-api.consumr.ai
Prod main → app.consumr.ai app → app-api.consumr.ai (api.consumr.ai = legacy)
Local feature branch → localhost dev-api / 127.0.0.1:5001
📎 Evidence
  • Dev FE branch is devpw-enterprise-frontend git — git branch -a shows remotes/origin/dev; a Dev workflow exists in .github/workflows/dev.yml. ✅
  • Dev FE host dev.consumr.aipw-enterprise-frontend/src/api/config.js:144-152 — hostname branch sets url = dev, environment = 'dev'. ✅
  • Dev BE branch is devpw-enterprise git — remotes/origin/dev exists; backend .github/workflows/dev.yml named dev. ✅
  • Dev BE API dev-api.consumr.aipw-enterprise-frontend/src/api/config.js:18const dev = 'https://dev-api.consumr.ai' assigned when hostname is dev.consumr.ai. ✅
  • Test FE branch is testpw-enterprise-frontend git — git branch -a shows remotes/origin/test exists. (Finder wrongly marked this unverifiable; the branch IS present.) ✅
  • Test FE host test.consumr.aipw-enterprise-frontend/src/api/config.js:135-143 — hostname branch sets url = test, environment = 'test'. ✅
  • Test BE branch is testpw-enterprise git — remotes/origin/test exists. Note .github/workflows/test.yml is an API-test workflow targeting alpha, NOT a test-env deploy. ✅
  • Test BE API test-api.consumr.aipw-enterprise-frontend/src/api/config.js:19,136const test = 'https://test-api.consumr.ai' assigned when hostname is test.consumr.ai. ✅
  • Prod FE branch is mainpw-enterprise-frontend git — git symbolic-ref refs/remotes/origin/HEAD = refs/remotes/origin/main; Main workflow runs main-action.sh. ✅
  • Prod FE host app.consumr.aipw-enterprise-frontend/src/api/config.js:127-134 — hostname branch sets url = app, environment = 'app'. ✅
  • Prod BE branch is apppw-enterprise git — git symbolic-ref refs/remotes/origin/HEAD = refs/remotes/origin/app; local * app. ✅
  • Prod BE API app-api.consumr.aipw-enterprise-frontend/src/api/config.js:17,129const app = 'https://app-api.consumr.ai' assigned under app.consumr.ai. ✅
  • api.consumr.ai is legacy (not active)pw-enterprise-frontend/src/api/config.js:15 declares const api = 'https://api.consumr.ai' but no hostname branch assigns it to url; active prod is app-api.consumr.ai. Table now marks it legacy. ✅
  • Local FE = feature branch → localhostpw-enterprise-frontend/src/api/config.js:103-125 — code routes localhost/local.consumr.ai hostnames; the ‘feature branch’ part is convention, not code-enforced. ⚠️
  • Local BE = dev-api / 127.0.0.1:5001pw-enterprise-frontend/src/api/config.js:104,118local.consumr.ai defaults to 127.0.0.1:5001 (via VITE_API_URL); plain localhost sets url = dev. ✅
  • Promotion flow: dev → test → prod (visible in git history — test is fed by merges from dev).
  • Environment selection: the frontend resolves its environment at runtime by hostname (src/api/config.js), not a build flag.
  • Deploys: manual — GitHub Actions workflow_dispatch on a self-hosted runner running shell scripts (dev-action.sh / main-action.sh for FE; pw-enterprise-dev-server.sh / pw-enterprise-main-server.sh for BE). The frontend dev deploy also purges the Cloudflare cache (the prod main.yml has no purge job).
📎 Evidence
  • Promotion dev → test → prodpw-enterprise git merge log on test — shows merges of app INTO test and PRs #9841 (I5216) etc.; a literal merge dev into test was not seen. Flow is convention-supported, not a hard pipeline. ⚠️
  • Env resolved at runtime by hostnamepw-enterprise-frontend/src/api/config.js:4,103-160 — reads window.location.hostname then if/else-if chain sets url/env/langgraph; only VITE_API_URL overrides local URL. ✅
  • Manual deploys via workflow_dispatchpw-enterprise/.github/workflows/dev.yml:2, Main.yml:3-4, pw-enterprise-frontend/.github/workflows/dev.yml:2, main.yml:2 — all use on: workflow_dispatch. ✅
  • Self-hosted runnerpw-enterprise/.github/workflows/dev.yml:9 & Main.yml:8, pw-enterprise-frontend/.github/workflows/dev.yml:9 & main.yml:9 — all jobs declare runs-on: self-hosted. ✅
  • FE dev deploy runs dev-action.shpw-enterprise-frontend/.github/workflows/dev.yml:11-15cd /home/coder/rushab/; time ./dev-action.sh. Script lives on the runner, not in repo. ✅
  • FE prod deploy runs main-action.shpw-enterprise-frontend/.github/workflows/main.yml:11-15cd /home/coder/rushab/; time ./main-action.sh. ✅
  • BE dev deploy runs pw-enterprise-dev-server.shpw-enterprise/.github/workflows/dev.yml:11-15cd /home/coder/rushab/; time ./pw-enterprise-dev-server.sh. ✅
  • BE prod deploy runs pw-enterprise-main-server.shpw-enterprise/.github/workflows/Main.yml:10-14cd /home/coder/rushab/; time ./pw-enterprise-main-server.sh. ✅
  • FE cache purge is dev-onlypw-enterprise-frontend/.github/workflows/dev.yml:18-26 runs purge_profitwheel_cache.sh after deploy; main.yml (prod) has no purge job. Doc now scopes this to dev. ✅
📎 Evidence
  • Stale production branch exists (BE)pw-enterprise git — git branch -a lists remotes/origin/production. ✅
  • production last commit April 2024pw-enterprise git — git log -1 remotes/origin/production = 2024-04-11 17:51:49 ‘Added tracing for answer engine’. ✅
  • Real prod branch is app (default)pw-enterprise git — git symbolic-ref refs/remotes/origin/HEAD = refs/remotes/origin/app; local checkout shows * app. ✅

Verified against the VMs on 2026-06-25 (GCP Cloud Console SSH, read-only).

Two GCP Compute Engine VMs run all three environments. There is no separate test VM — test and production are co-located on pw-prod as two checkouts.

VM Cloud Environments Backend checkout(s)
pw-dev GCP — project still-primer-308606, zone us-east1-c Dev /home/coder/served/pw-enterprise (live)
pw-prod GCP Compute Engine Test and Production test → /opt/test/pw-enterprise, prod → /opt/production/pw-enterprise

Both run Ubuntu 22.04.5 LTS on GCP kernels (6.8.0-1044-gcp). A separate external API service runs on pw-prod at /home/coder/rushab/pw-external-api. Ingress on both is Apache 2.4.52 fronting the apps, with public exposure via a Cloudflare Tunnel (cloudflared) — not a public load balancer. The React frontend is hosted separately on Cloudflare Pages, not on these VMs.

📎 Evidence
  • Two VMs, GCP — both prompts (navdeep_singh@pw-dev, navdeep_singh@pw-prod); uname -r = 6.8.0-1044-gcp on both; grep PRETTY_NAME /etc/os-release = Ubuntu 22.04.5 LTS. ✅
  • pw-dev project/zone — GCP Cloud Console SSH URL: .../projects/still-primer-308606/zones/us-east1-c/instances/pw-dev. ✅ (pw-prod’s project/zone not captured; GCP confirmed via -gcp kernel + google-cloud-ops-agent apt source.)
  • Test + prod co-located on pw-prodsudo find /var/www /home /opt /srv -maxdepth 5 -name uv.lock on pw-prod → /opt/test/pw-enterprise/uv.lock, /opt/production/pw-enterprise/uv.lock, /home/coder/rushab/pw-external-api/uv.lock. No separate pw-test host was accessed.
  • Live dev checkout = /home/coder/served/pw-enterprise — confirmed 2026-06-25; the /home/coder/pw-enterprise directory we first sampled is a separate, abandoned checkout (old pre-1.x deps) and is not what the dev service serves. 🔍 direct confirm: sudo readlink /proc/<dev-gunicorn-pid>/cwd. ✅
  • Apache 2.4.52 ingressapache2 -v on both = Server version: Apache/2.4.52 (Ubuntu); sudo ss -tlnp shows apache2 LISTEN on :80 and :443. nginx is not installed (nginx command not found). ✅
  • Public exposure via Cloudflare Tunnelsudo ss -tlnp shows cloudflared listening locally (pw-dev :46487, pw-prod :20241); /etc/apt/sources.list.d/cloudflared.list present. ✅
  • Frontend on Cloudflare Pages (not the VMs)pw-enterprise-frontend/wrangler.toml:3 (pages_build_output_dir); no frontend build is served from Apache on these boxes. ✅

Inventory completed 2026-06-25 from VM access (previously “pending access”).

GCP (primary): Compute Engine (the pw-dev / pw-prod VMs), Cloud SQL (managed Postgres reached via cloud-sql-proxy on pw-dev), Secret Manager, BigQuery, and the Cloud Ops / OpenTelemetry agent stack. Cloudflare: Pages (frontend), Workers (edge bindings AI, Vectorize ×3, Hyperdrive), Cloudflare Tunnel (VM ingress), and R2 object storage (server-side via boto3, not a Worker binding). Azure: legacy — the app has migrated off (Key Vault → GCP Secret Manager, Blob → R2), but Azure monitoring agents are still running on both VMs (amacoreagent, change-tracking).

Service / tech pw-dev pw-prod (test + prod)
Web server (ingress) Apache 2.4.52 Apache 2.4.52
Public ingress Cloudflare Tunnel Cloudflare Tunnel
App servers gunicorn (:8080), Flask (:2096, :5000) gunicorn ×2 clusters (:8445, :8450), FastAPI (:8000)
PostgreSQL local ×2 (:5432 = 12.12, :5433) local ×2 (:5432 = 12.12, :5433)
Managed DB GCP Cloud SQL via cloud-sql-proxy (:5434) — (no Cloud SQL proxy)
MySQL local 8.0.46 (:3306) local 8.0 (:3306)
Redis local 8.8.0 (:6129) local 6.0.16 (:6129, bound to VPC IP 10.0.0.4)
RabbitMQ 3.9.27 (:5672 AMQP) — (no RabbitMQ)
Job queue UI RQ Dashboard (:9181) RQ Dashboard (:9181)
Observability full OpenTelemetry Collector (OTLP/Jaeger/Zipkin) + GCP Ops + Fluent Bit + Datadog/Sentry GCP Ops (otelopscol) + Fluent Bit + Sentry
Legacy agents Azure Monitor (amacoreagent), Defender (mdatp) Azure Monitor (amacoreagent), change-tracking

Elasticsearch is remote — it’s a backend dependency but is not listening on either VM (:9200 absent in ss -tlnp), so it’s a managed/remote cluster.

📎 Evidence
  • Running servicessudo ss -tlnp on pw-dev and pw-prod (2026-06-25). pw-dev: apache2 80/443, cloudflared, mysqld 3306, postgres 5432/5433, cloud-sql-proxy 5434, redis-server 6129, RabbitMQ (beam.smp/epmd 5672/4369/25672), gunicorn 8080, flask 2096 + python 5000, rq-dashboard 9181, full otelcol (4317/4318/9411/14268…), otelopscol, fluent-bit, amacoreagent. pw-prod: apache2 80/443, cloudflared, fastapi 8000, gunicorn 8445 + 8450, mysqld 3306, postgres 5432/5433, redis-server on 10.0.0.4:6129, rq-dashboard 9181, containerd, otelopscol, fluent-bit, amacoreagent. ✅
  • Postgres server 12.12sudo -u postgres psql -tc 'select version();' on both = PostgreSQL 12.12 (Ubuntu 12.12-0ubuntu0.20.04.1). ✅
  • Redis server versions differ across VMsredis-server --version: pw-dev = Redis server v=8.8.0, pw-prod = Redis server v=6.0.16. (Dev runs Redis 8.x; prod runs Redis 6.x.) ✅
  • RabbitMQ dev-onlysudo rabbitmqctl version on pw-dev = 3.9.27; pw-prod ss -tlnp shows no AMQP/beam.smp listener. Likely the Airflow/Celery broker (Airflow runs on pw-dev: ~coder/airflow/, airflows_monitor_dev.log); no app call-site verified. ⚠️
  • Cloud SQL is dev-only in what we sawcloud-sql-proxy LISTEN on pw-dev :5434; absent on pw-prod. ✅
  • MySQL 8.0.46mysql --version on both = 8.0.46-0ubuntu0.22.04.3. ✅
  • Elasticsearch remote:9200 absent from ss -tlnp on both VMs; client dep present in uv.lock (see Tech Stack). ✅
  • Azure agents still running (legacy)amacoreagent (Azure Monitor Agent) + change_tracking LISTEN on both VMs; matches the app’s Azure→GCP/Cloudflare migration documented in library/storage.py / library/secrets.py. ✅
flowchart TB
U["Users / browser"] --> CFE["Cloudflare edge"]
CFE --> PAGES["Cloudflare Pages<br/>React 19 SPA (frontend)"]
CFE --> TUN["Cloudflare Tunnel<br/>(VM ingress — no public LB)"]

subgraph DEV["VM: pw-dev · GCP us-east1-c · DEV"]
  AP1["Apache 2.4.52"] --> GU1["gunicorn :8080 + Flask"]
  GU1 --> PG1[("PostgreSQL 12.12 ×2")]
  GU1 --> MY1[("MySQL 8.0.46")]
  GU1 --> RD1[("Redis 8.8.0")]
  AF1["Airflow + RabbitMQ 3.9.27<br/>(dev only)"]
  CS1["cloud-sql-proxy → GCP Cloud SQL<br/>(dev only)"]
  GU1 -.-> CS1
end

subgraph PROD["VM: pw-prod · GCP · TEST + PROD co-located"]
  AP2["Apache 2.4.52"] --> GU2["gunicorn :8445 + :8450"]
  AP2 --> FA["FastAPI :8000"]
  GU2 --> TST["/opt/test<br/>langchain 1.3.11"]
  GU2 --> PRD["/opt/production<br/>langchain 1.2.7"]
  GU2 --> PG2[("PostgreSQL 12.12 ×2")]
  GU2 --> MY2[("MySQL 8.0")]
  GU2 --> RD2[("Redis 6.0.16")]
end

TUN --> AP1
TUN --> AP2
ES[("Elasticsearch<br/>remote / managed")]
GU1 -.-> ES
GU2 -.-> ES

Two GCP VMs, three environments. The frontend is served from Cloudflare Pages; backend ingress is the Cloudflare Tunnel → Apache → gunicorn/FastAPI. RabbitMQ and the Cloud SQL proxy are dev-only; Elasticsearch is remote. (Evidence: same ss -tlnp / find uv.lock / version captures cited above.)

📎 Evidence
  • GCP Secret Manager (primary)pw-enterprise/library/gcp_secrets.py:1-18 & pyproject.toml:101 — imports google.cloud.secretmanager; SDK google-cloud-secret-manager>=2.26.0 declared (line 101, not 100). ✅
  • GCP BigQuerypw-enterprise/api/common.py:4,11 & pyproject.toml:100from google.cloud import bigquery; client = bigquery.Client(); SDK google-cloud-bigquery>=3.39.0 declared. ✅
  • Azure Key Vault is legacypw-enterprise/library/gcp_secrets.py:3-5 (docstring ‘replacing Azure Key Vault’), library/secrets.py:109-116 (*_key_vault fns delegate to GCP), pyproject.toml:27-28, .env:91-93 (AZURE_* keys empty). ✅
  • Cloudflare Pages (FE hosting)pw-enterprise-frontend/wrangler.toml:3pages_build_output_dir = "dist" is the Cloudflare Pages config key. ✅
  • Cloudflare Workerspw-enterprise-frontend/src/api/config.js:13 (a *.profitwheel.workers.dev worker URL) & pw-enterprise/pyproject.toml:31 (cloudflare>=4.3.1 SDK). ✅
  • Workers AI bindingpw-enterprise-frontend/wrangler.toml:13-14,22-23[ai] binding = "AI" for both default and [env.production]. ✅
  • Vectorize binding (×3 indices)pw-enterprise-frontend/functions/_middleware.ts:11-13 and worker-configuration.d.ts:9-11 declare VECTORIZE_INDEX_SMALL/BASE/LARGE: VectorizeIndex in the Worker Env; configured via the Pages dashboard (only AI is declared in wrangler.toml), which is why wrangler.toml alone doesn’t list it. ✅
  • Hyperdrive bindingpw-enterprise-frontend/functions/_middleware.ts:14 (PRODDB: Hyperdrive) and worker-configuration.d.ts:12 (DEVDB: Hyperdrive) declare it in the Worker Env; configured via the Pages dashboard, not wrangler.toml. ✅
  • Cloudflare R2 (object storage, server-side)pw-enterprise/library/storage.py:1-7,32-37 builds a boto3 S3 client against *.r2.cloudflarestorage.com; .env:144-148 define the R2 keys. Used server-side, not as a Worker edge binding. ✅
📎 Evidence
  • Inventory completed from VM accesssudo ss -tlnp, sudo find … -name uv.lock, apache2 -v, redis-server --version, sudo rabbitmqctl version, sudo -u postgres psql -tc 'select version()', and the per-checkout uv.lock extracts, all run on pw-dev and pw-prod on 2026-06-25. Raw capture: docs/vm-tech-inventory.md. ✅
  • Open items — deploy-script contents, dev serving-path confirmation, and edge/network config were explicitly not gathered in this pass. 🔍 needs: one more read-only session (read the scripts; sudo ss -tnp | grep <gunicorn pid> working dir via /proc/<pid>/cwd). ✅