Skip to main content

Deployment Infrastructure

Last verified: 2026-05-03 | Committed routing sources: equa-web/nginx.conf, equa-web/Dockerfile, equa-web/webpack.config.js, equa-web/.github/workflows/equa-cc-domain-smoke.yml | Legacy deployment source: equa-server/cloudbuild.yaml | Live edge capture: 2026-05-03T16:09:41Z against equa-web commit 3131f1699ae9df3dcf3655611bc8fb247afbb33e

Deployment History Timeline

Era 1: AWS (Prior Deployment)

Unconfirmed — Requires Google Drive/Confluence AuditThe Equa platform was previously deployed on AWS. Evidence of AWS services that remain active in the codebase:
  • AWS S3 — File storage via aws-sdk/clients/s3 in equa-server/modules/file-storage/src/s3.d.ts
  • AWS SES — Email sending via equa-server/modules/notifications/src/nodemailer/email-notifier.ts
The full AWS deployment architecture (compute service, database, networking, and migration details) is documented in Google Drive and Confluence but has not yet been audited or exported locally. This section will be completed after that audit.Items to confirm:
  • Compute: EC2, ECS, Elastic Beanstalk, or Lambda?
  • Database: RDS PostgreSQL instance details
  • Networking: VPC, load balancer, SSL certs
  • Migration timeline: When and why moved to GCP

Era 2: GCP Cloud Run / Public Cloud Legacy

The GCP-era public deployment still exists in repository automation. equa-server/cloudbuild.yaml deploys equa-backend to Cloud Run in us-central1 on port 3000, which remains relevant for cost wind-down and legacy endpoint investigations. It is no longer the authoritative browser-routing story for the SPA host. Source: equa-server/cloudbuild.yaml, Lines: 13-46

Era 3: Railway Interim / Local-First

The current committed interim routing story lives in equa-web/nginx.conf: equa.cc and www.equa.cc return 301 to https://app.equa.cc$request_uri, the default server block accepts the active SPA host, and /api/ proxies to https://equa-server-so-production.up.railway.app/. The container defaults PORT=8080 and exposes 8080, so Railway target-port parity remains the first check if the edge regresses to 502. Sources:
  • equa-web/nginx.conf, Lines: 4-29
  • equa-web/Dockerfile, Lines: 44-49

Era 4: Spec 040 Target

Spec 040 still targets equa.cc as the canonical marketing + app hostname with app.equa.cc eventually redirecting back to apex. Treat that as target-state planning, not as a claim about the live edge today. Source: command-center-so/specs/040-equa-cc-landing-rebuild/spec.md, Section: 3.1

Routing Truth Layers

Committed_interim

As committed today, equa-web defines two nginx server blocks. One block catches equa.cc and www.equa.cc and issues a 301 to https://app.equa.cc$request_uri; the other block serves the SPA, proxies /api/ to https://equa-server-so-production.up.railway.app/, and inherits ${PORT} from the runtime environment. This is the authoritative routing story for documentation until the repo changes. Sources:
  • equa-web/nginx.conf, Lines: 4-29
  • equa-web/Dockerfile, Lines: 44-49

Live_edge_baseline

The live edge must be treated as a separate measurement layer. The following snapshot was captured at 2026-05-03T16:09:41Z against equa-web commit 3131f1699ae9df3dcf3655611bc8fb247afbb33e; re-run it before any DNS, OAuth, or cutover decision.
ProbeObserved resultWhy it matters
https://equa.cc/HTTP/2 301 to https://app.equa.cc:443/, then HTTP/2 200 with server: railway-edgeApex live behavior now follows the committed interim redirect intent and reaches the app host.
dig equa.cc @8.8.8.8A 136.110.187.76Apex DNS still resolves to the legacy Google-era address before redirecting to the Railway-backed app host.
https://www.equa.cc/HTTP/2 301 to https://app.equa.cc:443/, then HTTP/2 200 with server: railway-edgewww now resolves and follows the same redirect path as apex.
dig www.equa.cc @8.8.8.8A 136.110.187.76www is no longer NXDOMAIN, but still uses the legacy Google-era A record before redirect.
https://app.equa.cc/HTTP/2 200 with server: railway-edge and Fastly cache headersThe app host reaches Railway and serves the SPA shell.
dig app.equa.cc @8.8.8.8CNAME ry6le6wy.up.railway.app.151.101.2.15app.equa.cc is the Railway-backed live host today.
https://app.equa.cc/api/v1/user/currentHTTP 200, body {}Same-origin API traffic reaches the Railway-backed API route; this validates the browser path shape, not authenticated application semantics.
https://api.equa.cc/HTTP/2 404 with via: 1.1 googleThe direct API host remains legacy/unverified; browser documentation should prefer same-origin https://app.equa.cc/api/v1.
Use this table to distinguish live behavior from committed intent. The runbook explains how to refresh each row and how to prepend captured_at plus equa_web_commit headers when pasting evidence into a PR or ticket. Source: equa-web/specs/037-docs-deploy-truth/VALIDATION-RUNBOOK.md, Sections: 1-5

Legacy_GCP_notes

The repository still contains the previous Cloud Run deploy path for equa-backend. Keep that material for cost, rollback, and historical architecture context only. Do not describe Cloud Run as the sole current browser/API path unless a direct endpoint is re-verified and explicitly labeled as an alternate endpoint. Source: equa-server/cloudbuild.yaml, Lines: 13-46

Target_Spec040

Spec 040 is still the cutover target: the marketing site and authenticated SPA converge on canonical equa.cc, and app.equa.cc becomes the redirect. That plan remains separate from the interim nginx story and separate from the live-edge baseline above. Source: command-center-so/specs/040-equa-cc-landing-rebuild/spec.md, Section: 3.1

Verifying Edge Behavior

For operator captures, use the read-only validation runbook in equa-web rather than copying shell snippets out of this page. GitHub permalink: Spec 037 validation runbook

Legacy GCP Cloud Run Reference

Cloud Build Pipeline

Source: equa-server/cloudbuild.yaml
steps:
  # 1. Build Docker image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/equa-backend:$COMMIT_SHA', '.']

  # 2. Push to Google Container Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/equa-backend:$COMMIT_SHA']

  # 3. Deploy to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'equa-backend', ...]

Cloud Run Service Configuration

PropertyValueSource
Service nameequa-backendcloudbuild.yaml line 19
Regionus-central1cloudbuild.yaml line 23
Imagegcr.io/$PROJECT_ID/equa-backend:$COMMIT_SHAcloudbuild.yaml line 7
Port3000cloudbuild.yaml line 29
Memory1Gicloudbuild.yaml line 30
CPU1cloudbuild.yaml line 31
Min instances1cloudbuild.yaml line 32
Max instances10cloudbuild.yaml line 33
Timeout300scloudbuild.yaml line 34
Access--allow-unauthenticatedcloudbuild.yaml line 27
FeaturesStartup CPU boost, CPU throttlingcloudbuild.yaml lines 39-40
LoggingCLOUD_LOGGING_ONLYcloudbuild.yaml line 46
Unconfirmed — Requires GCP Console Access
  • GCP Project ID ($PROJECT_ID in cloudbuild.yaml)
  • Cloud SQL instance details (type, tier, version, connection method)
  • Load balancer name and configuration specifics
  • Static IP reservation details
  • Monthly cost breakdown by SKU
The GCP cost check runbook at stacks-ranking-priorities/runbooks/EQUA_CC_COST_CHECK.md identifies Cloud SQL as the #1 cost driver.

Docker Configurations

Backend: equa-server

