# PostgreSQL database used by the backend. # Change POSTGRES_PASSWORD before deploying. POSTGRES_DB=freelingo POSTGRES_USER=freelingo POSTGRES_PASSWORD=CHANGE_ME_DB_PASSWORD # Host directory used by docker-compose bind mounts for PostgreSQL, Redis, avatars, and generated audio. DATA_PATH=/opt/docker/freelingo # Redis password used by the Redis container and backend REDIS_URL. # Change this before deploying. REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD # JWT signing secret. Generate with: openssl rand -hex 32 SECRET_KEY=CHANGE_ME_SECRET_KEY # Allowed browser origins as a JSON array. Add your production frontend URL. CORS_ORIGINS=["http://localhost:3000"] # Set true only when serving over HTTPS so refresh-token cookies use the Secure flag. COOKIE_SECURE=false # Email / SMTP # Set EMAIL_ENABLED=true and configure SMTP to send verification and reset emails. # Works with any SMTP provider: Gmail (app password), Brevo, Resend, Google Workspace, etc. EMAIL_ENABLED=false # Destination address for the in-app contact form submissions CONTACT_EMAIL=your@email.com SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER= SMTP_PASSWORD= SMTP_FROM=noreply@freelingo.app SMTP_TLS=true SMTP_SSL=false # Public base URL used in email links (no trailing slash) APP_BASE_URL=https://custom-domain.com # Public registration controls. ALLOW_REGISTRATION=true FIRST_USER_IS_ADMIN=true # Blocked email domains as a JSON array, useful for disposable/temporary email providers. BLOCKED_EMAIL_DOMAINS=["yopmail.com","mailinator.com","guerrillamail.com","temp-mail.org","throwaway.email","trashmail.com"] # LLM (ollama, openai, anthropic, deepseek) LLM_PROVIDER=ollama # Total time budget for Listening/Reading generation, including JSON recovery and audio. EXERCISE_GENERATION_TIMEOUT_SECONDS=600 # Ollama (if LLM_PROVIDER=ollama) OLLAMA_BASE_URL=http://host.docker.internal:11434 OLLAMA_MODEL=gemma4:e4b # OpenAI (if LLM_PROVIDER=openai) OPENAI_API_KEY= OPENAI_MODEL=gpt-5.4-mini # Anthropic (if LLM_PROVIDER=anthropic) ANTHROPIC_API_KEY= ANTHROPIC_MODEL=claude-4-5-haiku ANTHROPIC_MAX_TOKENS=8192 # DeepSeek (if LLM_PROVIDER=deepseek) DEEPSEEK_API_KEY= DEEPSEEK_MODEL=deepseek-v4-flash # TTS — set TTS_PROVIDER=local (Kokoro) or openai # Local: requires the kokoro service running in docker-compose # OpenAI: uses OPENAI_API_KEY above, no local service needed TTS_PROVIDER=local TTS_BASE_URL=http://kokoro:8880 TTS_VOICE=af_heart OPENAI_TTS_MODEL=tts-1 OPENAI_TTS_VOICE=nova OPENAI_TTS_SPEED=1.0 # STT — set STT_PROVIDER=local (faster-whisper) or openai # Local: requires the whisper service running in docker-compose # OpenAI: uses OPENAI_API_KEY above, no local service needed STT_PROVIDER=local STT_BASE_URL=http://whisper:9000 STT_MODEL=large-v3-turbo STT_ENGINE=faster_whisper OPENAI_STT_MODEL=whisper-1 # Logging # Levels: DEBUG, INFO, WARNING, ERROR. INFO is the recommended default. # Use DEBUG to see individual audio chunk sizes and TTS bytes (verbose). LOG_LEVEL=INFO # Uvicorn worker processes # Rule of thumb: 2 × CPU cores + 1. On a 2-core VPS set 5, on a 4-core set 9. # Keep at 1 for local development or low-resource machines. UVICORN_WORKERS=4 # Default AI usage quotas for new users and for users whose subscription becomes active/trialing. # Quota values support 0 as unlimited where noted below. # Must match supported settings UI values: 900 or 1800 seconds. DEFAULT_CONVERSATION_MAX_DURATION=1800 # Must match supported settings UI values: 60, 180, or 300 seconds. DEFAULT_CONVERSATION_INACTIVITY_TIMEOUT=180 # Weekly voice conversation session count. 0 = unlimited. DEFAULT_CONVERSATION_WEEKLY_SESSIONS=0 # Daily and weekly voice conversation minute limits. 0 = unlimited. DEFAULT_CONVERSATION_DAILY_MINUTES=30 DEFAULT_CONVERSATION_WEEKLY_MINUTES=90 # Monthly LLM token limit. 0 = unlimited. DEFAULT_MONTHLY_TOKENS_LIMIT=1000000 # One-time post-assessment voice demo duration for unsubscribed hosted users, in seconds. ASSESSMENT_VOICE_TRIAL_DURATION_SECONDS=300 # Freemium quotas for free users when STRIPE_ENABLED=true. # A value of 0 blocks the feature entirely for free users. FREEMIUM_CHAT_DAILY_MESSAGES=5 FREEMIUM_LESSONS_DAILY=3 FREEMIUM_LISTENING_WEEKLY=3 FREEMIUM_READING_WEEKLY=3 FREEMIUM_VOICE_WEEKLY_MINUTES=5 FREEMIUM_TRIAL_ENABLED=true FREEMIUM_TRIAL_DAYS=7 # Target languages offered by the deployment, as a JSON array of BCP-47 codes. # Supported curriculum languages: "de-DE", "en-GB", "en-US", "es-ES", "fr-FR", "it-IT", "ja-JP", "ko-KR", "pt-PT", "zh-CN". # Unknown or unsupported codes are ignored by the backend. AVAILABLE_TARGET_LANGUAGES=["de-DE","en-GB","en-US","es-ES","fr-FR","it-IT","ja-JP","ko-KR","pt-PT","zh-CN"] # Stripe (It isn't necessary to set these for local development or self-hosted deployments, only if you want to enable the subscription paywall in production) # Set STRIPE_ENABLED=true to activate the subscription paywall. # When false (default), all billing features are hidden and no paywall is shown. # Self-hosted deployments should leave this false. STRIPE_ENABLED=false # Stripe secret key — use sk_test_... for development, sk_live_... for production STRIPE_SECRET_KEY= # Webhook signing secret (whsec_...) — from Stripe Dashboard → Webhooks STRIPE_WEBHOOK_SECRET= # Price IDs from Stripe Dashboard → Product catalog. # Required when STRIPE_ENABLED=true; the app does not create Stripe products or prices. STRIPE_PRICE_MONTHLY= STRIPE_PRICE_YEARLY= # Trial period in days. Set to 0 to disable trial. STRIPE_TRIAL_DAYS=7 # Public base URL used in Stripe return URLs (no trailing slash) # Must match the domain configured in Stripe Dashboard → Webhooks STRIPE_BASE_URL=https://custom-domain.com # Public pricing copy shown in the frontend pricing UI. # STRIPE_PRICE_* above are the authoritative billing Price IDs; these values are display-only. PRICE_MONTHLY=14.95 PRICE_YEARLY=149.50 TOTAL_PRICE_MONTHLY=19.95 TOTAL_PRICE_YEARLY=199.50 # Umami Analytics (self-hosted, privacy-friendly) NEXT_PUBLIC_UMAMI_SCRIPT_URL= NEXT_PUBLIC_UMAMI_WEBSITE_ID=