Preparing for Production
Before deploying your CoFounder AI agents to production, you need a solid checklist covering environment separation, configuration management, feature flags, and health checks. This lesson walks through every step to ensure a smooth launch.
Production Readiness Checklist
A production-ready CoFounder application goes far beyond "it works on my machine." You need to verify that every layer of your stack is configured for reliability, security, and observability. Here is a practical checklist to work through before your first deploy:
- All environment variables are defined and validated (no fallback to dev defaults).
- Database migrations are up to date and tested against a staging database.
- Supabase Row Level Security (RLS) policies are enabled on every table.
- API keys are stored in a secrets manager, never committed to source control.
- Error tracking (Sentry or equivalent) is wired up and tested.
- Health check endpoints return meaningful status codes.
- Rate limiting is configured on all public-facing endpoints.
# .cofounder.yml — production readiness section
project:
name: my-ai-app
env: production
checks:
database_migrations: true
rls_policies: true
env_validation: true
health_endpoint: /api/health
error_tracking: sentry
rate_limiting:
enabled: true
max_requests_per_minute: 60Environment Separation
Running development, staging, and production on the same resources is a recipe for data corruption and security incidents. CoFounder projects should maintain strict separation across three tiers:
- Development — local Supabase instance, local Redis, mock LLM calls when possible.
- Staging — mirrors production infrastructure but uses isolated databases and a separate Supabase project.
- Production — locked-down access, secrets managed via Vercel/AWS Secrets Manager, real LLM provider keys with budget caps.
// lib/env.ts — environment-aware config loader
const ENV = process.env.NODE_ENV ?? 'development';
export const config = {
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
redisUrl: process.env.REDIS_URL!,
llmProvider: process.env.LLM_PROVIDER ?? 'openai',
isProd: ENV === 'production',
isStaging: ENV === 'staging' || process.env.VERCEL_ENV === 'preview',
};
if (config.isProd && !process.env.SENTRY_DSN) {
throw new Error('SENTRY_DSN is required in production');
}Feature Flags
Feature flags let you ship code to production without exposing unfinished features to users. They are especially important for AI agents where prompt behavior or tool availability may change between releases. A simple pattern using environment variables works well for small teams:
// lib/flags.ts
export const flags = {
newAgentPipeline: process.env.FLAG_NEW_AGENT_PIPELINE === 'true',
costDashboard: process.env.FLAG_COST_DASHBOARD === 'true',
streamingResponses: process.env.FLAG_STREAMING === 'true',
};
// Usage in a route handler
import { flags } from '@/lib/flags';
export async function POST(req: Request) {
if (flags.newAgentPipeline) {
return handleWithNewPipeline(req);
}
return handleWithLegacyPipeline(req);
}Health Checks
A health check endpoint lets your load balancer, container orchestrator, or monitoring tool verify that the application is alive and its dependencies are reachable. A good health check tests the database connection, Redis, and any critical external services.
// app/api/health/route.ts
import { createClient } from '@supabase/supabase-js';
import Redis from 'ioredis';
import { NextResponse } from 'next/server';
export async function GET() {
const checks: Record<string, string> = {};
// Database check
try {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const { error } = await supabase.from('_health').select('id').limit(1);
checks.database = error ? 'unhealthy' : 'ok';
} catch {
checks.database = 'unreachable';
}
// Redis check
try {
const redis = new Redis(process.env.REDIS_URL!);
await redis.ping();
checks.redis = 'ok';
await redis.quit();
} catch {
checks.redis = 'unreachable';
}
const healthy = Object.values(checks).every((v) => v === 'ok');
return NextResponse.json(
{ status: healthy ? 'healthy' : 'degraded', checks },
{ status: healthy ? 200 : 503 }
);
}Configuration Management Best Practices
Keep configuration close to the code but never in the code. Use .env.local for development, platform-level environment variables for staging and production, and validate every value at startup. The next lesson dives deep into environment configuration with Zod validation and secret rotation.