Deployment and Environments
SalesArck uses split deployment targets by app role.
Deployment Topology
| App | Platform | URL | Plan |
|---|---|---|---|
| API | Render | https://salesarc-api.onrender.com | Free (web service) |
| Web | Vercel | https://salesarc-web.vercel.app | Free (static SPA) |
| Workers | — | Not yet deployed | Scaffolded in apps/workers |
Deployment Configuration Files
Render (render.yaml)
services:
- type: web
name: salesarc-api
runtime: node
plan: free
buildCommand: pnpm install --frozen-lockfile --prod=false && pnpm turbo run build --filter=@salesarc/api...
startCommand: pnpm --filter @salesarc/api start
healthCheckPath: /health
autoDeploy: true
Environment variables set via Render dashboard:
- NODE_VERSION=20, NODE_ENV=production, PORT=3000
- SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, DATABASE_URL
- CORS_ALLOWED_ORIGINS, API_BASE_URL, FRONTEND_URL
- ENCRYPTION_KEY, SQUARE_APP_ID, SQUARE_APP_SECRET, SQUARE_ENVIRONMENT
- SQUARE_WEBHOOK_SIGNATURE_KEY, CRON_SECRET
- CLOVER_APP_ID, CLOVER_APP_SECRET (when Clover is enabled)
- RESEND_API_KEY, RESEND_FROM_EMAIL
Note: Cron jobs cannot run on Render free plan. Square polling requires manual invocation or external scheduler.
Vercel (vercel.json)
{
"buildCommand": "pnpm turbo run build --filter=@salesarc/web",
"outputDirectory": "apps/web/dist",
"installCommand": "pnpm install --frozen-lockfile",
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
Framework: none (static SPA with client-side routing via rewrites).
Fly.io (fly.toml) — Alternative/Future
app = "salesarc-api"
primary_region = "iad"
vm.memory = "512mb"
Present in repo as an alternative deployment target. Not actively used.
Environment Validation
Backend env contract is validated in apps/api/src/lib/env.ts using Zod.
Startup fails fast on invalid configuration.
Core required variables
| Variable | Type | Description |
|---|---|---|
DATABASE_URL | string | Postgres connection (Supabase Transaction Pooler) |
SUPABASE_URL | string (URL) | Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY | string | Service role key (server-side only) |
Configurable defaults
| Variable | Default | Description |
|---|---|---|
NODE_ENV | development | development, staging, production |
PORT | 3000 | HTTP listen port |
CORS_ALLOWED_ORIGINS | * | Comma-separated origins or * |
LOG_LEVEL | info | debug, info, warn, error |
SQUARE_ENVIRONMENT | sandbox | sandbox or production |
CLOVER_ENVIRONMENT | sandbox | sandbox or production |
Conditional requirements
When Square credentials are provided:
ENCRYPTION_KEYmust be 64 hex chars (openssl rand -hex 32)- In production,
API_BASE_URLandFRONTEND_URLare required
Optional but operationally critical
| Variable | Description |
|---|---|
SENTRY_DSN | Error tracking |
RESEND_API_KEY | Email notifications |
RESEND_FROM_EMAIL | Sender address (must be verified Resend domain) |
CRON_SECRET | Internal cron auth (min 16 chars) |
SQUARE_WEBHOOK_SIGNATURE_KEY | Webhook HMAC verification |
CLOVER_WEBHOOK_SECRET | Clover webhook HMAC verification |
Cron and Polling Model
Square poll endpoint:
POST /api/v1/internal/cron/square-poll-payments- Requires header
X-Cron-SecretmatchingCRON_SECRETenv var - Not available on Render free plan as a scheduled service
- Can be invoked externally (e.g., cron-job.org, GitHub Actions, or manual curl)
This route acts as fallback/backfill in addition to webhooks.
Database and Migration Operations
Monorepo scripts:
pnpm db:generate— Generate Drizzle migration SQL from schema changespnpm db:migrate— Apply pending migrationspnpm db:studio— Launch Drizzle Studio (visual DB editor)
Recommended operational pattern:
- Runtime API uses pooled
DATABASE_URL(Supabase Transaction Pooler) - Migration tasks use direct DB URL where required by platform
Frontend Runtime Variables
Web app relies on Vite env for runtime integration:
| Variable | Required | Description |
|---|---|---|
VITE_SUPABASE_URL | Yes | Supabase project URL |
VITE_SUPABASE_ANON_KEY | Yes | Public anon key (safe to expose) |
VITE_API_BASE_URL | No | API base override (default: uses Vite proxy in dev) |
VITE_APP_ORIGIN | No | Auth redirect origin override |
Missing Supabase URL/key is treated as fatal at startup.
Development Setup
Local development uses:
- Vite dev server on port 5173 with proxy:
/api->http://localhost:3000 - API dev server via
tsx watchon port 3000 - Supabase project for auth (shared between local and deployed)
- Postgres via Supabase (same DB for dev/staging unless configured separately)
Deployment Safety Checklist
Before production deploy:
- Run
lint,typecheck, andtestacross workspace - Verify API and web env parity (base URLs, origins)
- Ensure Square/Clover environment (
sandbox|production) matches credentials - Validate cron secret consistency between scheduler and API
- Verify webhook signature key is present for production security
- Confirm
ENCRYPTION_KEYis 64 hex chars when POS credentials are configured - Verify Resend from-email domain is verified if email notifications are desired
Known Constraints
- Workers app is scaffolded but not yet queue-backed
- Webhook processing uses in-process async callback model (
setImmediate) - Poll fallback is required for resilience until durable queue worker path is complete
- Render free plan has cold start latency and no built-in cron scheduler