04 - Configuration & Deployment
Environment Variables
Environment variables are managed at multiple levels:
T3 Env Validation (Next.js App)
File: apps/app/src/env.mjs
The main frontend validates ~50 environment variables at build time using @t3-oss/env-nextjs with Zod schemas. Variables are split into server (secret) and client (public) categories.
Required variables:
DATABASE_URL— PostgreSQL connection stringAUTH_SECRET— Better Auth signing secretRESEND_API_KEY— Email deliveryREVALIDATION_SECRET— Cache revalidation tokenNEXT_PUBLIC_PORTAL_URL— Portal app URL
Optional variables (features degrade gracefully when absent):
OPENAI_API_KEY— AI features (chat, policy generation, embeddings)ANTHROPIC_API_KEY,GROQ_API_KEY— Alternative LLM providersFIRECRAWL_API_KEY— Web scraping for vendor researchAPP_AWS_*— S3 file storageUPSTASH_REDIS_REST_*— CachingUPSTASH_VECTOR_REST_*— Vector store for RAGTRIGGER_SECRET_KEY— Background job processingAUTH_GOOGLE_ID/SECRET,AUTH_GITHUB_ID/SECRET,AUTH_MICROSOFT_*— OAuth providersNEXT_PUBLIC_POSTHOG_*— AnalyticsFLEET_URL,FLEET_TOKEN— MDM device managementSTRIPE_SECRET_KEY— Billing
API Environment (apps/api/.env.example)
The NestJS API expects:
BASE_URL,BETTER_AUTH_URL,PORTDATABASE_URLAPP_AWS_*— S3 buckets (attachments, questionnaires, knowledge base, org assets)OPENAI_API_KEY,ANTHROPIC_API_KEY,GROQ_API_KEYUPSTASH_REDIS_*,UPSTASH_VECTOR_*RESEND_API_KEYNOVU_API_KEY— Notification serviceSECURITY_HUB_ROLE_ASSUMER_ARN— AWS SecurityHub scanning
Turbo Global Env
turbo.json declares 55+ environment variables as globalEnv, ensuring cache invalidation when any change:
{
"globalEnv": [
"DATABASE_URL",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"AUTH_SECRET",
...
]
}Auth Configuration
File: apps/app/src/utils/auth.ts
Better Auth is configured with:
| Feature | Configuration |
|---|---|
| Database | Prisma adapter (PostgreSQL), custom ID generation disabled (uses Prisma defaults) |
| Magic Link | 1-hour expiry, email sent via Resend |
| Email OTP | 6-digit code, 10-minute expiry |
| JWT | 1-hour expiry, custom payload (id, email, name, emailVerified) |
| Bearer | Enables Authorization: Bearer for API calls |
| Organizations | Plugin with 100M member limit, role-based access (ac + allRoles) |
| Multi-session | Multiple concurrent sessions per user |
| OAuth | Google, GitHub, Microsoft (conditionally enabled via env vars) |
| Account linking | Enabled for all OAuth providers |
| Session hooks | Auto-sets activeOrganizationId on login |
Trusted origins: localhost:3000, localhost:3002, *.trycomp.ai, plus custom via AUTH_TRUSTED_ORIGINS env var.
AWS Configuration
The platform uses multiple S3 buckets:
| Bucket Env Var | Purpose |
|---|---|
APP_AWS_BUCKET_NAME |
General file attachments |
APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET |
Uploaded questionnaire files |
APP_AWS_KNOWLEDGE_BASE_BUCKET |
Knowledge base documents |
APP_AWS_ORG_ASSETS_BUCKET |
Organization assets (logos, etc.) |
Additional AWS services:
- SecurityHub (
@aws-sdk/client-securityhub) — Cloud security scanning via role assumption - STS (
@aws-sdk/client-sts) — Cross-account role assumption for customer AWS accounts - Lambda (
@aws-sdk/client-lambda) — Serverless function invocation
Docker Multi-Stage Build
File: Dockerfile (root)
The Dockerfile defines 6 build stages:
Stage 1: deps
FROM oven/bun:1.2.8 AS deps- Copies workspace package.json files and lockfile
- Runs
bun installwith frozen lockfile
Stage 2: migrator
FROM oven/bun:1.2.8 AS migrator- Minimal image for running Prisma migrations
- Installs only
prisma,@prisma/client,@trycompai/db,zod - Copies schema and migration files
- CMD:
bunx prisma migrate deploy
Stage 3: app-builder
FROM deps AS app-builder- Copies full source from deps stage
- Runs schema combination script
- Sets build-time args (NEXT_PUBLIC_* for PostHog, GTM, LinkedIn, etc.)
- Builds with
SKIP_ENV_VALIDATION=trueand 6GB Node heap
Stage 4: app (production)
FROM node:22-alpine AS app- Copies standalone output from app-builder
- Copies static assets and public files
- Runs as
node apps/app/server.json port 3000
Stage 5: portal-builder
FROM deps AS portal-builder- Same pattern as app-builder but for the portal app
Stage 6: portal (production)
FROM node:22-alpine AS portal- Copies standalone portal output
- Runs as
node apps/portal/server.json port 3000
Docker Compose
File: docker-compose.yml (root)
Defines 4 services:
| Service | Image Target | Port | Purpose |
|---|---|---|---|
migrator |
migrator |
— | Runs Prisma migrations on startup |
seeder |
migrator |
— | Seeds database with initial data |
app |
app |
3000:3000 | Main SaaS frontend |
portal |
portal |
3002:3000 | Employee/vendor portal |
Common configuration:
- Health checks: HTTP GET to app endpoints
- Logging: JSON file driver, 10MB max size, 5 files, compression
- Restart:
unless-stopped - Environment: loaded from per-service
.envfiles
Local database (packages/db/docker-compose.yml):
- PostgreSQL 17-alpine on port 5432
- Database name:
comp - Persistent volume:
postgres_data
CI/CD
GitHub Actions Workflows
| Workflow | Trigger | Purpose |
|---|---|---|
release.yml |
Push to release branch |
Semantic-release: changelog, git tag, GitHub release |
check-types.yml |
Reusable workflow | TypeScript type checking (bun run type-check:ci) |
auto-pr-to-main.yml |
Push to feature branches | Auto-creates PR to main |
auto-pr-to-release.yml |
Push to main |
Auto-creates PR from main to release |
database-migrations-main.yml |
Push to main |
Runs Prisma migrations against dev DB |
database-migrations-release.yml |
Push to release |
Runs Prisma migrations against prod DB |
trigger-tasks-deploy-main.yml |
Push to main |
Deploys Trigger.dev tasks (app) to staging |
trigger-tasks-deploy-release.yml |
Push to release |
Deploys Trigger.dev tasks (app) to production |
trigger-api-tasks-deploy-main.yml |
Push to main |
Deploys Trigger.dev tasks (API) to staging |
trigger-api-tasks-deploy-release.yml |
Push to release |
Deploys Trigger.dev tasks (API) to production |
github-releases-to-discord.yml |
Release published | Posts release notes to Discord webhook |
Release Process
File: release.config.js
Uses semantic-release on the release branch:
- commit-analyzer — Determines version bump from conventional commits
- release-notes-generator — Generates changelog entries
- changelog — Updates
CHANGELOG.md - git — Commits
package.json,bun.lockb,CHANGELOG.md,packages/*/package.json - github — Creates GitHub release with notes
After release, the workflow merges the release branch back to main.
Pre-commit Hooks
Directory: .husky/
commit-msg: Runsnpx commitlint --edit $1to enforce conventional commit messages
Root package.json also configures lint-staged for formatting staged files with Prettier.
Turbo Build Pipeline
File: turbo.json
db:generate → build → (deploy)
↑
lint, typecheck (parallel)
| Task | Dependencies | Cache | Outputs |
|---|---|---|---|
db:generate |
None | Yes | prisma/schema.prisma, node_modules/.prisma/** |
build |
^build, db:generate |
Yes | .next/**, dist/** |
lint |
^lint |
Yes | — |
typecheck |
None | Yes | node_modules/.cache/tsbuildinfo.json |
dev |
None | No | Persistent |
test |
None | No | coverage/** |
The ^ prefix means "run this task in all dependency packages first" (topological ordering).
Package Publishing
Published packages use npm with public access:
| Package | Version | Registry |
|---|---|---|
@trycompai/db |
1.3.21 | npm |
@trycompai/ui |
1.3.2 | npm |
@trycompai/email |
1.0.0 | npm |
@trycompai/kv |
1.0.0 | npm |
@trycompai/analytics |
1.0.0 | npm |
@trycompai/tsconfig |
1.0.0 | npm |