Skip to main content

Environment Variables

All environment variables are set in the Coolify UI for deployed environments. Never committed to the repository.

API (apps/api)

Required — will not start without these

VariableExampleNotes
DATABASE_URLpostgresql://user:pass@host:5432/pcmr_productionPostgreSQL connection string
BETTER_AUTH_SECRET(32+ random bytes, hex)Session encryption. Must match web app.
BETTER_AUTH_URLhttps://pcmr.grFrontend public URL — not the API URL. Better Auth builds email links from this.
FRONTEND_URLhttps://pcmr.grUsed for CORS and email links

Auth & OAuth

VariableNotes
COOKIE_DOMAINEmpty string for local dev. .pcmr.gr for prod (leading dot = all subdomains). Staging: .staging.pcmr.gr
GOOGLE_CLIENT_IDGoogle OAuth app credentials
GOOGLE_CLIENT_SECRETGoogle OAuth app credentials

Payment (Viva Smart Checkout)

VariableDemo valueProduction value
VIVA_BASE_URLhttps://demo-api.vivapayments.comhttps://api.vivapayments.com
VIVA_AUTH_URLhttps://demo-accounts.vivapayments.comhttps://accounts.vivapayments.com
VIVA_CLIENT_IDFrom Viva demo accountFrom Viva production account
VIVA_CLIENT_SECRETFrom Viva demo accountFrom Viva production account
VIVA_SOURCE_CODE4-digit code from VivaDifferent code for production
VIVA_WEBHOOK_KEYFrom Viva webhook configFrom Viva webhook config

All 5 Viva vars are validated at startup — missing any one will crash the API.

File Storage (Hetzner Object Storage)

VariableStagingProduction
STORAGE_ENDPOINThttps://nbg1.your-objectstorage.comhttps://hel1.your-objectstorage.com
STORAGE_BUCKETmneme-stagingmneme
STORAGE_REGIONnbg1hel1
STORAGE_ACCESS_KEYStaging keyProduction key
STORAGE_SECRET_KEYStaging secretProduction secret
STORAGE_PRESIGN_EXPIRY3600 (default)3600 (default)

Email (Zoho SMTP)

VariableValueNotes
SMTP_HOSTsmtppro.zoho.eu
SMTP_PORT587STARTTLS
SMTP_SECUREfalseDon't use SSL on connect for port 587
SMTP_USER[email protected]Full Zoho email address
SMTP_PASS(Zoho password)Rotate if compromised
EMAIL_FROMPCMR.gr <[email protected]>Display name + address
STAFF_NOTIFY_EMAIL[email protected]Receives quote-accepted notifications
WAITLIST_NOTIFY_EMAIL[email protected]Comma-separated; receives waitlist entries

Observability

VariableNotes
LOKI_URLhttp://10.1.0.4:3100 (Olympus network)
LOKI_HOSTSame as LOKI_URL
GLITCHTIP_DSNFrom GlitchTip project settings

Optional / Defaults

VariableDefaultNotes
PORT3001NestJS listen port
NODE_ENVdevelopmentSet to production in deployed containers

Web (apps/web)

Build-time (inlined by Next.js — must be set as Docker build args)

VariableExampleNotes
NEXT_PUBLIC_API_URLhttps://api.pcmr.grBrowser-side API base URL. Do not use internal hostname here.
NEXT_PUBLIC_GLITCHTIP_DSN(from GlitchTip)Client-side error tracking. Must be the public URL, not the internal Iris hostname.
NEXT_PUBLIC_VIVA_CHECKOUT_URLhttps://demo.vivapayments.comCheckout redirect base. Defaults to demo if unset.

Runtime (server-side only)

VariableExampleNotes
API_URLhttp://10.1.0.1:3001Internal NestJS URL for server-side fetches. Avoids public network / Cloudflare round-trip.
BETTER_AUTH_SECRET(same as API)Must match API's BETTER_AUTH_SECRET exactly.
BETTER_AUTH_URLhttps://api.pcmr.grUsed for server-side session validation (getServerSession()). Set to the API's public URL here (opposite of the API setting).
GLITCHTIP_DSN(from GlitchTip)Server-side error tracking DSN

Common Mistakes

BETTER_AUTH_SECRET mismatch

If the API and web have different BETTER_AUTH_SECRET values, session cookies signed by one cannot be validated by the other. Symptom: all users appear logged out, getSession returns null.

NEXT_PUBLIC_API_URL pointing to internal hostname

NEXT_PUBLIC_API_URL is embedded in the client-side JavaScript bundle. If set to http://10.1.0.1:3001, browser requests will fail because users can't reach the private WireGuard IP. Always use the public domain.

NEXT_PUBLIC_GLITCHTIP_DSN using internal hostname

Same issue — client-side code runs in the browser. Use the public GlitchTip URL (e.g., https://glitchtip.ctsolutions.gr/...), not the internal Iris address.

Browsers reject Domain=localhost. Leave COOKIE_DOMAIN empty for local development — the cookie will be set without a domain restriction and work on localhost.

BETTER_AUTH_URL confusion

The naming is confusing:

  • API container: Set to the frontend URL (https://pcmr.gr) — Better Auth uses it to build email verification links
  • Web container: Set to the API URL (https://api.pcmr.gr) — getServerSession() calls this URL

Wrong Viva base URL

VIVA_BASE_URL must be demo-api.vivapayments.com for demo (not demo.vivapayments.com). The checkout page URL is separate and used only for browser redirects.