Skip to main content
RestAI uses environment variables for configuration across both the API server and web frontend. This page documents every variable, its purpose, default values, and security requirements.

Critical Security Variables

These variables are REQUIRED for production deployment. The API will refuse to start if they are missing or use default values.

JWT_SECRET

Type: String (required)
Used by: API server
Purpose: Secret key for signing access tokens (15-minute expiry)
JWT_SECRET=your-random-64-character-secret-here
Security impact: If this secret is compromised or uses a weak default value, attackers can forge access tokens and impersonate any user in the system. See the Security Best Practices documentation for details on Phase A security fixes.Generate a strong secret:
openssl rand -base64 48

JWT_REFRESH_SECRET

Type: String (required)
Used by: API server
Purpose: Secret key for signing refresh tokens (7-day expiry)
JWT_REFRESH_SECRET=your-different-random-64-character-secret
Must be different from JWT_SECRET. If refresh tokens are compromised, attackers can generate new access tokens indefinitely until the refresh token expires.Generate a separate secret:
openssl rand -base64 48

POSTGRES_PASSWORD

Type: String (required)
Used by: PostgreSQL, API server
Purpose: Password for the PostgreSQL database user
POSTGRES_PASSWORD=change-me-in-production
The default value fenrinegro is used in development. Never use this in production. Use a strong, randomly generated password.

Database Configuration

DATABASE_URL

Type: PostgreSQL connection string
Used by: API server, migration scripts
Purpose: Full connection string to PostgreSQL database
DATABASE_URL=postgresql://user:password@host:5432/database
# In docker-compose.yml, this is constructed automatically:
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/restai

POSTGRES_USER

Type: String
Used by: PostgreSQL
Default: postgres
POSTGRES_USER=restai
The PostgreSQL superuser name. Only relevant when using the Docker Compose postgres service.

Redis Configuration

REDIS_URL

Type: Redis connection string
Used by: API server
Purpose: Connection to Redis for caching and session management
REDIS_URL=redis://localhost:6379
REDIS_URL=redis://redis:6379
In docker-compose.yml:51, this is hardcoded to redis://redis:6379. Override this only when running outside Docker or using an external Redis instance.

API Server Configuration

API_PORT

Type: Number
Used by: API server
Default: 3001
API_PORT=3001
The port on which the API server listens. The Docker container exposes this port (docker-compose.yml:69).

CORS_ORIGINS

Type: Comma-separated list of URLs
Used by: API server
Default: http://localhost:3000 (development only)
CORS_ORIGINS=https://app.yourrestaurant.com
Critical for production security. This variable controls which frontend origins can make authenticated requests to the API.Incorrect configuration causes:
  • CORS_ORIGINS not set → API blocked in production (PHASE-A-CHANGELOG.md:56-78)
  • CORS_ORIGINS=* → Any malicious site can make requests with user credentials
Production examples:
# Single domain
CORS_ORIGINS=https://app.yourrestaurant.com

# Multiple domains (staging + production)
CORS_ORIGINS=https://staging.yourrestaurant.com,https://app.yourrestaurant.com

LOG_LEVEL

Type: String
Used by: API server
Default: info
Options: debug, info, warn, error
LOG_LEVEL=warn
Controls verbosity of server logs. Use debug for development, warn or error for production.

Cloudflare R2 Storage

RestAI uses Cloudflare R2 for file storage (menu images, receipts, etc.). All R2 variables are optional but required for file upload functionality.

R2_ACCOUNT_ID

Type: String (optional)
Used by: API server
R2_ACCOUNT_ID=your-cloudflare-account-id
Your Cloudflare account ID. Found in the R2 dashboard URL: https://dash.cloudflare.com/{account_id}/r2

R2_ACCESS_KEY_ID

Type: String (optional)
Used by: API server
R2_ACCESS_KEY_ID=your-r2-access-key
R2 API token access key. Generate from Cloudflare dashboard → R2 → Manage R2 API Tokens.

R2_SECRET_ACCESS_KEY

Type: String (optional)
Used by: API server
R2_SECRET_ACCESS_KEY=your-r2-secret-key
R2 API token secret key (shown only once when creating the token).
Store this securely. If compromised, regenerate the token immediately in Cloudflare dashboard.

R2_BUCKET_NAME

Type: String
Used by: API server
Default: restai
R2_BUCKET_NAME=restai-production
The name of your R2 bucket. Must exist before starting the API.

R2_PUBLIC_URL

Type: URL (optional)
Used by: API server
R2_PUBLIC_URL=https://cdn.yourrestaurant.com
Public URL for accessing uploaded files. If not set, files use R2’s default URL format. Configure a custom domain in Cloudflare for better branding and control.
When R2_PUBLIC_URL is set, all file URLs returned by the API will use this domain instead of the default *.r2.dev URL.

Web Application Configuration

These variables are build-time only for the Next.js web app. They are baked into the client-side JavaScript bundle and cannot be changed after the build.

NEXT_PUBLIC_API_URL

