# ============================================================================= # PROJECT CONFIGURATION # ============================================================================= # Quoting: values are read by Node (util.parseEnv), Docker Compose and Vite. # Plain letters/digits/._/:@+=,%- need no quotes; wrap anything with spaces, # '#' or a single quote in double quotes; wrap ", \ or $ in single quotes. # Never put $ in a VITE_* value: Vite expands it in the browser build. # vibecarbon configure writes this form for you. # Project name (machine slug: Docker container names, network, pooler tenant) PROJECT_NAME="your-project-name" # Human-facing name (browser titles, PWA manifest, email sender). Substituted # into template files at create time; kept here so `vibecarbon upgrade` can # re-substitute it into updated template files. PROJECT_DISPLAY_NAME="Your Project Name" # ============================================================================= # SUPABASE CONFIGURATION # ============================================================================= # Supabase URLs (configured automatically by docker-compose) SUPABASE_URL="http://localhost:8000" VITE_SUPABASE_URL="http://localhost:8000" # Supabase Keys (generated during bootstrap) SUPABASE_ANON_KEY="your-anon-key-here" VITE_SUPABASE_ANON_KEY="your-anon-key-here" SUPABASE_SERVICE_ROLE_KEY="your-service-role-key-here" # Site URL (your public domain) SITE_URL="http://localhost:5173" # Public canonical URL baked into the client (og: tags, sitemap). At deploy this # is overridden with your real apex domain; localhost is correct for local dev. VITE_PUBLIC_URL="http://localhost:5173" # ============================================================================= # ADMIN CREDENTIALS # ============================================================================= # Admin user with dashboard access (Grafana, N8N, Metabase, etc.) # Created during project setup via `vibecarbon create` # Your actual credentials are in .env.local (gitignored) ADMIN_EMAIL="admin@example.com" ADMIN_PASSWORD="your-password-here" # ============================================================================= # REVERSE PROXY (optional) # ============================================================================= # Number of trusted reverse-proxy hops in front of the API server. The server # uses this to pick the real client IP out of X-Forwarded-For for rate limiting # and login lockout — only the entry this many positions from the RIGHT is # trusted; everything to its left is treated as attacker-controlled. The default # of 1 matches the standard single-Traefik deployment. Increase it only if you # add another trusted proxy in front (e.g. Cloudflare -> Traefik -> app = 2). TRUSTED_PROXY_HOPS="1" # ============================================================================= # OPERATOR ACCESS (optional) # ============================================================================= # CIDR allowlist for SSH and the external Supavisor pooler ports (see the # CONNECTION POOLING section below). `vibecarbon access` manages this # interactively and persists it to .vibecarbon.json — this var is only a # CI/non-interactive bootstrap fallback and the input the Pulumi IaC programs # read when no persisted list exists yet. # format: comma-separated IPv4/IPv6 addresses or CIDRs like 203.0.113.0/24 ALLOWED_SSH_IPS="" # ============================================================================= # ACME / TLS (optional override) # ============================================================================= # Points at Let's Encrypt staging (or another ACME directory) instead of # production — useful for avoiding LE's production rate limits while # iterating. The deployed server reads this from the shipped .env # (docker-compose.prod.yml's Traefik `--certificatesresolvers…caserver`), so # it belongs HERE, not in .env.local. Absent, deploys default to the # production LE directory. # format: an https:// ACME directory URL ACME_CA_SERVER="" # ============================================================================= # OAUTH PROVIDERS (optional) # ============================================================================= # Self-hosted GoTrue reads these from the environment — docker-compose maps them # to GOTRUE_EXTERNAL_GOOGLE_* / GOTRUE_EXTERNAL_AZURE_*. They are NOT set in # Supabase Studio. Run `vibecarbon configure` (OAuth) to fill them in; deploy # ships them automatically. In the provider console (Google Cloud / Microsoft # Entra) set the authorized redirect URI to /auth/v1/callback. # format: one of true, false GOOGLE_ENABLED="false" # format: ending in .apps.googleusercontent.com GOOGLE_CLIENT_ID="" # format: at least 16 characters GOOGLE_CLIENT_SECRET="" # format: one of true, false MICROSOFT_ENABLED="false" # format: an opaque identifier MICROSOFT_CLIENT_ID="" # format: at least 8 characters MICROSOFT_CLIENT_SECRET="" # format: a tenant UUID, or common/organizations/consumers MICROSOFT_TENANT_ID="" # ============================================================================= # SMTP (optional - used by both Supabase Auth and the app) # ============================================================================= # Get SMTP credentials from your email provider (e.g., Resend, Mailgun, AWS SES). # These credentials are shared: Supabase Auth uses them for auth emails (password # reset, verification) and the app uses them for transactional emails # (welcome, invite, billing). # format: a hostname SMTP_HOST="" # format: 1-65535 SMTP_PORT="587" # format: an opaque identifier SMTP_USER="" # format: at least 8 characters SMTP_PASS="" # format: a valid email address, e.g. admin@example.com SMTP_ADMIN_EMAIL="" # format: an opaque identifier SMTP_SENDER_NAME="Vibecarbon" # Carries GoTrue's value directly (true = SKIP the confirmation email). A # false without working SMTP above 500s every signup, so this defaults true. # format: one of true, false GOTRUE_MAILER_AUTOCONFIRM="true" # ============================================================================= # EXTERNAL SERVICES (optional) # ============================================================================= # Billing provider: "stripe" (default), "paddle", or "polar" # format: one of stripe, paddle, polar BILLING_PROVIDER="stripe" # Stripe (default provider) # format: sk_live_…, sk_test_… or a restricted rk_… key STRIPE_SECRET_KEY="" # format: whsec_… webhook signing secret STRIPE_WEBHOOK_SECRET="" # OPTIONAL — single-tier billing can skip the Starter/Pro price ids it isn't using. # format: an opaque identifier STRIPE_PRICE_STARTER="" # format: an opaque identifier STRIPE_PRICE_PRO="" # Paddle (alternative provider -- set BILLING_PROVIDER="paddle" to use) # API key from Paddle dashboard > Developer Tools > Authentication # Use sandbox for testing, production for live # format: at least 8 characters PADDLE_API_KEY="" # format: at least 8 characters PADDLE_WEBHOOK_SECRET="" # format: one of sandbox, production PADDLE_ENVIRONMENT="sandbox" # OPTIONAL — single-tier billing can skip the Starter/Pro price ids it isn't using. # format: an opaque identifier PADDLE_PRICE_STARTER="" # format: an opaque identifier PADDLE_PRICE_PRO="" # Polar (alternative provider -- set BILLING_PROVIDER="polar" to use) # Access token from Polar dashboard > Settings > Developer # format: at least 16 characters POLAR_ACCESS_TOKEN="" # format: at least 8 characters POLAR_WEBHOOK_SECRET="" # OPTIONAL — only needed if your Polar account manages multiple organizations. # format: an opaque identifier POLAR_ORGANIZATION_ID="" # OPTIONAL — single-tier billing can skip the Starter/Pro price ids it isn't using. # format: an opaque identifier POLAR_PRICE_STARTER="" # format: an opaque identifier POLAR_PRICE_PRO="" # ============================================================================= # OBSERVABILITY (optional - enabled per-environment) # ============================================================================= # Prometheus + Grafana + Loki stack. Install it first with `vibecarbon add # observability` (it is not bundled by default), then enable with: # docker compose -f docker-compose.yml -f docker-compose.observability.yml up -d # Admin login reuses ADMIN_EMAIL / ADMIN_PASSWORD from above # (GF_SECURITY_ADMIN_USER / _PASSWORD in the observability module's compose # file) — no separate Grafana credentials to set. # # GF_SERVER_ROOT_URL — the external URL Grafana builds absolute links from. # Grafana has no host port (H-9): reach it through Traefik. Dev = grafana.localhost; # prod overrides this to https://grafana. automatically. GRAFANA_URL="http://grafana.localhost" # ============================================================================= # OPTIONAL SERVICES (for admin dashboard visibility) # ============================================================================= # These control which services appear in the admin dashboard # Set automatically during project creation based on CLI flags VITE_N8N_ENABLED="false" VITE_METABASE_ENABLED="false" VITE_OBSERVABILITY_ENABLED="false" # ============================================================================= # N8N WORKFLOW AUTOMATION (optional) # ============================================================================= # Enable with: docker compose -f docker-compose.yml -f docker-compose.n8n.yml up -d # Access: http://n8n.localhost (requires super_admin role) # n8n's own Postgres role password is the shared POSTGRES_PASSWORD # (docker-compose.n8n.yml: N8N_DB_PASSWORD=${POSTGRES_PASSWORD}) — no separate # n8n DB credential to set. N8N_ENCRYPTION_KEY="your-n8n-encryption-key-here" N8N_HOST="n8n.localhost" N8N_PROTOCOL="http" N8N_WEBHOOK_URL="http://n8n.localhost/" N8N_EDITOR_BASE_URL="http://n8n.localhost/" # ============================================================================= # METABASE ANALYTICS (optional) # ============================================================================= # Enable with: docker compose -f docker-compose.yml -f docker-compose.metabase.yml up -d # Access: http://metabase.localhost (requires super_admin role) # Metabase's own Postgres role reuses the shared POSTGRES_PASSWORD # (auto-generated into .env.local) — no separate Metabase DB credential to set. METABASE_SITE_URL="http://metabase.localhost" # ============================================================================= # S3 OBJECT STORAGE (required for production) # ============================================================================= # WAL-G backup target — any S3-compatible provider works (Hetzner Object # Storage, DigitalOcean Spaces, AWS S3, ...). Create a bucket and access keys # with your provider, then fill these in. Read directly by docker-compose.yml # (WALG_S3_PREFIX / AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_ENDPOINT / # AWS_REGION) — `vibecarbon deploy` fills these automatically in production. S3_ENDPOINT="https://nbg1.your-objectstorage.com" S3_BUCKET="your-project-storage" S3_REGION="nbg1" # OPTIONAL — a separate bucket for wal-g backups only; defaults to S3_BUCKET # when unset. # S3_BACKUP_BUCKET="" S3_ACCESS_KEY="" S3_SECRET_KEY="" # ============================================================================= # PORT CONFIGURATION (for running multiple projects) # ============================================================================= # Use DEV_PORT_OFFSET to shift all ports by a fixed amount when running # multiple vibecarbon projects simultaneously on the same machine. # # Example: DEV_PORT_OFFSET=100 shifts ports: # - Vite: 5173 → 5273 # - API: 3000 → 3100 # - DB: 5432 → 5532 # - Kong: 8000 → 8100 # - Traefik: 80 → 180 # - Grafana: 3002 → 3102 # - Prometheus: 9190 → 9290 # - Loki: 3100 → 3200 DEV_PORT_OFFSET="0" # Override individual ports (takes precedence over offset): # DEV_VITE_PORT="5173" # DEV_API_PORT="3000" # DEV_DB_PORT="5432" # DEV_KONG_PORT="8000" # DEV_TRAEFIK_PORT="80" # DEV_GRAFANA_PORT="3002" # DEV_PROMETHEUS_PORT="9190" # DEV_LOKI_PORT="3100" # Docker network subnet prefix. Each project's vibecarbon-network claims # .0/24, so two projects on one Docker daemon need different # prefixes or the second fails with "Pool overlaps with other one on this # address space". `vibecarbon up` detects this and writes a free prefix to # .env automatically — set it manually only to pick a specific range. Must # live in .env (not .env.local): docker compose reads .env on every # invocation, keeping bare `docker compose` ops on the same subnet. # DEV_SUBNET_PREFIX="172.30.0" # ============================================================================= # INTERNATIONALIZATION # ============================================================================= # Enabled languages live in app_settings.enabled_languages (admin Settings → # Localization card), not in this env file. Translations live in # src/client/locales/.json. Built-in: en, es, fr, de, pt. # ============================================================================= # ANALYTICS (optional - Plausible) # ============================================================================= # Privacy-friendly analytics. Works with Plausible Cloud or self-hosted. # Sign up at https://plausible.io or self-host: https://plausible.io/docs/self-hosting # # PLAUSIBLE_DOMAIN: your site's domain (e.g., "myapp.com") # PLAUSIBLE_SCRIPT_URL: script URL (default: Plausible Cloud) # - Cloud: https://plausible.io/js/script.js # - Self-hosted: https://analytics.yourdomain.com/js/script.js # format: a hostname VITE_PLAUSIBLE_DOMAIN="" # format: a URL VITE_PLAUSIBLE_SCRIPT_URL="https://plausible.io/js/script.js" # GITHUB STARS BUTTON (optional) # Opt-in: set to a GitHub repo URL to render a stars button in the nav. # Leave empty (default) and the button renders nothing and fetches nothing. # format: a URL VITE_GITHUB_REPO_URL="" # ============================================================================= # ENVIRONMENT # ============================================================================= NODE_ENV="development" PORT="3000" # Environment name shown in Super Admin (set automatically during deployment) # Values: development, qa, staging, prod, etc. VITE_ENVIRONMENT="development" # App version shown in Super Admin (set automatically during deployment) VITE_APP_VERSION="0.1.0" # ============================================================================= # CONNECTION POOLING (Supavisor — production) # ============================================================================= # The internal Supabase stack (auth, rest, realtime, storage, meta) connects to # Postgres DIRECTLY and must stay that way — a transaction pooler breaks # realtime's replication slot, PostgREST's NOTIFY-based schema reload, and # migration advisory locks. # # Supavisor (started by docker-compose.prod.yml) is the pooler for EXTERNAL # direct-DB clients — e.g. a BI tool, a one-off script, or a serverless function # you connect to your database yourself. Nothing internal routes through it. # # Supavisor needs VAULT_ENC_KEY (vault encryption) and REALTIME_SECRET # (SECRET_KEY_BASE, also used by Realtime). `vibecarbon create` AUTO-GENERATES # both into .env.local alongside JWT_SECRET / POSTGRES_PASSWORD — you do not set # them by hand. docker-compose.prod.yml REQUIRES them (`${VAR:?}`): a missing # secret aborts compose loudly instead of booting on a blank value. # # External connection strings (POOLER_TENANT_ID defaults to PROJECT_NAME; the # username carries the tenant id). Port->mode mapping is pinned in # docker-compose.prod.yml (PROXY_PORT_SESSION=5432, PROXY_PORT_TRANSACTION=6543). # The cloud firewall scopes both ports to the operator CIDR allowlist (same # list as SSH) — allowlist a client machine with `vibecarbon access add`: # Session mode (needs prepared statements / LISTEN-NOTIFY / advisory locks): # postgres://postgres.:@:5432/postgres # Transaction mode (short, stateless queries / serverless): # postgres://postgres.:@:6543/postgres # # The pooler is exercised end-to-end on every compose e2e run (tenant routing # through both modes + external reachability through the operator firewall).