Source: equa-server/Dockerfile
StageBase ImagePurpose
Buildnode:18-alpineInstall deps (yarn install --frozen-lockfile), compile TypeScript (tsc --build for file-storage and api modules)
Productionnode:18-alpinePM2 runtime, health check, production serving
Build dependencies: git, python3, make, g++ (for native npm module compilation) Process manager: PM2 v5 (pm2-runtime start ecosystem.config.js --only api) Health check: wget --spider http://localhost:3000/ every 30s (3s timeout, 5s start period, 3 retries) Environment variables:
VariableDefaultPurpose
PORT3000HTTP listen port
NODE_ENVproductionNode environment
DATABASE_TYPEpostgresDatabase driver
DATABASE_HOSTPostgreSQL host
DATABASE_USERNAMEDatabase user
DATABASE_PASSWORDDatabase password
DATABASE_NAMEDatabase name
API_SSLEnable HTTPS (loads certs from SSL_PRIVATE_KEY_PATH, SSL_PUBLIC_KEY_PATH)
Source: equa-server/Dockerfile (full file), equa-server/README.md (database env vars), equa-server/modules/api/src/server.ts lines 53-97 (SSL and port config).

Frontend: equa-web

Source: equa-web/Dockerfile
StageBase ImagePurpose
Buildnode:20-alpineInstall deps, run webpack production build
Productionnginx:alpineServe static files, reverse proxy API requests
Build settings:
  • NODE_OPTIONS=--openssl-legacy-provider (required for older webpack/SSL compatibility)
  • API_URL=/api/v1 (baked into the build)
  • Webpack runs via npx webpack --mode production (changed from direct node_modules/.bin/webpack in PR #501)
  • A post-install verification step checks for the webpack binary and fails fast with a clear error if missing
Required environment variables for build:
VariablePurposeSource
GH_PATTERNLIB_TOKENGitHub token for fetching equa-patternlib private dependencyPR #501
API_URLBackend API base URL (default: /api/v1)webpack.config.js
The build requires GH_PATTERNLIB_TOKEN to be set as a Railway service variable. Without it, yarn install silently fails to fetch equa-patternlib (SSH clone fails on Alpine), leaving node_modules corrupt. PR #501 added a verification step that catches this early.
Nginx configuration (equa-web/nginx.conf):
FeatureConfiguration
Listen port${PORT} (templated via envsubst at container startup; Dockerfile ENV PORT=8080 provides the fallback)
Apex redirectequa.cc and www.equa.cc return 301 to https://app.equa.cc$request_uri when this config is the active edge
API proxy/api/https://equa-server-so-production.up.railway.app/
Static files/usr/share/nginx/html with 1-year cache (Cache-Control: public, immutable)
SPA fallbackAll routes serve index.html
GzipEnabled for text, CSS, JSON, JS, XML
Health checkGET /health returns 200 OK
Dynamic PORT (PR #516): Prior to 2026-04-02, nginx.conf hard-coded listen 8080. Railway injects a $PORT env var per service instance and the hard-coded value worked only because it happened to match the Dockerfile EXPOSE. PR #516 changed the config to listen ${PORT} and runs envsubst on the template before nginx starts, so the container honours whatever port Railway assigns. Local docker run still defaults to 8080 via the Dockerfile ENV PORT=8080. Source: equa-web/Dockerfile (full file), equa-web/nginx.conf (full file), PR #516 (2026-04-02).

Railway Deployments

Four services have Railway deployment configurations:

equa-server (Railway)

Source: equa-server/railway.toml
PropertyValue
Buildernixpacks
Start commandnpm run start:api
Health check/health (timeout: 300s)
Restart policyon_failure (max 3 retries)

equa-web (Railway)

Source: equa-web/railway.toml
PropertyValue
Buildernixpacks
Start commandnpm run start
Health check/ (timeout: 100ms)
Restart policyon_failure (max 3 retries)

equa-patternlib (Railway — Storybook)

Source: equa-patternlib-nextjs/railway.toml
PropertyValue
Buildernixpacks
Build commandnpm run build-storybook
Start commandnpx http-server storybook-static -p $PORT
Health check/ (timeout: 100ms)
Restart policyon_failure (max 3 retries)

Command Center (Railway)

Source: command-center-so/railway.toml
PropertyValue
Buildernixpacks
Start commandnpm run start
Health check/command-center/api/health (timeout: 100ms)
Restart policyon_failure (max 3 retries)
Required env varsEQUABOT_GATEWAY_URL, EQUABOT_GATEWAY_TOKEN, AUTH_SECRET, AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET

DNS and Networking

Host-by-Host Baseline

HostCommitted intentLive edge on 2026-05-03Status
equa.cc301 to https://app.equa.cc$request_uriHTTP/2 301 to https://app.equa.cc:443/, then HTTP/2 200; A 136.110.187.76Live apex now redirects to the app host, while DNS still lands on the legacy Google-era A record first.
www.equa.ccSame 301 as apex when attached to the nginx serviceHTTP/2 301 to https://app.equa.cc:443/, then HTTP/2 200; A 136.110.187.76www now resolves and follows the same redirect path as apex.
app.equa.ccActive SPA host served by the default nginx blockHTTP/2 200, CNAME ry6le6wy.up.railway.app.151.101.2.15Railway-backed SPA host is serving.
www.equa.cc was intentionally a soft check in the automated smoke workflow while DNS was missing. As of the 2026-05-03T16:09:41Z capture, it resolves and redirects successfully; keep the soft-check behavior until the cutover contract is explicitly tightened.Source: equa-web/.github/workflows/equa-cc-domain-smoke.yml, Lines: 31-46

Reverse Proxy (Nginx)

In the committed interim config, equa-web’s nginx container handles both host redirect behavior and the same-origin API path:
equa.cc, www.equa.cc  →  301 https://app.equa.cc$request_uri
/api/*                 →  https://equa-server-so-production.up.railway.app/
Headers set on the backend hop: Host (equa-server-so-production.up.railway.app), X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. Source: equa-web/nginx.conf, Lines: 4-29

AWS Services (Active in Codebase)

S3 File Storage

The file-storage module uses the AWS SDK v2 for S3 operations:
FunctionPurposeSource
newS3Client(config)Create S3 client from configmodules/file-storage/src/s3.d.ts
uploadS3(s3, bucket, stream, file, contentType)Upload file to bucketmodules/file-storage/src/s3.d.ts
downloadS3(s3, bucket)Download file from bucketmodules/file-storage/src/s3.d.ts
newS3FileUploader(s3, bucket)Create file uploader instancemodules/file-storage/src/s3.d.ts
Configuration via AwsFileStorageConfig (env vars — bucket name, region, access key, secret key).
Unconfirmed: S3 bucket name, region, and whether this is the same AWS account as the prior full AWS deployment.

SES Email

The notifications module uses AWS SES as the primary email transport with SMTP as fallback: Source: equa-server/modules/notifications/src/nodemailer/email-notifier.ts
Unconfirmed: SES region, verified sender domains/emails, and sending limits.

Database Infrastructure

PostgreSQL (Confirmed from Code)

PropertyValueSource
Database typePostgreSQLequa-server/README.md
ORMTypeORM 0.2.24equa-server/modules/persistence/package.json
Entity count92+ entitiesequa-server/modules/persistence/src/schema.ts (~2000 lines)
Schema definitionequa-server/modules/persistence/src/schema.tsTypeORM entity classes
Connection configEnvironment variablesequa-server/README.md
Session storageTypeORM session store (express-session)equa-server/modules/api/src/server.ts line 189
Access patternAll modules use persistence helpers, not direct TypeORM callsequa-server/README.md line 106
Connection environment variables:
DATABASE_TYPE=postgres
DATABASE_HOST=localhost
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=password
DATABASE_NAME=equa
DATABASE_LOGS=query,error      # optional
DATABASE_SCHEMA=               # optional
DATABASE_SYNC=                 # auto-sync in development
Source: equa-server/README.md lines 14-23.

Production Database

Unconfirmed — Requires GCP Console AccessThe production database is likely a managed PostgreSQL service, with Cloud SQL as the leading historical candidate, based on:
  • stacks-ranking-priorities/runbooks/EQUA_CC_COST_CHECK.md identifies Cloud SQL as the #1 cost driver
  • The wind-down runbook at equabot/threads/wind-down-equa-cc-public-instance/WIND-DOWN-RUNBOOK.md explicitly marks the database type as unknown (“Cloud SQL? Firestore? both?”)
Items to confirm:
  • Managed database provider, instance name, tier, and PostgreSQL version
  • Connection method (provider proxy, direct IP, Unix socket, or equivalent)
  • Backup configuration and retention
  • Storage size and IOPS

SSL/TLS

Cloud Run (Automatic)

Cloud Run provides automatic HTTPS with Google-managed SSL certificates for the service URL (equa-server-333648330110.us-central1.run.app).

Custom Domains

Partially VerifiedPublic TLS termination is currently split by host. The 2026-05-03 baseline saw equa.cc reply through Google-managed infrastructure while app.equa.cc replied with Railway headers, so certificate ownership and edge termination must be re-verified per host before treating any DNS/HTTP prose as authoritative.

Server-Side SSL (Optional)

The equa-server supports optional SSL termination at the application level:
Env VarPurpose
API_SSLEnable HTTPS server
SSL_PRIVATE_KEY_PATHPath to private key file
SSL_PUBLIC_KEY_PATHPath to public certificate file
When enabled, Express starts an HTTPS server instead of HTTP. This is typically not needed when running behind Cloud Run or a reverse proxy that handles TLS termination. Source: equa-server/modules/api/src/server.ts lines 53-80.

Local Development Setup

Service Map

ServiceStart CommandPortDirectory
PostgreSQLdocker run (or local install)5432
equa-serveryarn start:dev3000equa-server/
equa-webyarn dev8080equa-web/
equa-patternlibnpm run storybook6006equa-patternlib-nextjs/
command-center-sonpm run dev3001command-center-so/
equabot-gatewayequabot gateway start18789equabot/ (workspace root)
OllamaSystem service11434

Proxy Configuration (Development)

In development, the Webpack dev server handles API proxying:
PathTarget
/apihttp://localhost:3000 (equa-server)
/equanaut-apihttp://localhost:19792
Source: equa-web/webpack.config.js (dev server proxy configuration).

Prerequisites

  1. Node.js 18+ for equa-web / equa-server; Node 22+ for equabot-gateway (see Developer Setup for per-repo version table)
  2. Yarn (equa-server and equa-web use Yarn workspaces)
  3. npm (command-center-so, equa-patternlib use npm)
  4. PostgreSQL (local or Docker: docker run -e POSTGRES_PASSWORD=password -p 5432:5432 postgres)
  5. Git (all repos cloned to /Users/shawnowen/Documents/repos/)

Setup Steps

# 1. equa-server
cd equa-server
cp .env.example .env  # configure DATABASE_* vars
yarn install
yarn tsc
yarn init:db          # if fresh database
yarn start:dev

# 2. equa-web
cd equa-web
yarn install
yarn dev              # serves on :8080, proxies /api to :3000

# 3. command-center-so
cd command-center-so
npm install
npm run dev           # serves on :3001

# 4. equa-patternlib (optional, for component development)
cd equa-patternlib-nextjs
npm install
npm run storybook     # serves on :6006
Source: equa-server/README.md, equa-web/package.json scripts, command-center-so/package.json scripts.

Health Endpoints

ServiceEndpointResponse
equa-serverGET /health{"status": "healthy", "timestamp": "..."}
equa-serverGET /{"message": "API is running"}
equa-web (Nginx)GET /health200 OK (text/plain)
command-center-soGET /command-center/api/healthHealth status JSON
Source: equa-server/modules/api/src/server.ts lines 110-117, equa-web/nginx.conf, Lines: 54-58

Wind-Down Status

Decision Context

ItemDetail
Decision dateFebruary 16, 2026
ReasonSave $300-500/month; no paying customers
StrategyLocal-first development focus
ReversibilityFull — all data backed up, infrastructure documented
GitHub issue#358

Redeployment Triggers

  1. Feature-complete local version (Mac/iOS desktop app)
  2. Paying customer pipeline ready
  3. Funding available for hosting
  4. Hosting platform decision finalized (GCP vs Railway vs other)

Wind-Down Runbook

A detailed runbook exists at equabot/threads/wind-down-equa-cc-public-instance/WIND-DOWN-RUNBOOK.md covering:
  • Pre-flight scope confirmation
  • Inventory of all billable GCP resources
  • Database and config backups/exports
  • Traffic cutover and service scaling
  • Cost verification

Cost Analysis

The cost check runbook at stacks-ranking-priorities/runbooks/EQUA_CC_COST_CHECK.md identifies common lingering charge sources:
  • Cloud SQL (often #1 cost driver)
  • Cloud Load Balancing (forwarding rules, URL maps, proxies)
  • Cloud NAT (if used)
  • Compute Engine (disks, snapshots)
  • Artifact Registry storage
  • Cloud Logging ingestion/retention

Appendix: Unconfirmed Items Requiring Audit

The following items cannot be verified from source code alone and require access to external systems. Use the Phase 1 Audit Runbook for step-by-step instructions to complete these audits and then update this document.

Google Drive / Confluence Audit Required

ItemExpected Source
Full AWS deployment architecture (prior era)Google Drive infrastructure docs
AWS → GCP migration timeline and rationaleConfluence
Original AWS infrastructure diagramsGoogle Drive
Database migration recordsConfluence

GCP Console Access Required

ItemHow to Verify
GCP project IDGCP Console → Project selector
Managed PostgreSQL provider detailsVerify the live provider first, then inspect the matching console/service
Load balancer configurationGCP Console → Network Services → Load Balancing
Static IP reservationGCP Console → VPC Network → External IP Addresses
SSL certificate namesGCP Console → Security → Certificate Manager
Monthly cost by SKUGCP Console → Billing → Reports (last 30 days)
Nameserver configurationGCP Console → Cloud DNS → Zone details

AWS Console Access Required

ItemHow to Verify
S3 bucket name and regionAWS Console → S3 → Buckets
SES verified domains/emailsAWS Console → SES → Verified Identities
SES sending regionAWS Console → SES → Account Dashboard
IAM user/role for S3+SES accessAWS Console → IAM
Once these audits are complete, the “Unconfirmed” sections in this document should be updated with verified details and the warning boxes removed.

Deployment Checklist

Source: Confluence KnowledgeBase — Deployment Checklist (by Christopher Johnson, v2.27.0)

Standard Deployment Procedure

  1. Pull code
  2. Bump versions
  3. Push frontend staging code
  4. Push backend staging code
  5. Notify team that deployment has started
  6. Push frontend production code
  7. Push backend production code
  8. Restart backend server
  9. Migrate database
  10. Watch for frontend to finish deploying
  11. Check production site
  12. Notify team that deployment is finished

Pre-Deploy Checks (supplementary)

  • All tests passing on target branch
  • Database migrations reviewed and tested on staging
  • Environment variables verified for target environment
  • Stakeholder sign-off obtained

Post-Deploy Verification (supplementary)

  • Health endpoint responds 200
  • Login flow functional
  • Key API endpoints return expected data
  • No new error spikes in monitoring

Rollback Procedure

  • Identify the issue (logs, monitoring, user reports)
  • Revert to previous Cloud Run revision or Railway deployment
  • Verify rollback successful
  • Investigate root cause before re-deploying