Type: URL (build-time)
Used by: Web frontend
Default: http://localhost:3001 (development)
NEXT_PUBLIC_API_URL=https://api.yourrestaurant.com
The base URL for API requests from the web frontend. This must point to your production API server.
NEXT_PUBLIC_API_URL=http://localhost:3001
Build-time override:
# In Dockerfile.web, change line 24:
ENV NEXT_PUBLIC_API_URL=https://api.yourrestaurant.com
Or use Docker build args:
docker build --build-arg NEXT_PUBLIC_API_URL=https://api.yourrestaurant.com -f Dockerfile.web .

NEXT_PUBLIC_WS_URL

Type: WebSocket URL (build-time)
Used by: Web frontend
Default: ws://localhost:3001 (development)
NEXT_PUBLIC_WS_URL=wss://api.yourrestaurant.com
The WebSocket URL for real-time updates (orders, kitchen status, notifications).
Use wss:// (secure WebSocket) in production, not ws://. Most browsers block insecure WebSocket connections from HTTPS pages.
NEXT_PUBLIC_WS_URL=ws://localhost:3001

Complete .env.example Reference

Here’s the complete .env.example file with all variables documented:
# ── PostgreSQL ────────────────────────────────────────────────────────
POSTGRES_USER=restai
POSTGRES_PASSWORD=change-me-in-production

# ── API Server ────────────────────────────────────────────────────────
API_PORT=3001
JWT_SECRET=change-me-in-production
JWT_REFRESH_SECRET=change-me-in-production
LOG_LEVEL=info
CORS_ORIGINS=http://localhost:3000

# ── Database (composed automatically in docker-compose) ───────────────
# Only needed when running outside Docker:
# DATABASE_URL=postgresql://restai:change-me-in-production@localhost:5432/restai

# ── Redis (hardcoded in docker-compose as redis://redis:6379) ─────────
# Only needed when running outside Docker:
# REDIS_URL=redis://localhost:6379

# ── Cloudflare R2 Storage ────────────────────────────────────────────
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET_NAME=restai
R2_PUBLIC_URL=

# ── Web App (build-time — set as Build Variable in Coolify) ──────────
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_WS_URL=ws://localhost:3001

Environment Variable Checklist

Before deploying to production, verify:
1

Security variables set

  • JWT_SECRET is set to a strong random value (not default)
  • JWT_REFRESH_SECRET is set to a different strong random value
  • POSTGRES_PASSWORD is changed from default
  • Secrets are stored securely (not committed to git)
2

CORS configured correctly

  • CORS_ORIGINS matches your production domain(s)
  • Multiple domains are comma-separated with no spaces
  • Uses HTTPS URLs (not HTTP)
3

API URLs in web build

  • NEXT_PUBLIC_API_URL points to production API
  • NEXT_PUBLIC_WS_URL uses wss:// (secure WebSocket)
  • Both URLs match your actual domain
4

Optional features configured

  • R2 storage variables set if using file uploads
  • LOG_LEVEL set to warn or error for production
  • Database connection strings use SSL if required

Troubleshooting

API fails to start with “JWT_SECRET required”

Cause: JWT_SECRET or JWT_REFRESH_SECRET environment variables are missing. Fix: Set both variables in your .env file:
JWT_SECRET=$(openssl rand -base64 48)
JWT_REFRESH_SECRET=$(openssl rand -base64 48)
See apps/api/src/lib/jwt.ts:23-28 for the validation logic.

CORS errors in production

Symptoms: Browser console shows CORS policy: No 'Access-Control-Allow-Origin' header Cause: CORS_ORIGINS doesn’t include your frontend domain. Fix: Update CORS_ORIGINS in your .env:
CORS_ORIGINS=https://app.yourrestaurant.com
Restart the API server after changing this variable.

Web app can’t connect to API in production

Symptoms: All API requests fail with network errors Cause: NEXT_PUBLIC_API_URL still points to localhost or incorrect domain. Fix: Rebuild the web Docker image with the correct URL:
# Edit Dockerfile.web line 24
ENV NEXT_PUBLIC_API_URL=https://api.yourrestaurant.com

# Rebuild
docker-compose build --no-cache web
docker-compose up -d web

File uploads fail

Symptoms: Menu images or receipts can’t be uploaded Cause: R2 credentials not configured or incorrect. Fix: Verify all R2 variables are set:
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_BUCKET_NAME=restai-production
Check the bucket exists in Cloudflare R2 dashboard.

Security Best Practices

  1. Never commit .env files - Add .env to .gitignore
  2. Use different secrets per environment - Development and production should have completely different JWT secrets
  3. Rotate secrets regularly - Change JWT secrets every 90 days
  4. Restrict CORS origins - Only list domains you control
  5. Use environment variable managers - Consider tools like HashiCorp Vault, AWS Secrets Manager, or Doppler for production
  6. Enable SSL for external databases - Add ?sslmode=require to DATABASE_URL
  7. Audit logs regularly - Set LOG_LEVEL=info in production to track authentication attempts

Build docs developers (and LLMs) love