# Environment Variables Reference Complete guide to environment variables used in Flyer Crawler. --- ## Quick Reference ### Minimum Required Variables (Development) | Variable | Example | Purpose | | ---------------- | ------------------------ | -------------------- | | `DB_HOST` | `localhost` | PostgreSQL host | | `DB_USER` | `postgres` | PostgreSQL username | | `DB_PASSWORD` | `postgres` | PostgreSQL password | | `DB_NAME` | `flyer_crawler_dev` | Database name | | `REDIS_URL` | `redis://localhost:6379` | Redis connection URL | | `JWT_SECRET` | (32+ character string) | JWT signing key | | `GEMINI_API_KEY` | `AIzaSy...` | Google Gemini API | ### Source of Truth The Zod schema at `src/config/env.ts` is the authoritative source for all environment variables. If a variable is not in this file, it is not used by the application. --- ## Configuration by Environment ### Production | Aspect | Details | | -------- | ------------------------------------------ | | Location | Gitea CI/CD secrets injected at deployment | | Path | `/var/www/flyer-crawler.projectium.com/` | | File | No `.env` file - all from CI/CD secrets | ### Test | Aspect | Details | | -------- | --------------------------------------------- | | Location | Gitea CI/CD secrets + `.env.test` overrides | | Path | `/var/www/flyer-crawler-test.projectium.com/` | | File | `.env.test` for test-specific values | ### Development Container | Aspect | Details | | -------- | --------------------------------------- | | Location | `.env.local` file in project root | | Priority | Overrides defaults in `compose.dev.yml` | | File | `.env.local` (gitignored) | --- ## Complete Variable Reference ### Database Configuration | Variable | Required | Default | Description | | ------------- | -------- | ------- | ----------------- | | `DB_HOST` | Yes | - | PostgreSQL host | | `DB_PORT` | No | `5432` | PostgreSQL port | | `DB_USER` | Yes | - | Database username | | `DB_PASSWORD` | Yes | - | Database password | | `DB_NAME` | Yes | - | Database name | **Environment-Specific Variables** (Gitea Secrets): | Variable | Environment | Description | | ------------------ | ----------- | ------------------------ | | `DB_USER_PROD` | Production | Production database user | | `DB_PASSWORD_PROD` | Production | Production database pass | | `DB_DATABASE_PROD` | Production | Production database name | | `DB_USER_TEST` | Test | Test database user | | `DB_PASSWORD_TEST` | Test | Test database password | | `DB_DATABASE_TEST` | Test | Test database name | ### Redis Configuration | Variable | Required | Default | Description | | ---------------- | -------- | ------- | ------------------------- | | `REDIS_URL` | Yes | - | Redis connection URL | | `REDIS_PASSWORD` | No | - | Redis password (optional) | **URL Format**: `redis://[user:password@]host:port` **Examples**: ```bash # Development (no auth) REDIS_URL=redis://localhost:6379 # Production (with auth) REDIS_URL=redis://:${REDIS_PASSWORD_PROD}@localhost:6379 ``` ### Authentication | Variable | Required | Min Length | Description | | ---------------------- | -------- | ---------- | ----------------------- | | `JWT_SECRET` | Yes | 32 chars | JWT token signing key | | `JWT_SECRET_PREVIOUS` | No | - | Previous key (rotation) | | `GOOGLE_CLIENT_ID` | No | - | Google OAuth client ID | | `GOOGLE_CLIENT_SECRET` | No | - | Google OAuth secret | | `GITHUB_CLIENT_ID` | No | - | GitHub OAuth client ID | | `GITHUB_CLIENT_SECRET` | No | - | GitHub OAuth secret | **Generate Secure Secret**: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ### AI Services | Variable | Required | Description | | ---------------------------- | -------- | -------------------------------- | | `GEMINI_API_KEY` | Yes\* | Google Gemini API key | | `GEMINI_RPM` | No | Rate limit (default: 5) | | `AI_PRICE_QUALITY_THRESHOLD` | No | Quality threshold (default: 0.5) | \*Required for flyer processing. Application works without it but cannot extract flyer data. **Get API Key**: [Google AI Studio](https://aistudio.google.com/app/apikey) **Test Environment Note**: The test/staging environment **deliberately omits** `GEMINI_API_KEY` to preserve free API quota. This is intentional - the API has strict daily limits on the free tier, and we want to reserve tokens for production use. AI features will be non-functional in test, but all other features can be tested normally. Deploy warnings about missing `GEMINI_API_KEY` in test logs are expected and safe to ignore. ### Google Services | Variable | Required | Description | | ---------------------- | -------- | -------------------------------- | | `GOOGLE_MAPS_API_KEY` | No | Google Maps Geocoding API | | `GOOGLE_CLIENT_ID` | No | OAuth (see Authentication above) | | `GOOGLE_CLIENT_SECRET` | No | OAuth (see Authentication above) | ### UPC Lookup APIs | Variable | Required | Description | | ------------------------ | -------- | ---------------------- | | `UPC_ITEM_DB_API_KEY` | No | UPC Item DB API key | | `BARCODE_LOOKUP_API_KEY` | No | Barcode Lookup API key | ### Application Settings | Variable | Required | Default | Description | | -------------- | -------- | ------------- | ------------------------ | | `NODE_ENV` | No | `development` | Environment mode | | `PORT` | No | `3001` | Backend server port | | `FRONTEND_URL` | No | - | Frontend URL (CORS) | | `BASE_URL` | No | - | API base URL | | `STORAGE_PATH` | No | (see below) | Flyer image storage path | **NODE_ENV Values**: `development`, `test`, `staging`, `production` **Default STORAGE_PATH**: `/var/www/flyer-crawler.projectium.com/flyer-images` ### Email/SMTP Configuration | Variable | Required | Default | Description | | ----------------- | -------- | ------- | ----------------------- | | `SMTP_HOST` | No | - | SMTP server hostname | | `SMTP_PORT` | No | `587` | SMTP server port | | `SMTP_USER` | No | - | SMTP username | | `SMTP_PASS` | No | - | SMTP password | | `SMTP_SECURE` | No | `false` | Use TLS | | `SMTP_FROM_EMAIL` | No | - | From address for emails | **Note**: Email functionality degrades gracefully if not configured. ### Worker Configuration | Variable | Default | Description | | ------------------------------------- | ------- | ---------------------------- | | `WORKER_CONCURRENCY` | `1` | Main worker concurrency | | `WORKER_LOCK_DURATION` | `30000` | Lock duration (ms) | | `EMAIL_WORKER_CONCURRENCY` | `10` | Email worker concurrency | | `ANALYTICS_WORKER_CONCURRENCY` | `1` | Analytics worker concurrency | | `CLEANUP_WORKER_CONCURRENCY` | `10` | Cleanup worker concurrency | | `WEEKLY_ANALYTICS_WORKER_CONCURRENCY` | `1` | Weekly analytics concurrency | ### Error Tracking (Bugsink/Sentry) | Variable | Required | Default | Description | | --------------------- | -------- | -------- | ------------------------------- | | `SENTRY_DSN` | No | - | Backend Sentry DSN | | `SENTRY_ENABLED` | No | `true` | Enable error tracking | | `SENTRY_ENVIRONMENT` | No | NODE_ENV | Environment name for errors | | `SENTRY_DEBUG` | No | `false` | Enable Sentry SDK debug logging | | `VITE_SENTRY_DSN` | No | - | Frontend Sentry DSN | | `VITE_SENTRY_ENABLED` | No | `true` | Enable frontend error tracking | | `VITE_SENTRY_DEBUG` | No | `false` | Frontend SDK debug logging | **DSN Format**: `http://[key]@[host]:[port]/[project_id]` **Dev Container DSNs**: ```bash # Backend (internal) SENTRY_DSN=http://@localhost:8000/1 # Frontend (via nginx proxy) VITE_SENTRY_DSN=https://@localhost/bugsink-api/2 ``` --- ## Configuration Files | File | Purpose | | ------------------------------------- | ------------------------------------------- | | `src/config/env.ts` | Zod schema validation - **source of truth** | | `ecosystem.config.cjs` | PM2 process manager (production) | | `ecosystem.dev.config.cjs` | PM2 process manager (development) | | `.gitea/workflows/deploy-to-prod.yml` | Production deployment workflow | | `.gitea/workflows/deploy-to-test.yml` | Test deployment workflow | | `.env.example` | Template with all variables | | `.env.local` | Dev container overrides (not in git) | | `.env.test` | Test environment overrides (not in git) | --- ## Adding New Variables ### Checklist 1. [ ] **Update Zod Schema** - Edit `src/config/env.ts` 2. [ ] **Add to Gitea Secrets** - For prod/test environments 3. [ ] **Update Deployment Workflows** - `.gitea/workflows/*.yml` 4. [ ] **Update PM2 Config** - `ecosystem.config.cjs` 5. [ ] **Update .env.example** - Template for developers 6. [ ] **Update this document** - Add to appropriate section ### Step-by-Step #### 1. Update Zod Schema Edit `src/config/env.ts`: ```typescript const envSchema = z.object({ // ... existing variables ... newSection: z.object({ newVariable: z.string().min(1, 'NEW_VARIABLE is required'), }), }); // In loadEnvVars(): newSection: { newVariable: process.env.NEW_VARIABLE, }, ``` #### 2. Add to Gitea Secrets 1. Go to Gitea repository Settings > Secrets 2. Add `NEW_VARIABLE` with production value 3. Add `NEW_VARIABLE_TEST` if test needs different value #### 3. Update Deployment Workflows Edit `.gitea/workflows/deploy-to-prod.yml`: ```yaml env: NEW_VARIABLE: ${{ secrets.NEW_VARIABLE }} ``` Edit `.gitea/workflows/deploy-to-test.yml`: ```yaml env: NEW_VARIABLE: ${{ secrets.NEW_VARIABLE_TEST }} ``` #### 4. Update PM2 Config Edit `ecosystem.config.cjs`: ```javascript module.exports = { apps: [ { env: { NEW_VARIABLE: process.env.NEW_VARIABLE, }, }, ], }; ``` --- ## Security Best Practices ### Do - Use Gitea Secrets for prod/test - Use `.env.local` for dev (gitignored) - Generate secrets with cryptographic randomness - Rotate secrets regularly - Use environment-specific database users ### Do Not - Commit secrets to git - Use short or predictable secrets - Share secrets across environments - Log sensitive values ### Secret Generation ```bash # Generate secure random secrets (64 hex characters) node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # Example output: # a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 ``` ### Database Users by Environment | Environment | User | Database | | ----------- | -------------------- | -------------------- | | Production | `flyer_crawler_prod` | `flyer-crawler-prod` | | Test | `flyer_crawler_test` | `flyer-crawler-test` | | Development | `postgres` | `flyer_crawler_dev` | --- ## Validation Environment variables are validated at startup via `src/config/env.ts`. ### Startup Validation If validation fails, you will see: ```text ╔════════════════════════════════════════════════════════════════╗ ║ CONFIGURATION ERROR - APPLICATION STARTUP ║ ╚════════════════════════════════════════════════════════════════╝ The following environment variables are missing or invalid: - database.host: DB_HOST is required - auth.jwtSecret: JWT_SECRET must be at least 32 characters Please check your .env file or environment configuration. ``` ### Debugging Configuration ```bash # Check what variables are set (dev container) podman exec flyer-crawler-dev env | grep -E "^(DB_|REDIS_|JWT_|SENTRY_)" # Test database connection podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "SELECT 1;" # Test Redis connection podman exec flyer-crawler-redis redis-cli ping ``` --- ## Troubleshooting ### Variable Not Found ```text Error: Missing required environment variable: JWT_SECRET ``` **Solutions**: 1. Check `.env.local` exists and has the variable 2. Verify variable name matches schema exactly 3. Restart the application after changes ### Invalid Value ```text Error: JWT_SECRET must be at least 32 characters ``` **Solution**: Generate a longer secret value. ### Wrong Environment Selected Check `NODE_ENV` is set correctly: | Value | Purpose | | ------------- | ---------------------- | | `development` | Local dev container | | `test` | CI/CD test server | | `staging` | Pre-production testing | | `production` | Production server | ### Database Connection Issues ```bash # Development podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "SELECT 1;" # If connection fails, check: # 1. Container is running: podman ps # 2. DB_HOST matches container network # 3. DB_PASSWORD is correct ``` --- ## Related Documentation - [QUICKSTART.md](QUICKSTART.md) - Quick setup guide - [INSTALL.md](INSTALL.md) - Detailed installation - [DEV-CONTAINER.md](../development/DEV-CONTAINER.md) - Dev container setup - [DEPLOYMENT.md](../operations/DEPLOYMENT.md) - Production deployment - [AUTHENTICATION.md](../architecture/AUTHENTICATION.md) - OAuth setup - [ADR-007](../adr/0007-configuration-and-secrets-management.md) - Configuration decisions --- Last updated: January 2026