Appearance
Developer Workflow Guide
This document describes the branching strategy, CI/CD pipeline, local development setup, and staging/production promotion flow for Compath.
Environments
| Environment | Backend URL | Frontend URL | Branch | Auto-deploy |
|---|---|---|---|---|
| Local | http://localhost:3000 | http://localhost:5173 | any | — |
| Staging | https://brain-staging.compath.app | Cloudflare Pages branch preview | staging | ✅ on push |
| Production | https://brain.compath.app | https://app.compath.app | main | ✅ on push |
Branch Strategy
main ─────────────────────────────────────────────── production
│
└── staging ──────────────────────────────────────── staging env
│
├── feat/calendar-sync
├── fix/booking-notification
└── chore/update-depsBranch naming
| Type | Pattern | Example |
|---|---|---|
| New feature | feat/<short-description> | feat/reschedule-booking |
| Bug fix | fix/<short-description> | fix/pending-booking-calendar |
| Chore / maintenance | chore/<short-description> | chore/update-go-deps |
| Hotfix (prod only) | hotfix/<short-description> | hotfix/payment-500-error |
Protected branches
| Branch | Rules |
|---|---|
main | Requires PR, CI must pass, 1 approval |
staging | Requires PR, CI must pass |
Standard Developer Flow
1. branch off staging
git checkout staging && git pull
git checkout -b feat/my-feature
2. develop locally
make dev # backend with hot reload
bun dev # frontend
3. push and open PR → staging
git push -u origin feat/my-feature
# Open PR targeting staging (not main)
4. CI runs automatically
✓ Go tests + race detector
✓ golangci-lint
✓ TypeScript type-check
✓ Swagger generation
5. PR reviewed and merged → staging
→ deploy-staging workflow fires
→ backend image tagged :staging pushed to GHCR
→ staging containers restarted on VPS
→ https://brain-staging.compath.app live within ~3 min
6. QA on staging
→ verify feature works end-to-end
→ run any manual checks
7. open PR: staging → main
→ requires 1 approval
→ CI reruns
8. merge → production
→ deploy-backend workflow fires
→ image tagged :latest pushed to GHCR
→ production containers restarted on VPSHotfix Flow (production issue, no time for staging)
1. branch off main
git checkout main && git pull
git checkout -b hotfix/critical-bug
2. fix, test locally
3. open PR → main
→ CI runs
→ requires 1 approval (expedited)
4. merge → production auto-deploys
5. backport to staging
git checkout staging && git pull
git cherry-pick <commit-sha>
git pushPrototype UI Promotion Flow (prototype -> main)
Use this for validated UI in prototype routes/components when /proto must not ship to main.
bash
# 1) Create a clean promotion branch from main
git fetch origin
git switch main
git pull --ff-only origin main
git switch -c promote/<scope>-<yyyy-mm-dd>
# 2) Cherry-pick production-ready commits from prototype
git cherry-pick -x <sha1> <sha2> ...
# 3) For mixed commits (prod + proto), apply without commit and exclude proto files
git cherry-pick -n <sha>
git restore --source=HEAD --staged --worktree frontend/src/app/proto
git commit -m "promote(ui): <summary> (proto excluded)"
# 4) Verify no proto leakage
git diff --name-only origin/main...HEAD | rg "^frontend/src/app/proto/"
rg -n "@/app/proto" frontend/src --glob "!frontend/src/app/proto/**"
# 5) Validate and publish
cd frontend
bun run type-check
bun run build
git push -u origin promote/<scope>-<yyyy-mm-dd>Rules:
- Never open PRs from
prototypedirectly tomain. - Open PRs from
promote/*tomainonly. - If a commit introduces dependencies not yet in
main, promote the minimal required file(s) as a separate commit.
Local Development Setup
Prerequisites
- Go 1.25+
- Bun 1.x
- Docker + Docker Compose
1. Start local services
bash
# From repo root — starts PostgreSQL + Adminer
npm run docker:up2. Backend
bash
cd backend
cp .env.example .env # fill in required values
make migrate-up # run migrations
make dev # hot reload at http://localhost:3000Key env vars for local development:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/compath?sslmode=disable
SUPABASE_URL=https://<your-project>.supabase.co
SERVER_PORT=3000
ENVIRONMENT=development3. Frontend
bash
cd frontend
cp .env.example .env.local # fill in required values
bun install
bun dev # dev server at http://localhost:5173Key env vars:
VITE_API_URL=http://localhost:3000
VITE_SUPABASE_URL=https://<your-project>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-anon-key>4. Verify setup
bash
curl http://localhost:3000/api/v1/health # → {"status":"ok"}Adminer (DB browser): http://localhost:8080
- Server:
postgres, User:postgres, Password:postgres, DB:compath
CI Pipeline
CI runs automatically on every PR. All jobs must pass before merge is allowed.
On PRs targeting staging or main
| Job | What it checks | Blocks merge |
|---|---|---|
test | go test -race ./... against a real Postgres | ✅ Yes |
lint | golangci-lint | ⚠️ Warning only (continue-on-error) |
swagger | swag init succeeds | ✅ Yes |
type-check | tsc --noEmit | ✅ Yes |
Workflow files
| File | Trigger | Purpose |
|---|---|---|
ci-backend.yml | PR → staging or main | Test, lint, swagger |
ci-frontend.yml | PR → staging or main | Type-check |
deploy-staging.yml | Push to staging | Build :staging image, deploy |
deploy-backend.yml | Push to main | Build :latest image, deploy production |
Staging Environment
Architecture
VPS (same server as production)
├── compath-backend :3000 ← production
├── compath-backend-staging :3001 ← staging (joins production nginx network)
├── compath-postgres :5432 ← production DB
├── compath-postgres-staging:5433 ← staging DB (separate data)
├── compath-redis ← production cache
├── compath-redis-staging ← staging cache (separate)
└── compath-nginx :80/:443 ← routes both domains
├── brain.compath.app → compath-backend
└── brain-staging.compath.app → compath-backend-stagingCloudflare SSL Setup (one-time, covers all subdomains)
SSL is handled by Cloudflare — no certbot required. The nginx config uses a Cloudflare Origin Certificate (wildcard *.compath.app) which is already installed for production. If the Origin Certificate doesn't exist yet, follow these steps:
1. Generate the Cloudflare Origin Certificate
- Go to Cloudflare Dashboard →
compath.app→ SSL/TLS → Origin Server - Click Create Certificate
- Choose:
- Key type: RSA (2048)
- Hostnames:
compath.app,*.compath.app - Validity: 15 years
- Click Create
- Copy the Origin Certificate (PEM) and Private Key
2. Install on the VPS
bash
ssh compath-vps
sudo mkdir -p /etc/ssl/cloudflare
# Paste the Origin Certificate
sudo nano /etc/ssl/cloudflare/compath.app.pem
# Paste the Private Key
sudo nano /etc/ssl/cloudflare/compath.app.key
sudo chmod 644 /etc/ssl/cloudflare/compath.app.pem
sudo chmod 600 /etc/ssl/cloudflare/compath.app.keyIf production is already running, the cert files are already on the VPS — staging reuses them.
3. Set Cloudflare SSL mode
In Cloudflare Dashboard → compath.app → SSL/TLS → Overview:
- Set encryption mode to Full (strict)
This ensures Cloudflare validates the Origin Certificate when connecting to your VPS.
First-time staging setup on the VPS
SSH into the VPS and run these steps once:
bash
ssh compath-vps
cd /home/compath
# 1. Create the staging env file
cp infrastructure/deployment/.env infrastructure/deployment/.env.staging
nano infrastructure/deployment/.env.staging
# Edit: STAGING_POSTGRES_*, STAGING_REDIS_PASSWORD, SUPABASE_URL (staging project), etc.
# 2. Add DNS record in Cloudflare
# Type: A Name: brain-staging Content: <VPS IP> Proxy: ON (orange cloud)
# (The wildcard Origin Certificate already covers *.compath.app — no cert work needed)
# 3. Reload nginx to pick up staging.conf
docker exec compath-nginx nginx -s reload
# Verify nginx config is valid before reloading
docker exec compath-nginx nginx -t
# 4. Start staging stack for the first time
docker compose -f infrastructure/deployment/docker-compose.staging.yml up -d
# 5. Run staging migrations
docker exec compath-backend-staging ./migrate -command up
# 6. Health check
curl -sf https://brain-staging.compath.app/api/v1/healthRequired GitHub Secrets
The staging deploy reuses the existing production VPS secrets. No new secrets needed unless you want to separate them.
| Secret | Used by | Value |
|---|---|---|
VPS_HOST | deploy-staging, deploy-backend | VPS IP address |
VPS_USERNAME | deploy-staging, deploy-backend | SSH user |
VPS_SSH_KEY | deploy-staging, deploy-backend | Private key |
VPS_PORT | deploy-staging, deploy-backend | SSH port (usually 22) |
GHCR_TOKEN | deploy-staging, deploy-backend | GitHub PAT with read:packages |
GHCR_USERNAME | deploy-staging, deploy-backend | GitHub username |
Staging .env.staging reference
env
# Server
SERVER_PORT=3000
SERVER_HOST=0.0.0.0
ENVIRONMENT=staging
# Database (staging-specific)
STAGING_POSTGRES_USER=postgres
STAGING_POSTGRES_PASSWORD=<strong-password>
STAGING_POSTGRES_DB=compath_staging
DATABASE_URL=postgresql://postgres:<password>@compath-postgres-staging:5432/compath_staging?sslmode=disable
# Redis (staging-specific)
STAGING_REDIS_PASSWORD=<strong-password>
REDIS_URL=redis://:${STAGING_REDIS_PASSWORD}@compath-redis-staging:6379
# Supabase — use your STAGING Supabase project, not production
SUPABASE_URL=https://<staging-project>.supabase.co
SUPABASE_ANON_KEY=<staging-anon-key>
# Email — use test mode or a staging Resend domain
RESEND_API_KEY=<resend-key>
RESEND_FROM_EMAIL=no-reply@staging.compath.app
# Google Calendar — use a separate GCP project or the same with staging redirect URI
GOOGLE_CLIENT_ID=<google-client-id>
GOOGLE_CLIENT_SECRET=<google-client-secret>
GOOGLE_REDIRECT_URI=https://brain-staging.compath.app/api/v1/calendar/callback/google
GOOGLE_WEBHOOK_URL=https://brain-staging.compath.app/api/v1/webhooks/google/calendar
# Axiom — same API token as production, separate dataset
AXIOM_TOKEN=<axiom-api-token>
VECTOR_AXIOM_DATASET=compath-stagingRunning migrations on staging
bash
ssh compath-vps
docker exec compath-backend-staging ./migrate -command version
docker exec compath-backend-staging ./migrate -command upFrontend Staging (Cloudflare Pages)
Cloudflare Pages automatically creates a branch preview for every branch. When the staging branch is pushed:
- Branch preview URL:
https://staging.compath.pages.dev - To use a custom domain (
app-staging.compath.app):- Go to Cloudflare Pages → your project → Settings → Environment Variables
- Add a
stagingenvironment withVITE_API_URL=https://brain-staging.compath.app - Go to Custom Domains → add
app-staging.compath.appmapped to thestagingbranch
Environment variables per branch (Cloudflare Pages dashboard)
| Variable | Production (main) | Staging (staging) |
|---|---|---|
VITE_API_URL | https://brain.compath.app | https://brain-staging.compath.app |
VITE_SUPABASE_URL | prod Supabase URL | staging Supabase URL |
VITE_SUPABASE_ANON_KEY | prod anon key | staging anon key |
Deployment Checklist
Deploying to staging
Staging deploys automatically on every merge to staging. No manual steps needed unless running migrations with a schema change.
bash
# If the PR includes a new migration:
ssh compath-vps
docker exec compath-backend-staging ./migrate -command upPromoting staging → production
- Open PR from
staging→mainon GitHub - CI reruns — wait for green
- Get approval
- Merge
deploy-backend.ymlfires automatically (~3 min to deploy)- SSH in and run migrations if needed:bash
ssh compath-vps docker exec compath-backend ./migrate -command up curl -sf https://brain.compath.app/api/v1/health
Common Commands Reference
bash
# Check staging is healthy
curl -sf https://brain-staging.compath.app/api/v1/health
# View staging logs
ssh compath-vps "docker logs compath-backend-staging --tail 100 -f"
# View staging compose status
ssh compath-vps "docker compose -f /home/compath/infrastructure/deployment/docker-compose.staging.yml ps"
# Restart staging backend manually
ssh compath-vps "docker compose -f /home/compath/infrastructure/deployment/docker-compose.staging.yml restart backend"
# Connect to staging database
ssh compath-vps -t "docker exec -it compath-postgres-staging psql -U postgres -d compath_staging"
# Roll back last staging migration
ssh compath-vps "docker exec compath-backend-staging ./migrate -command down"