Site

For a new project, let pnpm run init generate .env.local and its isolated database credentials. .env.example is the complete variable reference and a manual-configuration template; copying it does not install the database or provision credentials. Never copy it over a completed init result. In production, inject server secrets through the deployment platform. Never commit a database URL, service key, signing secret or operator credential. Restart the app and workers after environment changes; rebuild client assets when public values change.

Deployment selection

VariableValuesRule
DATABASE_PROVIDERsupabase, neonOne value for the deployment
AUTH_PROVIDERsupabase, better_authOne value for the deployment
JOB_SCHEDULER_PROVIDERsupabase_pg_cron, postgres_pg_cron, external_runnerSupabase uses supabase_pg_cron; Neon uses postgres_pg_cron; either may use the external fallback
STORAGE_PROVIDERdisabled, supabase, neon, s3Independent server-side object-storage adapter

These values are server-owned. A request, User or Account cannot select a provider.

PostgreSQL runtime credentials

Every composition requires:

VariableRequired rolePurpose
DATABASE_URLapp_runtimeShared Account-scoped Prisma business runtime
DATABASE_MAINTENANCE_URLapp_maintenanceBounded retention and GDPR maintenance
DATABASE_FINANCIAL_WORKER_URLapp_financial_workerIsolated credit-ledger operations; pool maximum 1
DATABASE_PLATFORM_ADMIN_URLapp_platform_adminRead-only platform administration and logs

All four URLs target the same database and TLS mode and use distinct passwords. Remote URLs require TLS. Neon uses its pooled -pooler hostname; Supabase pooler usernames may encode the project suffix, for example app_runtime.<project-ref>.

The direct administrator URL is supplied only to pnpm run init or an isolated installation command. It must not exist in the web or worker environment.

Pool and deadline controls are bounded by DATABASE_POOL_MAX, DATABASE_CONNECTION_TIMEOUT_MS, DATABASE_IDLE_TIMEOUT_MS, DATABASE_MAX_LIFETIME_SECONDS, DATABASE_STATEMENT_TIMEOUT_MS, DATABASE_TRANSACTION_MAX_WAIT_MS and DATABASE_TRANSACTION_TIMEOUT_MS.

Connection admission also requires DATABASE_BACKEND_CONNECTION_LIMIT, DATABASE_RESERVED_CONNECTIONS and DATABASE_APPLICATION_INSTANCES. Copy the backend limit from the selected provider tier; do not guess it. Runtime and init reserve one maintenance, one financial-worker and two platform-admin connections per active instance, plus DATABASE_POOL_MAX and, for Better Auth, BETTER_AUTH_DATABASE_POOL_MAX. Count web and worker processes, including overlapping deployment replicas. Configuration fails closed unless at least one backend slot remains after the declared maximum instance count and reserved provider/operator capacity.

Canonical identity

Fresh installations require:

dotenv
AUTH_SESSION_FINALIZATION_MODE=enforce
AUTH_SESSION_FINALIZATION_WRITE_VERSION=v2
AUTH_SESSION_FINALIZATION_SECRET=<at-least-32-random-characters>

Invitation delivery also requires this secret, including deployments with session finalization disabled. Initial invitations and resends share a per-recipient quota across workspaces; its keys are domain-separated HMAC commitments. Missing signing configuration returns a generic 503 before any email is sent.

There is no identity-specific DSN or pool. Remove all retired IDENTITY_DATABASE_*, IDENTITY_COMPLIANCE_DATABASE_* and IDENTITY_ACCOUNT_DUAL_READ_MODE values.

Object storage

Every enabled adapter uses the same private-document and public-CMS-media contract:

dotenv
STORAGE_PROVIDER=supabase
STORAGE_DOCUMENTS_BUCKET=documents
STORAGE_MEDIA_BUCKET=media
STORAGE_MEDIA_PUBLIC_URL=
STORAGE_REQUEST_TIMEOUT_MS=15000

The documents bucket is private and is exposed only through short-lived signed download URLs after Account authorization. The media bucket is a platform-wide public asset library; STORAGE_MEDIA_PUBLIC_URL is required for Neon Object Storage and AWS S3 and may point to the public bucket base URL or a CDN. Provider credentials and endpoint values are server-only. SUPABASE_STORAGE_ENABLED is a deprecated compatibility input; new installations keep it false and use STORAGE_PROVIDER.

For Neon Object Storage:

dotenv
STORAGE_PROVIDER=neon
STORAGE_FORCE_PATH_STYLE=true
AWS_REGION=<Neon-provided-region>
AWS_ACCESS_KEY_ID=<server-only-access-key>
AWS_SECRET_ACCESS_KEY=<server-only-secret-key>
AWS_SESSION_TOKEN=
AWS_ENDPOINT_URL_S3=https://<Neon-provided-S3-endpoint>
STORAGE_MEDIA_PUBLIC_URL=https://<public-media-base-url>

Neon Object Storage is currently Beta and supports the S3 operations used here. The media rename path deliberately performs bounded Get/Put/Delete operations because Neon does not advertise CopyObject compatibility. Configure the media bucket as public through Neon, and keep the documents bucket private.

For AWS S3, use STORAGE_PROVIDER=s3, set the region, keep AWS_ENDPOINT_URL_S3 empty and STORAGE_FORCE_PATH_STYLE=false, and configure STORAGE_MEDIA_PUBLIC_URL to the public bucket or CDN base URL. Prefer the AWS SDK default credential chain with a short-lived IAM role; leave all three static credential variables empty in that mode. Access key, secret key and optional session token remain supported when an IAM role is unavailable. Grant the runtime identity only Get/Put/Delete/List access to the two selected buckets. Bucket creation, public access, lifecycle, encryption and CDN policy remain infrastructure responsibilities; the application does not mutate them at startup.

Supabase Auth and Storage

Set the following only when Supabase Auth or STORAGE_PROVIDER=supabase is selected:

dotenv
NEXT_PUBLIC_SUPABASE_URL=https://project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable-key>
SUPABASE_SECRET_KEY=<server-secret-key>

Only the URL and publishable key are browser-safe. SUPABASE_SECRET_KEY is server-only. With Neon, these credentials remain Auth/Storage credentials and never authorize the Neon database.

Better Auth

When AUTH_PROVIDER=better_auth, configure:

dotenv
BETTER_AUTH_URL=https://your-domain.example
BETTER_AUTH_SECRET=<high-entropy-secret>
BETTER_AUTH_DATABASE_URL=<auth_runtime_login_v1 pooled URL>
BETTER_AUTH_DATABASE_POOL_MAX=3

BETTER_AUTH_URL must have the same origin as NEXT_PUBLIC_APP_URL. The database URL targets the selected PostgreSQL database, but its role can access only the isolated authn schema. OAuth client IDs and secrets are configured as complete provider pairs with BETTER_AUTH_GOOGLE_*, BETTER_AUTH_GITHUB_* or BETTER_AUTH_GITLAB_*.

Native capabilities

STORAGE_PROVIDER=supabase keeps Supabase Storage behind server adapters. Business authorization and metadata remain in the selected PostgreSQL database. STORAGE_PROVIDER=disabled makes document and CMS upload capabilities unavailable.

JOB_SCHEDULER_PROVIDER=supabase_pg_cron means Supabase pg_cron and pg_net call POST /api/jobs/run. The command reads the Bearer secret from Supabase Vault, so jobs_api_url, JOBS_SECRET_KEY, and the matching Vault value are required.

JOB_SCHEDULER_PROVIDER=external_runner means an external scheduler calls POST /api/jobs/run with JOBS_SECRET_KEY. The durable queue, leases, retries and terminal visibility remain database-backed.

JOB_SCHEDULER_PROVIDER=postgres_pg_cron means PostgreSQL pg_cron runs a fixed, secret-free SQL enqueue command. It needs no jobs_api_url, JOBS_SECRET_KEY, pg_net, or Vault. Keep pnpm jobs:worker running beside the application to claim pending occurrences and execute the TypeScript handlers.

Health and logs

GET /api/health probes the selected shared Prisma database runtime with a bounded select 1, without calling a remote Auth API. Its public response is limited to status: "ok" (200) or status: "degraded" (503), with no provider, capability or timing details. Pool failures, pool backlog and terminal job failures are written through the centralized redacted logger.

Keep LOGS_ENABLED, LOGS_RETENTION_DAYS and LOGS_SALT configured when operational logs are enabled.

Runtime and release certification

Implementation and certification are separate. Runtime validates supported providers, isolated credentials, TLS, connection budgets and canonical identity; database transactions also attest SQL roles and enforce Account/RLS permissions. pnpm run qa:release separately checks the final local_only evidence in config/database-auth-provider-compatibility.json. Application imports do not read Git metadata or local build proofs. Init and a successful Docker build do not certify a release. Disposable QA runners still require their complete loopback-only proofs.

The mandatory AUTH_SESSION_FINALIZATION_SECRET also signs purpose-separated signup/legal intentions and browser-bound email links for every Auth composition. Rotation invalidates pending intentions and links; users must request a new link. Administrative setup credentials expire after one hour and cannot be created from an ordinary copied login token. Never leave this secret empty, including with Supabase Auth or when the finalization marker is disabled.