Environment variables
Configure PostgreSQL, NextAuth, AWS S3, local MinIO, logging, and search tuning
Environment variables are runtime configuration, not source code. The main app reads them on the server; the docs site is a separate repository with no dependency on the application's .env. Never commit .env, access keys, or seeded passwords.
Complete reference
| Variable | Required? | Example | Used for |
|---|---|---|---|
DATABASE_URL | Yes for the main app | postgresql://postgres:postgres@localhost:5432/roundtable | Prisma PostgreSQL connection. |
NEXTAUTH_SECRET | Yes | Long random value | Signs NextAuth JWT/session data. |
NEXTAUTH_URL | Yes when deployed | http://localhost:3000 | Canonical callback and redirect URL. |
UPLOADS_S3_BUCKET | Yes for uploads | roundtable-uploads | Bucket name for private objects. |
AWS_REGION | Yes for S3 | us-gov-west-1 or local region | S3 client region. |
AWS_ACCESS_KEY_ID | Usually | Local or task-role credential | S3 credential-chain input. |
AWS_SECRET_ACCESS_KEY | Usually | Local secret | S3 credential-chain input. |
AWS_ENDPOINT_URL_S3 | Local/portable only | http://minio:9000 | S3-compatible endpoint override. |
UPLOADS_S3_SSE | No (defaults to AES256) | none | Server-side encryption requested on uploads. Unset or blank means AES256; none omits the request (local MinIO); AES256, aws:kms, and aws:kms:dsse are passed through. Matched case-insensitively; any other value throws when an upload is attempted. |
MINIO_KMS_SECRET_KEY | Only when MinIO must serve SSE requests | Secret value | Gives MinIO a KMS key so it can serve the app's SSE request. Not needed if the app runs with UPLOADS_S3_SSE=none. |
MINIO_DOMAIN | MinIO virtual host | minio.local | Enables virtual-host bucket addressing. |
LOG_LEVEL | No (default info) | debug, info, warn, or error | Minimum severity for the structured operational logger (src/lib/logger.ts). |
MATCHING_SCORER | No (default deterministic) | bedrock | Selects Bedrock embedding scoring only when set to bedrock (case-insensitive); unset, blank, or another value keeps deterministic tag-overlap scoring. |
BEDROCK_EMBEDDING_MODEL_ID | No (default amazon.titan-embed-text-v2:0) | Titan-style model ID | Bedrock model used when matching is enabled. The response must be JSON with an embedding number array; other response shapes fail and routing falls back to tag scoring. An unset or blank value uses the default. |
BEDROCK_SIMILARITY_FLOOR | No (default 0.35) | Number in [0, 1) | Similarity at or below this floor scores zero; the remaining range rescales to 0..1. An unset, blank, or invalid value uses 0.35. |
SEARCH_SIMILARITY_THRESHOLD | No (default 0.2) | Number strictly between 0 and 1 | Minimum trigram word similarity for a global-search fuzzy match. Read once at startup; a value outside the range logs a warning and the default is used. |
SEARCH_BACKEND | No (default hybrid) | hybrid, fts, trigram, or prisma | Global-search backend: full-text search with trigram fuzzy fallback on zero results (default), full-text search only (boolean/phrase/wildcard operators), pg_trgm fuzzy only, or the case-insensitive substring fallback. |
AWS SDK credentials should come from the default credential chain in deployed AWS environments, usually an App Runner instance role. Do not put production credentials in the image.
PostgreSQL
DATABASE_URL is consumed by Prisma's datasource. Verify connectivity with:
psql "$DATABASE_URL" -c 'select 1;'If psql is not installed, use docker exec -it roundtable-pg psql -U postgres -d roundtable -c 'select 1;'.
Port 5432 is already occupied
Either stop the unrelated PostgreSQL process or map the container to another host port, such as -p 55432:5432, and update DATABASE_URL to use localhost:55432. Do not change the container-side port in the Compose network; other services use the service name and internal port.
NextAuth
NEXTAUTH_SECRET must be stable across restarts in a given environment. Changing it invalidates existing signed sessions. NEXTAUTH_URL must match the browser-visible origin in deployment; a wrong HTTPS URL commonly causes callback failures or redirect loops.
S3 and MinIO
src/lib/s3.ts creates an S3Client with AWS_REGION and the default credential chain. AWS_ENDPOINT_URL_S3 is useful for MinIO because it supplies an S3-compatible endpoint without changing application code. Uploads use keys under uploads/ and request server-side encryption controlled by UPLOADS_S3_SSE: unset or blank defaults to AES256 (production behavior), while the explicit value none (matched case-insensitively) omits the SSE parameter so stores without KMS (local MinIO) accept the upload (a warning is logged).
The portable Compose recipe also requires MinIO-specific behavior:
- Either set
UPLOADS_S3_SSE=nonefor the app, or setMINIO_KMS_SECRET_KEYso MinIO can satisfy an SSE request. - Set
MINIO_DOMAINso bucket virtual-host addressing resolves. - Ensure the app can resolve the MinIO hostname used in the endpoint and domain.
- Use bucket-scoped credentials generated by the
minio-initservice.
Without these settings, a submission with a file may create a database request that ultimately fails with an S3 500 even though text-only submissions work.
Why virtual-host addressing matters
S3 clients may address a bucket as a path or as a host such as bucket.minio.local. A local endpoint that resolves only minio:9000 can fail when the SDK constructs a virtual-host URL. MINIO_DOMAIN and local DNS/hosts configuration need to agree with the bucket name. This is a network-resolution problem, not a Prisma migration problem.
Safe local example
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/roundtable
NEXTAUTH_SECRET=replace-with-a-long-random-development-value
NEXTAUTH_URL=http://localhost:3000
UPLOADS_S3_BUCKET=roundtable-uploads
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_S3=http://localhost:9000
AWS_ACCESS_KEY_ID=roundtable-local
AWS_SECRET_ACCESS_KEY=replace-me
# local only: the app stops asking the store to encrypt uploads
UPLOADS_S3_SSE=none
MINIO_DOMAIN=localhostThis is a pattern, not a credential to copy into a shared environment. It takes the unencrypted route so MinIO needs no key; the portable bundle takes the other route, keeping the default AES256 request and a MINIO_KMS_SECRET_KEY in deploy/portable/.env.example for MinIO to serve it.
Troubleshooting
Prisma says it cannot connect
Check that the container is running, port 5432 is not mapped to another process, and the hostname in DATABASE_URL is reachable from where pnpm db:setup runs. Inside Compose, use the service name db, not localhost.
Uploads return 500
Confirm the bucket exists, the endpoint is reachable from the app container, the credentials can put objects, and the encryption request matches the store — MinIO needs MINIO_KMS_SECRET_KEY unless the app runs with UPLOADS_S3_SSE=none — plus MinIO domain settings when virtual-host addressing is used. The upload validator can reject a file with 400; a storage configuration failure is a different class of error and is logged server-side.
Secret changes seem ignored
Restart the Next.js process after changing .env. NextAuth also requires a stable secret; do not expect existing sessions to survive a deliberate secret rotation.
Environment review checklist
-
.envexists at the root and is ignored by Git. -
DATABASE_URLpoints to the intended database, not a stale container. -
NEXTAUTH_SECRETis long, random, and stable for this environment. -
NEXTAUTH_URLmatches the browser origin exactly, including HTTPS in deployment. - The upload bucket exists and is private.
- AWS credentials are supplied by a safe local profile or task role.
- Local MinIO has virtual-host settings, and either
UPLOADS_S3_SSE=noneor a KMS key. -
UPLOADS_S3_SSEis unset, blank, or set to one of the encrypting values (AES256,aws:kms,aws:kms:dsse) in production so uploads stay encrypted.
Troubleshooting matrix
| Symptom | Likely cause | Next check |
|---|---|---|
| Port 3000 bind error | Main app port is occupied | Inspect lsof, stop stale app, or use a deliberate alternate. |
| Port 3001 bind error | Docs server is already running | Reuse the existing docs server or stop the stale process. |
P1001 database error | PostgreSQL stopped/unreachable | docker ps, container logs, and host/port in DATABASE_URL. |
| Migration cannot apply | Wrong database or migration state | pnpm prisma migrate status; inspect the first SQL error. |
| Prisma client lacks an enum | Generated client stale | Run pnpm prisma generate from root. |
| Upload returns 500 | S3/MinIO endpoint, bucket, or SSE | Test credentials, endpoint DNS, bucket policy, and UPLOADS_S3_SSE/KMS support. |
| Upload returns 400 | File or field validation | Check extension, size, magic bytes, and Zod limits. |
| Redirect loops after login | NEXTAUTH_URL mismatch | Match the public scheme/host/port and restart Next.js. |
| Notifications absent | Profile overlap or setting disabled | Inspect normalized tags, profile, and NotificationSetting. |
Do not respond to every failure by deleting the database. First classify whether the error is process startup, network connectivity, schema generation, request validation, authorization, or storage.