Configuration reference
Customer Portal reads deployment settings from the process environment. Copy .env.example for local development, but provide production values through the hosting platform or secret manager.
Environment variables
| Variable | Required | Default | Meaning |
|---|---|---|---|
DATABASE_URL | Yes | None | PostgreSQL connection string used by the shared Drizzle client and migrations. |
PUBLIC_URL | Production | http://localhost:3051 in .env.example | Public origin used to construct application links and as the fallback Better Auth origin. |
BETTER_AUTH_URL | Production | Falls back to PUBLIC_URL | Canonical Better Auth server and client origin. It must address the same deployed portal. |
BETTER_AUTH_SECRET | Yes | None in production | High-entropy secret used by Better Auth for encryption, signing, and hashing. Use at least 32 characters and keep the same value across application instances. |
ADMIN_EMAILS | No | Empty | Comma-separated, case-insensitive email allowlist for system administrators. Whitespace is trimmed. |
RESEND_API_KEY | Email flows | Empty | Platform Resend credential for authentication messages such as verification and password reset. |
RESEND_FROM_EMAIL | Email flows | Empty | Sender used with the platform Resend credential. |
PORTAL_GITHUB_ENABLED | No | true | Shows and configures GitHub sign-in when both GitHub credentials are also present. |
GITHUB_CLIENT_ID | GitHub sign-in | Empty | GitHub OAuth client identifier. |
GITHUB_CLIENT_SECRET | GitHub sign-in | Empty | GitHub OAuth client secret. |
PORTAL_GOOGLE_ENABLED | No | true | Shows and configures Google sign-in when both Google credentials are also present. |
GOOGLE_CLIENT_ID | Google sign-in | Empty | Google OAuth client identifier. |
GOOGLE_CLIENT_SECRET | Google sign-in | Empty | Google OAuth client secret. |
PORTAL_REGISTRATION_MODE | No | open | Account policy: open, invitation-only, or disabled. Invalid values fall back to open. |
PORTAL_TERMS_URL | No | / | Link shown from authentication forms for the deployment's Terms of Service. |
The provider flags use strict string parsing: only the literal value true enables a flag. Set a provider flag to false when the deployment does not configure that provider.
Generate BETTER_AUTH_SECRET with openssl rand -base64 32; never copy a documentation placeholder into a deployment. See the Better Auth secret reference for the upstream contract.
Runtime variables
These variables belong to Node or the generated Nitro server rather than a Customer Portal feature:
| Variable | Production behavior |
|---|---|
NODE_ENV | Set to production by the Docker image and normal production runtimes. Customer Portal uses it to avoid retaining the development database singleton. |
HOST | Bind address for the generated server. The Docker image sets 0.0.0.0; use a loopback address only when a local proxy is the sole caller. |
PORT | Listening port for the generated server. Nitro uses port 3000 unless the runtime overrides it. |
URL precedence
The authentication server resolves its origin in this order:
BETTER_AUTH_URL;PUBLIC_URL;- the local development fallback.
Set both URL variables to the same HTTPS origin in production. A mismatch can produce incorrect email links, rejected OAuth callbacks, or session cookies attached to the wrong host. Do not add a trailing path.
PUBLIC_URL=https://portal.example.com
BETTER_AUTH_URL=https://portal.example.com
Registration and administrators
PORTAL_REGISTRATION_MODE controls who may create an account:
| Value | Behavior |
|---|---|
open | Anyone can use the signup flow. |
invitation-only | Signup is available only in an organization invitation flow. |
disabled | Public signup is unavailable; existing users may still sign in. |
ADMIN_EMAILS grants the system-wide administrator role when a listed account registers or signs in. This is separate from the owner, admin, and member roles inside an organization. Limit this list to accounts that genuinely administer the whole installation.
Email ownership
The Resend environment variables belong to the platform and send authentication mail. Organization-owned transactional mail is configured in the portal UI and stored per organization. The timesheets module uses that organization credential for invoices and reminders, so changing the platform Resend key does not replace an organization's sender.
Secret handling
Keep these server-only values out of source control, client runtime configuration, logs, screenshots, and issue reports:
DATABASE_URL;BETTER_AUTH_SECRET;RESEND_API_KEY;GITHUB_CLIENT_SECRET;GOOGLE_CLIENT_SECRET;- organization email-provider credentials.
Client IDs and public origins are identifiers rather than secrets, but should still be specific to the intended deployment.
Do not rotate BETTER_AUTH_SECRET by simply replacing it on running instances: existing encrypted or signed auth state may depend on the prior value. Plan secret rotation using Better Auth's versioned-secret mechanism and test active sessions and OAuth flows before production rollout.
Production verification
After changing configuration:
- restart the application so Nitro reads the new environment;
- open the portal at
PUBLIC_URLand complete a sign-in; - verify signup matches the configured registration mode;
- request an authentication email and inspect its origin and sender;
- test each enabled OAuth provider from the public hostname;
- confirm a normal member does not receive system-administrator access;
- run the deployment checks in Deployment.