Skip to content

nest-api environment

Every variable is validated with Zod when the app boots (src/common/services/config.service.ts), and the process refuses to start if one is missing or malformed, instead of failing later inside a request.

  • An empty value counts as missing for a required string: SMTP_USER='' fails.
  • Numbers must parse: PORT='' fails (a default applies only when the variable is absent altogether).
  • App code never reads process.env: it injects CustomConfigService. Adding a variable means three edits in that file (the interface, the schema and getVariables) and a line in .env.example.
VariableWhat to set
APP_ENV
default development

Production: production.

Switches logs to JSON on stdout and is stamped on all telemetry as deployment.environment.name. In nest-api, development also adds local servers to the OpenAPI document.
APP_NAME
default Acme Corp

Production: Your product's display name.

Used in emails, the Swagger title and better-auth. Set by --name.
APP_HOST
default 0.0.0.0

Production: 0.0.0.0 in a container.

The address the server binds to. 127.0.0.1 would make it unreachable from outside the container.
PORT
default 5000

Production: The port the process listens on (nest-api: the container's exposed port).

Changing it after generation means changing SERVER_HOST and BETTER_AUTH_URL too; --port at generation does all three.
SERVER_HOST
default http://localhost:5000

Production: The API's public URL, e.g. https://api.example.com.

Used in email links, the OpenAPI servers list and the /health HTTP check, which calls it from inside the container, so it must resolve there.
FRONTEND_HOST
default http://localhost:3000

Production: The front end's origin, e.g. https://app.example.com.

Trusted by auth and allowed by CORS. The welcome email links to it. Other front-end origins go in MISC_CORS_ORIGINS.
MISC_CORS_ORIGINS
empty by default

Extra origins beyond FRONTEND_HOST, comma separated, trusted by auth and allowed by CORS: every react-monorepo app goes here or in FRONTEND_HOST. A full origin (`http://localhost:3001`) is used as written; a bare hostname (`example.com`) expands to https://example.com and its auth., app. and admin. subdomains.

Production: Every other front-end origin, comma-separated; empty for a single front end.

A full origin (http://localhost:3001) is used as written; a bare hostname (example.com) expands to https origins for it and its auth., app. and admin. subdomains. See Auth, cookies and CORS.
DATABASE_URL
default postgresql://root:postgres@localhost:5432/main

Production: postgresql://<user>:<password>@<host>:5432/<db>?sslmode=require.

Where to get it: Your PostgreSQL provider.

A worker's value must be the API's database. URL-encode special characters in the password. See PostgreSQL.
BETTER_AUTH_SECRET
default change-me-openssl-rand-base64-32

openssl rand -base64 32

Production: A random 32-byte value: openssl rand -base64 32.

Signs sessions. The example value boots, but anyone reading the template knows it. Changing it signs every user out.
BETTER_AUTH_URL
default http://localhost:5000

openssl rand -base64 32

Production: The API's public URL, like SERVER_HOST.

better-auth's base URL: OAuth callbacks are <this>/auth/callback/<provider>.
SUPERUSER_EMAIL
default admin@example.com

Seeded by `yarn db:seed`.

Production: The first admin's address.

yarn db:seed creates this user, verified, with the superuser role. Seeding again fails on the existing email.
SUPERUSER_PASSWORD
default Secret@1

Seeded by `yarn db:seed`.

Production: A strong password, changed after first sign-in.

Only read by the seeder.
OAUTH_GOOGLE_CLIENT_ID
empty by default

Production: Your Google OAuth client id.

Where to get it: Google Cloud console → Credentials. See Google sign-in.

Empty disables Google sign-in (the button fails).
OAUTH_GOOGLE_CLIENT_SECRET
empty by default

Production: The client's secret.

Where to get it: Google Cloud console → Credentials.

LOG_LEVEL
default debug

Production: info.

pino levels: trace, debug, info, warn, error, fatal, silent. An empty value fails validation in nest-api.
OPENOBSERVE_URL
default https://o2.aurostack.co
feature observability

OpenTelemetry → OpenObserve: traces, logs and metrics. Leave empty and telemetry stays off (local development, tests). Per project: OPENOBSERVE_ORG the project's organization identifier OPENOBSERVE_TOKEN that org's ingestion token (IAM → Ingestion Tokens, o2oi_…) OPENOBSERVE_STREAM defaults to the service name (OTEL_SERVICE_NAME). Any other OTLP backend: set OTEL_EXPORTER_OTLP_ENDPOINT/HEADERS instead.

Production: Your OpenObserve instance.

Telemetry is on only when the URL, org and token are all set. See OpenObserve.
OPENOBSERVE_ORG
empty by default
feature observability

OpenTelemetry → OpenObserve: traces, logs and metrics. Leave empty and telemetry stays off (local development, tests). Per project: OPENOBSERVE_ORG the project's organization identifier OPENOBSERVE_TOKEN that org's ingestion token (IAM → Ingestion Tokens, o2oi_…) OPENOBSERVE_STREAM defaults to the service name (OTEL_SERVICE_NAME). Any other OTLP backend: set OTEL_EXPORTER_OTLP_ENDPOINT/HEADERS instead.

Production: The project's organization identifier.

Where to get it: OpenObserve → organization settings (or the org segment of its URLs).

OPENOBSERVE_TOKEN
empty by default
feature observability

OpenTelemetry → OpenObserve: traces, logs and metrics. Leave empty and telemetry stays off (local development, tests). Per project: OPENOBSERVE_ORG the project's organization identifier OPENOBSERVE_TOKEN that org's ingestion token (IAM → Ingestion Tokens, o2oi_…) OPENOBSERVE_STREAM defaults to the service name (OTEL_SERVICE_NAME). Any other OTLP backend: set OTEL_EXPORTER_OTLP_ENDPOINT/HEADERS instead.

Production: The organization's ingestion token (o2oi_…).

Where to get it: OpenObserve → IAM → Ingestion Tokens.

Write-only, but secret: never use it in a browser app (front ends use a RUM token).
OTEL_SERVICE_NAME
default acme-api
feature observability

OpenTelemetry → OpenObserve: traces, logs and metrics. Leave empty and telemetry stays off (local development, tests). Per project: OPENOBSERVE_ORG the project's organization identifier OPENOBSERVE_TOKEN that org's ingestion token (IAM → Ingestion Tokens, o2oi_…) OPENOBSERVE_STREAM defaults to the service name (OTEL_SERVICE_NAME). Any other OTLP backend: set OTEL_EXPORTER_OTLP_ENDPOINT/HEADERS instead.

Production: A distinct name per service: shop-api, shop-worker, shop-py.

The service name on every span, and the default stream name, so each service gets its own stream.
HEALTH_HEAP_MAX_MB
default 300
feature observability

Limits past which /health reports down. Size them to the container's real limits: an orchestrator restarts instances that fail this check.

Production: Below the container's memory limit, e.g. 75% of it.

Past it, /health reports down and orchestrators restart the instance.
HEALTH_RSS_MAX_MB
default 300
feature observability

Limits past which /health reports down. Size them to the container's real limits: an orchestrator restarts instances that fail this check.

Production: Below the container's memory limit.

Resident memory, heap included.
HEALTH_DISK_THRESHOLD
default 0.8
feature observability

Limits past which /health reports down. Size them to the container's real limits: an orchestrator restarts instances that fail this check. Fraction (0–1) of HEALTH_DISK_PATH's volume that may be used.

Production: The fraction of the volume that may be used, e.g. 0.9.

HEALTH_DISK_PATH
default /
feature observability

Limits past which /health reports down. Size them to the container's real limits: an orchestrator restarts instances that fail this check. Fraction (0–1) of HEALTH_DISK_PATH's volume that may be used.

Production: The volume to watch: /, or where logs and uploads are written.

BASIC_AUTH_USER
default admin
feature openapi, queue

Guards /docs, /openapi-json and /dashboard.

Production: A new operator username.

Guards /docs, /openapi, /openapi-json and /dashboard. Front ends' yarn gen uses it as API_DOC_USER.
BASIC_AUTH_PASS
default Secret@1
feature openapi, queue

Guards /docs, /openapi-json and /dashboard.

Production: A strong password, not the example's.

REDIS_HOST
default localhost
feature cache

Production: Your Redis host; identical on the API and every worker.

A worker on a different Redis connects fine and never receives a job. See Redis.
REDIS_PORT
default 6379
feature cache

Production: Your Redis port (6379).

REDIS_USER
empty by default
feature cache

Production: An ACL user, if your Redis uses them.

REDIS_PASSWORD
empty by default
feature cache

Production: The Redis password.

SMTP_HOST
default smtp.example.com
feature mail

Production: Your mail provider's SMTP host.

See Email (SMTP), including a local catcher.
SMTP_PORT
default 465
feature mail

Production: 465 or 587.

No other value validates. For a local catcher, map it to 587.
SMTP_SECURE
default true
feature mail

Production: true with 465, false with 587 (STARTTLS).

SMTP_USER
empty by default
feature mail

Production: The SMTP username.

Where to get it: Your mail provider.

Required and empty by default: the API won't start until it's set.
SMTP_PASSWORD
empty by default
feature mail

Production: The SMTP password or API key.

Where to get it: Your mail provider.

Required and empty by default.
SMTP_FROM
default Acme Corp <no-reply@example.com>
feature mail

Production: Name <no-reply@your-domain>, on a domain with SPF, DKIM and DMARC.

Without those records, verification emails land in spam, and new users can't sign in until they verify.
SMTP_SUPPORT_EMAIL
default support@example.com
feature mail

Production: Your support address.

Shown in the welcome email; keep it equal to a landing site's VITE_APP_SUPPORT_EMAIL.
S3_REGION
default us-east-1
feature media

Production: The bucket's region.

S3_ENDPOINT
empty by default
feature media

Leave empty for AWS; set it for MinIO or another S3-compatible host.

Production: The endpoint URL, even on AWS: https://s3.<region>.amazonaws.com.

Also the prefix of stored file URLs: empty, they're relative and browsers can't load them. See Object storage.
S3_BUCKET_NAME
default acme
feature media

Leave empty for AWS; set it for MinIO or another S3-compatible host.

Production: The bucket.

Must allow object ACLs: uploads outside the private folders are public-read.
S3_ACCESS_KEY_ID
empty by default
feature media

Leave empty for AWS; set it for MinIO or another S3-compatible host.

Production: An access key for the bucket.

Where to get it: Your storage provider's IAM.

S3_SECRET_ACCESS_KEY
empty by default
feature media

Leave empty for AWS; set it for MinIO or another S3-compatible host.

Production: Its secret.

TEMPORAL_ADDRESS
default localhost:7233
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line.

Production: Your Temporal frontend, e.g. temporal-grpc.example.com:7233.

See Temporal.
TEMPORAL_NAMESPACE
default default
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line.

Production: The project's namespace, the same on the API and its workers.

TEMPORAL_TASK_QUEUE
default main
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line. Where workflows started here are queued. The worker that runs them must poll the same queue (its TEMPORAL_TASK_QUEUE); a mismatch is silent, workflows just sit waiting.

Production: The queue a worker polls.

API: where workflows are started. Worker: the queue it polls. A mismatch is silent: workflows wait forever.
TEMPORAL_TLS_CA
empty by default
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line. Where workflows started here are queued. The worker that runs them must poll the same queue (its TEMPORAL_TASK_QUEUE); a mismatch is silent, workflows just sit waiting.

Production: The CA that signed the server's certificate, as PEM; empty if it's publicly trusted.

Escaped \n are accepted, so it fits on one line.
TEMPORAL_TLS_CERT
empty by default
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line. Where workflows started here are queued. The worker that runs them must poll the same queue (its TEMPORAL_TASK_QUEUE); a mismatch is silent, workflows just sit waiting.

Production: The project's client certificate, as PEM.

Where to get it: Issued by whoever runs your Temporal (the gateway's CA).

Set it together with TEMPORAL_TLS_KEY, or startup fails.
TEMPORAL_TLS_KEY
empty by default
feature temporal

Temporal: durable workflows. Locally, the dev server in compose.yml (no TLS; UI on http://localhost:8233). In production, the server address, the project's namespace and its mTLS client certificate (plus the CA that signed the server's), as PEM. Escaped \n are accepted, so each fits on one line. Where workflows started here are queued. The worker that runs them must poll the same queue (its TEMPORAL_TASK_QUEUE); a mismatch is silent, workflows just sit waiting.

Production: The client certificate's private key, as PEM.

A secret: keep it in your secret store.
RATE_LIMIT_ENABLED
default true
feature rate-limit

Production: true.

Off in .env.test so specs don't share a rate-limit window.
RATE_LIMIT_TTL
default 60000
feature rate-limit

Throttler TTLs are in milliseconds.

Production: The window for app routes, in milliseconds (default 60000).

RATE_LIMIT_MAX
default 120
feature rate-limit

Throttler TTLs are in milliseconds.

Production: Requests per window per client IP on app routes (default 120).

RATE_LIMIT_AUTH_WINDOW
default 10
feature rate-limit

Throttler TTLs are in milliseconds. better-auth's own window is in seconds.

Production: better-auth's window for /auth/*, in seconds (default 10).

Sign-in, sign-up and password routes also have stricter built-in rules.
RATE_LIMIT_AUTH_MAX
default 100
feature rate-limit

Throttler TTLs are in milliseconds. better-auth's own window is in seconds.

Production: Requests per auth window per client IP (default 100).

RATE_LIMIT_IP_HEADERS
default x-forwarded-for
feature rate-limit

Throttler TTLs are in milliseconds. better-auth's own window is in seconds. Load-bearing behind a proxy: if no IP can be resolved, better-auth skips rate limiting entirely and only warns.

Production: The header your reverse proxy sets to the client's IP (x-forwarded-for, cf-connecting-ip, …).

Behind a proxy without it, every client shares one bucket, and if no IP resolves, better-auth stops rate limiting. Exposed directly, a client can spoof it.
GROWTHBOOK_API_HOST
empty by default
feature feature-flags

Production: https://cdn.growthbook.io or your GrowthBook's API host.

Required with feature-flags and empty by default. See GrowthBook.
GROWTHBOOK_CLIENT_KEY
empty by default
feature feature-flags

Production: An SDK connection's client key (sdk-…).

Where to get it: GrowthBook → SDK Connections.

.env.test (from .env.test.example) is what every test script runs against: each is wrapped in dotenv -e .env.test --. It points at the separate test stack (PostgreSQL 5499, Redis 6399), never at your development database: the e2e factory truncates every table between specs. Variables it doesn’t set (telemetry, health, Temporal) fall back to schema defaults or to .env.

VariableWhat to set
APP_ENV
default test

Production: production.

Switches logs to JSON on stdout and is stamped on all telemetry as deployment.environment.name. In nest-api, development also adds local servers to the OpenAPI document.
APP_NAME
default Acme Corp Test

Production: Your product's display name.

Used in emails, the Swagger title and better-auth. Set by --name.
APP_HOST
default 0.0.0.0

Production: 0.0.0.0 in a container.

The address the server binds to. 127.0.0.1 would make it unreachable from outside the container.
PORT
default 5001

Production: The port the process listens on (nest-api: the container's exposed port).

Changing it after generation means changing SERVER_HOST and BETTER_AUTH_URL too; --port at generation does all three.
SERVER_HOST
default http://localhost:5001

Production: The API's public URL, e.g. https://api.example.com.

Used in email links, the OpenAPI servers list and the /health HTTP check, which calls it from inside the container, so it must resolve there.
FRONTEND_HOST
default http://localhost:3000

Production: The front end's origin, e.g. https://app.example.com.

Trusted by auth and allowed by CORS. The welcome email links to it. Other front-end origins go in MISC_CORS_ORIGINS.
MISC_CORS_ORIGINS
empty by default

Production: Every other front-end origin, comma-separated; empty for a single front end.

A full origin (http://localhost:3001) is used as written; a bare hostname (example.com) expands to https origins for it and its auth., app. and admin. subdomains. See Auth, cookies and CORS.
DATABASE_URL
default postgresql://root:postgres@localhost:5499/main

Production: postgresql://<user>:<password>@<host>:5432/<db>?sslmode=require.

Where to get it: Your PostgreSQL provider.

A worker's value must be the API's database. URL-encode special characters in the password. See PostgreSQL.
BETTER_AUTH_SECRET
default test-secret-not-used-in-production

Production: A random 32-byte value: openssl rand -base64 32.

Signs sessions. The example value boots, but anyone reading the template knows it. Changing it signs every user out.
BETTER_AUTH_URL
default http://localhost:5001

Production: The API's public URL, like SERVER_HOST.

better-auth's base URL: OAuth callbacks are <this>/auth/callback/<provider>.
SUPERUSER_EMAIL
default admin@example.com

Production: The first admin's address.

yarn db:seed creates this user, verified, with the superuser role. Seeding again fails on the existing email.
SUPERUSER_PASSWORD
default Secret@1

Production: A strong password, changed after first sign-in.

Only read by the seeder.
OAUTH_GOOGLE_CLIENT_ID
empty by default

Production: Your Google OAuth client id.

Where to get it: Google Cloud console → Credentials. See Google sign-in.

Empty disables Google sign-in (the button fails).
OAUTH_GOOGLE_CLIENT_SECRET
empty by default

Production: The client's secret.

Where to get it: Google Cloud console → Credentials.

LOG_LEVEL
default silent

Production: info.

pino levels: trace, debug, info, warn, error, fatal, silent. An empty value fails validation in nest-api.
BASIC_AUTH_USER
default admin
feature openapi, queue

Production: A new operator username.

Guards /docs, /openapi, /openapi-json and /dashboard. Front ends' yarn gen uses it as API_DOC_USER.
BASIC_AUTH_PASS
default Secret@1
feature openapi, queue

Production: A strong password, not the example's.

REDIS_HOST
default localhost
feature cache

Production: Your Redis host; identical on the API and every worker.

A worker on a different Redis connects fine and never receives a job. See Redis.
REDIS_PORT
default 6399
feature cache

Production: Your Redis port (6379).

REDIS_USER
empty by default
feature cache

Production: An ACL user, if your Redis uses them.

REDIS_PASSWORD
empty by default
feature cache

Production: The Redis password.

SMTP_HOST
default smtp.example.com
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: Your mail provider's SMTP host.

See Email (SMTP), including a local catcher.
SMTP_PORT
default 465
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: 465 or 587.

No other value validates. For a local catcher, map it to 587.
SMTP_SECURE
default true
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: true with 465, false with 587 (STARTTLS).

SMTP_USER
default test
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: The SMTP username.

Where to get it: Your mail provider.

Required and empty by default: the API won't start until it's set.
SMTP_PASSWORD
default test
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: The SMTP password or API key.

Where to get it: Your mail provider.

Required and empty by default.
SMTP_FROM
default Acme Corp Test <no-reply@example.com>
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: Name <no-reply@your-domain>, on a domain with SPF, DKIM and DMARC.

Without those records, verification emails land in spam, and new users can't sign in until they verify.
SMTP_SUPPORT_EMAIL
default support@example.com
feature mail

Never delivered — AppFactory replaces MailService with a no-op.

Production: Your support address.

Shown in the welcome email; keep it equal to a landing site's VITE_APP_SUPPORT_EMAIL.
S3_REGION
default us-east-1
feature media

Production: The bucket's region.

S3_ENDPOINT
empty by default
feature media

Production: The endpoint URL, even on AWS: https://s3.<region>.amazonaws.com.

Also the prefix of stored file URLs: empty, they're relative and browsers can't load them. See Object storage.
S3_BUCKET_NAME
default acme-test
feature media

Production: The bucket.

Must allow object ACLs: uploads outside the private folders are public-read.
S3_ACCESS_KEY_ID
empty by default
feature media

Production: An access key for the bucket.

Where to get it: Your storage provider's IAM.

S3_SECRET_ACCESS_KEY
empty by default
feature media

Production: Its secret.

RATE_LIMIT_ENABLED
default false
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: true.

Off in .env.test so specs don't share a rate-limit window.
RATE_LIMIT_TTL
default 60000
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: The window for app routes, in milliseconds (default 60000).

RATE_LIMIT_MAX
default 120
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: Requests per window per client IP on app routes (default 120).

RATE_LIMIT_AUTH_WINDOW
default 10
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: better-auth's window for /auth/*, in seconds (default 10).

Sign-in, sign-up and password routes also have stricter built-in rules.
RATE_LIMIT_AUTH_MAX
default 100
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: Requests per auth window per client IP (default 100).

RATE_LIMIT_IP_HEADERS
default x-forwarded-for
feature rate-limit

Off in tests so a shared window cannot leak between specs.

Production: The header your reverse proxy sets to the client's IP (x-forwarded-for, cf-connecting-ip, …).

Behind a proxy without it, every client shares one bucket, and if no IP resolves, better-auth stops rate limiting. Exposed directly, a client can spoof it.
GROWTHBOOK_API_HOST
default http://localhost:3100
feature feature-flags

Required by config validation but never contacted — AppFactory replaces FeatureFlagService with a mock.

Production: https://cdn.growthbook.io or your GrowthBook's API host.

Required with feature-flags and empty by default. See GrowthBook.
GROWTHBOOK_CLIENT_KEY
default sdk-test
feature feature-flags

Required by config validation but never contacted — AppFactory replaces FeatureFlagService with a mock.

Production: An SDK connection's client key (sdk-…).

Where to get it: GrowthBook → SDK Connections.

Variable Used by Effect
OPENOBSERVE_STREAM telemetry Stream name; defaults to OTEL_SERVICE_NAME
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS telemetry Any OTLP backend instead of OpenObserve; takes precedence
OTEL_SDK_DISABLED=true telemetry Turns telemetry off
OTEL_METRIC_EXPORT_INTERVAL telemetry Metrics interval in ms (default 60000)
INFISICAL_PROJECT_ID, INFISICAL_PATH yarn secrets See Secrets with Infisical
INFISICAL_TOKEN, INFISICAL_API_URL Infisical CLI, CI Machine token and your Infisical host