Passa al contenuto principale

Environment Variables

Copy .env.example to .env and replace every production placeholder. The production Compose overlay passes an explicit allowlist to each service; it does not inject the whole file into containers.

Application​

VariableRequiredLocal defaultDescription
NODE_ENVRuntimedevelopmentproduction is forced in production images
PORTNo8000Next.js web port
WS_PORTNo8003WebSocket service port
NEXT_PUBLIC_APP_URLYeshttps://probeya.comBrowser-visible application origin; embedded at build time
NEXT_PUBLIC_WS_URLYeswss://ws.probeya.comBrowser-visible WebSocket origin; embedded at build time
AUTH_URLProductionhttps://probeya.comCanonical Auth.js origin
AUTH_TRUST_HOSTProductiontrueTrust the Nginx-forwarded host
ITEM_EVENT_OUTBOX_WORKER_ENABLEDProductionfalseEnables the durable item-event worker in web; the production overlay forces true
FORM_EMAIL_OUTBOX_PRODUCER_ENABLEDProductionfalseEnables Forms invitation/reminder producers only after the outbox rollout is safe

There is no separate application API origin or API port. tRPC and HTTP routes are served by Next.js on PORT.

Database​

VariableRequiredDescription
DATABASE_URLRuntime/localprobeya_app connection used by the application with RLS enforced
DATABASE_APP_URLRLS acceptanceExact alias of DATABASE_URL used to prove the direct application role
PROBEYA_RLS_ACCEPTANCE_DISPOSABLERLS acceptanceMust be 1; confirms the target database may be destructively tested
DATABASE_ADMIN_URLLocal schema operationsprobeya_admin URL for db:migrate, RLS apply/check, and seeds
DATABASE_URL_DOCKERProduction runtimeContainer-network probeya_app URL passed only to web
DATABASE_ADMIN_URL_DOCKERProduction schemaContainer-network probeya_admin URL passed only to schema
POSTGRES_DBProductionDatabase name, normally probeya
POSTGRES_USERProductionCompose PostgreSQL superuser used only for cluster/bootstrap operations
POSTGRES_PASSWORDProductionRandom PostgreSQL superuser password
PROBEYA_APP_DB_PASSWORDProductionRandom RLS runtime-role password
PROBEYA_ADMIN_DB_PASSWORDProductionDifferent random schema-owner password

The production URLs must use the Compose hostname and URL-encoded passwords:

DATABASE_URL_DOCKER=postgresql://probeya_app:<encoded-password>@postgres:5432/probeya
DATABASE_ADMIN_URL_DOCKER=postgresql://probeya_admin:<encoded-password>@postgres:5432/probeya

Never pass DATABASE_ADMIN_URL or DATABASE_ADMIN_URL_DOCKER to an application runtime. probeya_admin can bypass RLS by design. The MVP ws service receives neither the application nor the admin database URL.

Redis​

VariableRequiredDescription
REDIS_URLLocal runtimeHost URL, normally redis://localhost:6380
REDIS_PASSWORDProductionRandom Redis password
REDIS_URL_DOCKERProductionPassword-protected URL using hostname redis and port 6379
REDIS_URL_DOCKER=redis://:<encoded-password>@redis:6379

Authentication and Email​

VariableRequiredDescription
AUTH_SECRETYesAuth.js signing secret; at least 32 random bytes; fallback Forms-envelope key
FORM_EMAIL_OUTBOX_ENCRYPTION_SECRETOptionalDedicated current Forms-envelope key; at least 32 bytes when set
FORM_EMAIL_OUTBOX_PREVIOUS_ENCRYPTION_SECRETRotation onlyPrevious Forms-envelope key; remove only after all jobs encrypted with it have drained
WS_AUTH_SECRETYesSeparate WebSocket authentication secret
CRON_SECRETProductionSeparate secret for protected scheduled endpoints
RESEND_API_KEYProductionResend API key; public-form OTP, Forms delivery, and recovery fail closed without it
EMAIL_FROMProductionVerified sender identity

Generate independent secrets instead of reusing one value:

openssl rand -base64 32

Optional OIDC providers use the corresponding Authentik or Azure client variables in .env.example. Their NEXT_PUBLIC_*_ENABLED flags are embedded at image build time.

Stripe Billing​

VariableRequiredDescription
STRIPE_SECRET_KEYProductionServer API key; keep it in the same Stripe mode as the Price IDs
STRIPE_WEBHOOK_SECRETProductionSigning secret for /api/webhooks/stripe
STRIPE_PRICE_STARTERProductionExact monthly Starter Price ID used for checkout and entitlements
STRIPE_PRICE_PROProductionExact monthly Pro Price ID used for checkout and entitlements

Configure all four values together. The two price_... IDs must be distinct; unknown Stripe prices never grant a paid plan. Enterprise is contract-only and is not accepted by self-service Checkout.

S3-Compatible Storage​

VariableRequiredDescription
S3_ENDPOINTYesServer-reachable S3 API origin, for example http://minio:9000 in Compose
S3_PUBLIC_URLYesBrowser-reachable HTTPS origin for public assets
S3_BUCKETYesBucket name, normally probeya-uploads
S3_ACCESS_KEYYesLeast-privilege application identity
S3_SECRET_KEYYesApplication secret, distinct from the MinIO root secret
S3_PREVIOUS_ACCESS_KEYRotation onlyPrevious identity removed after a successful rotation
S3_REGIONNoS3 region; defaults to us-east-1
MINIO_ROOT_USERBundled MinIORoot identity used only by minio-init
MINIO_ROOT_PASSWORDBundled MinIORoot secret used only by minio-init

S3_PUBLIC_URL must not use the Compose-only minio hostname. The one-shot bootstrap keeps the bucket private and grants anonymous GetObject only for the avatar and logo prefixes. Attachments require an ACL-checked signed URL.

Attachment Scanning​

VariableRequiredProduction valueDescription
CLAMAV_HOSTYesclamavclamd hostname
CLAMAV_PORTYes3310clamd container port
CLAMAV_TIMEOUT_MSNo30000Scan timeout
CLAMAV_REQUIREDProductiontrueBlock writes when scanning is unavailable

The production overlay forces CLAMAV_REQUIRED=true. A false value is only appropriate for a controlled local development environment.

Production Example​

POSTGRES_DB=probeya
POSTGRES_USER=probeya
POSTGRES_PASSWORD=<random-superuser-password>
PROBEYA_APP_DB_PASSWORD=<random-app-password>
PROBEYA_ADMIN_DB_PASSWORD=<random-admin-password>
DATABASE_URL_DOCKER=postgresql://probeya_app:<encoded-app-password>@postgres:5432/probeya
DATABASE_ADMIN_URL_DOCKER=postgresql://probeya_admin:<encoded-admin-password>@postgres:5432/probeya

REDIS_PASSWORD=<random-redis-password>
REDIS_URL_DOCKER=redis://:<encoded-redis-password>@redis:6379

MINIO_ROOT_USER=<random-root-access-key>
MINIO_ROOT_PASSWORD=<random-root-secret-key>
S3_ACCESS_KEY=<different-app-access-key>
S3_SECRET_KEY=<different-app-secret-key>
S3_ENDPOINT=http://minio:9000
S3_PUBLIC_URL=https://files.example.com
S3_BUCKET=probeya-uploads

AUTH_SECRET=<random-secret>
FORM_EMAIL_OUTBOX_PRODUCER_ENABLED=false
FORM_EMAIL_OUTBOX_ENCRYPTION_SECRET=<optional-dedicated-random-secret>
FORM_EMAIL_OUTBOX_PREVIOUS_ENCRYPTION_SECRET=
WS_AUTH_SECRET=<different-random-secret>
CRON_SECRET=<different-random-secret>
RESEND_API_KEY=<production-key>
EMAIL_FROM=ProBeya <[email protected]>

STRIPE_SECRET_KEY=<production-secret-key>
STRIPE_WEBHOOK_SECRET=<endpoint-signing-secret>
STRIPE_PRICE_STARTER=price_<starter-monthly-id>
STRIPE_PRICE_PRO=price_<pro-monthly-id>

NEXT_PUBLIC_APP_URL=https://probeya.example.com
NEXT_PUBLIC_WS_URL=wss://ws.example.com

Run the merged Compose validation after every environment change:

docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
config --quiet