Environment Variables
Flowstate uses environment variables for all configuration. This document lists every variable used across the platform, organized by service.
Secrets Management
Section titled “Secrets Management”Development: Secrets are injected via your team’s secrets management solution and never written to disk. A template file maps environment variable names to secret references that are resolved at runtime.
Production: Secrets are stored in your deployment platform’s secret management system (Docker secrets, Kubernetes secrets, AWS Secrets Manager, etc.).
CRITICAL: Never commit actual secret values to the repository. Always use environment variables or secret management tools.
Backend Service
Section titled “Backend Service”The backend service (REST API + RPC Gateway) requires the following variables:
Database
Section titled “Database”| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | MongoDB connection string with replica set |
Important:
- Must include
replicaSetparameter (Prisma requirement) - Use
authSource=adminfor authentication - In production, use a managed MongoDB service (MongoDB Atlas, AWS DocumentDB)
| Variable | Required | Description |
|---|---|---|
REDIS_URL | Yes | Redis connection string |
REDIS_PASSWORD | No | Redis password (if auth enabled) |
Important:
- Enable password authentication in production
- Use Redis Sentinel or Cluster for high availability
Authentication
Section titled “Authentication”| Variable | Required | Description |
|---|---|---|
JWT_SECRET | Yes | Secret for signing user session tokens |
ADMIN_JWT_SECRET | Yes | Secret for signing admin session tokens |
JWT_EXPIRES_IN | No | JWT expiration time (default: 30d) |
AUTH_COOKIE_DOMAIN | No* | Parent domain for the session cookie (see below) |
Important:
- Secrets must be at least 32 characters
- Use cryptographically random strings (not passwords)
- Generate with:
openssl rand -hex 32 - NEVER reuse secrets across environments
AUTH_COOKIE_DOMAIN — cross-subdomain sessions
Section titled “AUTH_COOKIE_DOMAIN — cross-subdomain sessions”Leave this unset in development. The flowstate_auth cookie is then
host-only, which is correct locally: cookies ignore port, so the web app
(localhost:14321) and the developer portal (localhost:14323) already share
one session.
Set it in any environment where surfaces live on different subdomains —
e.g. AUTH_COOKIE_DOMAIN=.goflowstate.com so app. and developers. share a
session. Without it, a signed-in user is bounced to login when they open the
developer portal, because a host-only cookie is never sent to a different host.
Scope it as tightly as the deployment allows: every subdomain beneath the value you set receives the session cookie.
| Variable | Required | Description |
|---|---|---|
CORS_ORIGIN | Yes | Allowed frontend origins (comma-separated) |
Important:
- NEVER use
*in production (security risk) - Include all frontend domains (web, admin)
- No trailing slashes
Agora (Video/Voice)
Section titled “Agora (Video/Voice)”| Variable | Required | Description |
|---|---|---|
AGORA_APP_ID | Yes | Agora.io App ID |
AGORA_APP_CERTIFICATE | Yes | Agora.io App Certificate |
Important:
- Obtain from Agora Console
- Keep certificate secret (used for token generation)
OpenAI
Section titled “OpenAI”| Variable | Required | Description |
|---|---|---|
OPENAI_API_KEY | Yes | OpenAI API key for AI features |
Important:
- Obtain from OpenAI Platform
- Monitor usage to avoid unexpected costs
- Set spending limits in OpenAI dashboard
Application URLs
Section titled “Application URLs”| Variable | Required | Description |
|---|---|---|
APP_URL | Yes | Main application URL (for magic links) |
ADMIN_URL | Yes | Admin dashboard URL |
Important:
- No trailing slashes
- Must match actual deployment URLs (used in emails)
S3/MinIO Storage
Section titled “S3/MinIO Storage”| Variable | Required | Description |
|---|---|---|
S3_ENDPOINT | Yes | S3-compatible endpoint URL |
S3_BUCKET | Yes | Bucket name for uploads |
S3_ACCESS_KEY | Yes | S3 access key ID |
S3_SECRET_KEY | Yes | S3 secret access key |
S3_REGION | Yes | S3 region |
S3_PUBLIC_URL | Yes | Public URL for serving files |
S3_FORCE_PATH_STYLE | No | Use path-style URLs (MinIO) |
Supported providers:
- AWS S3 —
s3.amazonaws.com - DigitalOcean Spaces —
<region>.digitaloceanspaces.com - Cloudflare R2 —
<account-id>.r2.cloudflarestorage.com - MinIO — Self-hosted (development)
Important:
- Set
S3_FORCE_PATH_STYLE=truefor MinIO - Configure bucket CORS for direct uploads
- Enable public read access for uploaded files
Rate Limiting
Section titled “Rate Limiting”| Variable | Required | Description |
|---|---|---|
RATE_LIMIT_WINDOW_MS | No | Rate limit window in milliseconds (default: 60000) |
RATE_LIMIT_MAX_REQUESTS | No | Max requests per window (default: 100) |
Important:
- Adjust based on expected traffic
- Lower limits for auth endpoints (prevent brute force)
- Higher limits for authenticated users
Email (SendGrid)
Section titled “Email (SendGrid)”| Variable | Required | Description |
|---|---|---|
SENDGRID_API_KEY | Yes | SendGrid API key for sending emails |
SENDGRID_FROM_EMAIL | Yes | Sender email address |
SENDGRID_FROM_NAME | No | Sender name (default: Flowstate) |
Important:
- Verify sender domain in SendGrid
- Monitor email deliverability
- Set up SPF/DKIM records
Logging
Section titled “Logging”| Variable | Required | Description |
|---|---|---|
LOG_LEVEL | No | Logging level (default: info) |
LOG_FORMAT | No | Log format (default: json) |
Important:
- Use
jsonin production (for log aggregation) - Use
prettyin development (human-readable) - Set to
debugfor troubleshooting
Server
Section titled “Server”| Variable | Required | Description |
|---|---|---|
PORT | No | Backend REST API port (default: 3000) |
GATEWAY_PORT | No | RPC Gateway port (default: 3001) |
NODE_ENV | No | Node.js environment (default: development) |
Web Frontend
Section titled “Web Frontend”The web application (Astro) requires:
| Variable | Required | Description |
|---|---|---|
SERVER_API_URL | Yes | Backend API URL (server-side only) |
PUBLIC_APP_URL | No | Public app URL (client-side) |
Important:
SERVER_API_URLis used by the Astro proxy (never exposed to browser)- Use Docker service names in containers (e.g.,
http://backend:3000)
Admin Dashboard
Section titled “Admin Dashboard”The admin dashboard (Astro) requires:
| Variable | Required | Description |
|---|---|---|
SERVER_API_URL | Yes | Backend API URL (server-side only) |
OIDC_ISSUER_URL | Yes | OIDC provider issuer URL |
OIDC_CLIENT_ID | Yes | OIDC client ID |
OIDC_CLIENT_SECRET | Yes | OIDC client secret |
OIDC_REDIRECT_URI | Yes | OIDC redirect URI |
SUPER_ADMIN_EMAILS | Yes | Comma-separated list of super admin emails |
Important:
- Configure OIDC provider (Google, Okta, Auth0, etc.)
- Add redirect URI to OIDC provider’s allowed list
- Super admins have full access (choose carefully)
Desktop App
Section titled “Desktop App”The Electron desktop app uses:
| Variable | Required | Description |
|---|---|---|
VITE_RPC_URL | Yes | RPC Gateway URL (build-time) |
DESKTOP_UPDATE_URL | No | Auto-update server URL |
DESKTOP_S3_BUCKET | No | S3 bucket for desktop builds |
Important:
VITE_RPC_URLis baked into the build (not runtime)- Auto-update requires S3 bucket with public read access
Development-Only Variables
Section titled “Development-Only Variables”These variables are only used in development:
| Variable | Required | Description |
|---|---|---|
SKIP_AUTH | No | Skip authentication (dev only) |
SEED_DATA | No | Seed demo data on startup |
ENABLE_PLAYGROUND | No | Enable GraphQL playground |
CRITICAL: Never set these in production.
Example Configurations
Section titled “Example Configurations”Development
Section titled “Development”Set the following variables for a local development environment. Obtain actual values from your administrator or secrets vault:
# DatabaseDATABASE_URL=<VALUE>
# RedisREDIS_URL=<VALUE>
# AuthenticationJWT_SECRET=<VALUE>ADMIN_JWT_SECRET=<VALUE>
# CORS (use your local web app URLs)CORS_ORIGIN=<VALUE>
# AgoraAGORA_APP_ID=<VALUE>AGORA_APP_CERTIFICATE=<VALUE>
# OpenAIOPENAI_API_KEY=<VALUE>
# URLs (use your local service URLs)APP_URL=<VALUE>ADMIN_URL=<VALUE>
# StorageS3_ENDPOINT=<VALUE>S3_BUCKET=<VALUE>S3_ACCESS_KEY=<VALUE>S3_SECRET_KEY=<VALUE>S3_REGION=<VALUE>S3_PUBLIC_URL=<VALUE>S3_FORCE_PATH_STYLE=true
# EmailSENDGRID_API_KEY=<VALUE>SENDGRID_FROM_EMAIL=<VALUE>
# LoggingLOG_LEVEL=debugLOG_FORMAT=pretty
# Development flagsSKIP_AUTH=falseSEED_DATA=trueProduction
Section titled “Production”# Database (use your managed MongoDB connection string)DATABASE_URL=<VALUE>
# Redis (use your managed Redis connection string)REDIS_URL=<VALUE>REDIS_PASSWORD=<VALUE>
# Authentication (generate with: openssl rand -hex 32)JWT_SECRET=<VALUE>ADMIN_JWT_SECRET=<VALUE>JWT_EXPIRES_IN=30d
# CORS (list your actual production domains)CORS_ORIGIN=<VALUE>
# AgoraAGORA_APP_ID=<VALUE>AGORA_APP_CERTIFICATE=<VALUE>
# OpenAIOPENAI_API_KEY=<VALUE>
# URLsAPP_URL=<VALUE>ADMIN_URL=<VALUE>
# Storage (AWS S3 or compatible)S3_ENDPOINT=<VALUE>S3_BUCKET=<VALUE>S3_ACCESS_KEY=<VALUE>S3_SECRET_KEY=<VALUE>S3_REGION=<VALUE>S3_PUBLIC_URL=<VALUE>
# Rate LimitingRATE_LIMIT_WINDOW_MS=60000RATE_LIMIT_MAX_REQUESTS=100
# EmailSENDGRID_API_KEY=<VALUE>SENDGRID_FROM_EMAIL=<VALUE>SENDGRID_FROM_NAME=Flowstate
# LoggingLOG_LEVEL=infoLOG_FORMAT=json
# ServerPORT=3000GATEWAY_PORT=3001NODE_ENV=productionAdmin Dashboard
Section titled “Admin Dashboard”# Backend APISERVER_API_URL=<VALUE>
# OIDC provider (Google, Okta, Auth0, etc.)OIDC_ISSUER_URL=<VALUE>OIDC_CLIENT_ID=<VALUE>OIDC_CLIENT_SECRET=<VALUE>OIDC_REDIRECT_URI=<VALUE>
# Super AdminsSUPER_ADMIN_EMAILS=<VALUE>Validation
Section titled “Validation”The backend validates all required environment variables on startup. If any are missing or invalid, the server will not start.
Validation checks:
- Required variables are present
- URLs are valid (no trailing slashes)
- JWT secrets are at least 32 characters
- Database URL includes replica set
- CORS origins are not
*in production
Example error:
Error: Missing required environment variable: JWT_SECRETError: CORS_ORIGIN cannot be '*' in productionError: JWT_SECRET must be at least 32 charactersSecurity Best Practices
Section titled “Security Best Practices”- Never commit secrets — Use
.envfiles (gitignored) or secret management tools - Rotate secrets regularly — Change JWT secrets, API keys every 90 days
- Use strong secrets — Generate with
openssl rand -hex 32 - Restrict CORS — Never use
*in production - Enable authentication — Set Redis password, MongoDB auth
- Monitor access — Log all secret access in your vault system
- Separate environments — Different secrets for dev/staging/production
- Limit permissions — Use least-privilege IAM roles for S3, database
- Encrypt at rest — Enable encryption for database, Redis, S3
- Audit regularly — Review who has access to secrets
Troubleshooting
Section titled “Troubleshooting”Missing environment variable
Section titled “Missing environment variable”Error: Missing required environment variable: DATABASE_URLSolution: Add the variable to your .env file or deployment configuration.
Invalid database URL
Section titled “Invalid database URL”Error: DATABASE_URL must include replicaSet parameterSolution: Add ?replicaSet=rs0 to your MongoDB connection string.
CORS errors in production
Section titled “CORS errors in production”Access to fetch at 'https://api.your-domain.com' from origin 'https://app.your-domain.com' has been blocked by CORS policySolution: Add https://app.your-domain.com to CORS_ORIGIN.
JWT secret too short
Section titled “JWT secret too short”Error: JWT_SECRET must be at least 32 charactersSolution: Generate a new secret: openssl rand -hex 32
S3 connection errors
Section titled “S3 connection errors”Error: Could not connect to S3 endpointSolution: Verify S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY are correct. For MinIO, ensure S3_FORCE_PATH_STYLE=true.
Redis connection errors
Section titled “Redis connection errors”Error: Redis connection refusedSolution: Verify REDIS_URL is correct and Redis is running. Check firewall rules.
Reference
Section titled “Reference”For more details on specific services: