CI/CD & Deployment

Status: Active Owner: @bilal @deen Last Updated: 2026-02-24 (unified infra→deploy pattern across all services)

Infrastructure and deployment planning. Detailed configs and scripts live in the repo.

Pipeline Overview

PR → Lint + Type-Check → Unit Tests → Build → Security Scan → AI Code Review
  → DB Migrations → Deploy to Cloudflare Workers → Smoke Tests → Live

Environment Strategy

EnvironmentDatabaseHostingStatus
LocalSupabase local (Docker)localhost:3000Active
ProductionSupabase CloudCloudflare WorkersActive
StagingSupabase Cloud (separate)Cloudflare WorkersFuture

Branch Strategy

main                → Production
├── staging         → Staging (future)
└── feature/*       → Development
    └── claude/*    → AI-generated branches

Local Development

Requires: Docker 24+, Node.js 20+, pnpm, Supabase CLI 1.150+.

supabase start            # Start local Supabase (first run downloads ~2GB)
pnpm install && pnpm dev  # Start Next.js

Local services: App (:3000), Supabase API (:54321), Studio (:54323), PostgreSQL (:54322), Inbucket (:54324).

Seed users: admin@test.local, landlord@test.local, staff@test.local (all password123).

Cloudflare Workers Preview

pnpm build:cf      # Build for Cloudflare Workers (via @opennextjs/cloudflare)
pnpm preview:cf    # Run locally on workerd runtime (wrangler dev)

Environment Variables

Required for Production

CategoryVariables
SupabaseNEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY
DatabaseVia Hyperdrive binding (not a plain env var)
LLMANTHROPIC_API_KEY, OPENAI_API_KEY
CommsTWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_NUMBER, VOICE_WEBHOOK_SECRET
WhatsAppMETA_WHATSAPP_PHONE_NUMBER_ID, META_WHATSAPP_ACCESS_TOKEN, META_WHATSAPP_VERIFY_TOKEN, META_WHATSAPP_APP_SECRET
EmailSENDGRID_API_KEY, SENDGRID_FROM_EMAIL
OptionalSENTRY_DSN, UPTIME_ROBOT_KEY, KIMI_API_KEY, GLM_API_KEY

Where secrets are stored

TypeLocation
Non-secret varswrangler.jsonc [vars] section (committed)
SecretsCloudflare Worker secrets (via wrangler secret put)
Terraform varsTerraform Cloud workspace variables
CI/CD secretsGitHub Actions secrets

CI Pipeline (GitHub Actions)

StepToolRuns On
Lint + type-checkESLint, tscEvery PR
Unit testsVitest (42 tests)Every PR
BuildNext.jsEvery PR
Secret scanningTrufflehogEvery PR
SASTSemgrepEvery PR
Dependency auditpnpm auditEvery PR
AI code reviewCodeRabbitPRs only

CD Pipeline

StepTool
Buildpnpm build:cf (@opennextjs/cloudflare)
Deploywrangler deploy (via GitHub Actions on push to main)
DB migrationsSupabase CLI (supabase db push)
Smoke testscurl health + message endpoints
Rollbackwrangler rollback

Deploy triggers

All three services follow the same infra → deploy dependency pattern:

  1. deploy-*.yml — Two-job pipeline: infra (Terraform apply) → deploy (needs: [infra]). Terraform always runs before content is pushed. On subsequent runs where infra hasn’t changed, Terraform is a no-op (~10s).
  2. terraform-*.yml — PR plan only + manual dispatch apply. No push-apply on main (that’s handled by the deploy workflow). This avoids race conditions where Terraform and deploy run in parallel.
WorkflowTriggerWorking DirectoryNotes
deploy-marketing.ymlPush to main on envo-marketing/** or infra/marketing/**Bothinfra → deploy pipeline
terraform-marketing.ymlPR on infra/marketing/**, or manual dispatchinfra/marketing/Plan on PR, apply on manual dispatch only
deploy-dashboard.ymlPush to main on envo-dashboard/** or infra/dashboard/**Bothinfra → deploy pipeline
terraform-dashboard.ymlPR on infra/dashboard/**, or manual dispatchinfra/dashboard/Plan on PR, apply on manual dispatch only
deploy-brain.ymlPush to main on docs/** or infra/brain/**Bothinfra → deploy pipeline
terraform-brain.ymlPR on infra/brain/**, or manual dispatchinfra/brain/Plan on PR, apply on manual dispatch only

Security Tooling

ToolPurposeFrequency
TrufflehogSecret detectionEvery PR
SemgrepSAST / code analysisEvery PR
TrivyDependency scanningEvery PR
OWASP ZAPDAST / web scanningWeekly
pnpm auditDependency vulnerabilitiesEvery PR + Dependabot

Local Isolation Strategy

External services are stubbed for local development:

ComponentLocalProduction
PostgreSQLSupabase localSupabase Cloud (via Hyperdrive)
AuthSupabase local (seeded users)Supabase Auth
LLMStub provider (LLM_PROVIDER=stub)Real API keys
EmbeddingsDeterministic stub (content hashing)OpenAI
TwilioTest credentials (no send)Real API
EmailInbucket (port 54324)SendGrid
VoiceFixture payloadsVAPI/Retell

Implementation Phases

Phase 0: Local Isolation — DONE (partial)

  • Supabase local stack
  • Seed auth users
  • .env.example with local defaults
  • LLM stub provider
  • Embedding stub
  • Webhook test fixtures

Phase 1: Foundation — DONE

  • Basic CI (lint + test on PR)
  • Cloudflare Workers deployment
  • Cloudflare Access (ZTNA)
  • Hyperdrive connection pooling
  • Type-check + build in CI
  • Health check endpoint

Phase 2: Security — PLANNED

  • Trufflehog
  • Semgrep
  • pnpm audit
  • CodeRabbit AI review

Phase 3: Automation — PLANNED

  • OWASP ZAP scheduled scans
  • Slack notifications
  • Rollback automation

Phase 4: Staging — FUTURE

  • Staging Supabase project
  • Staging environment config
  • Data sync scripts