Environment variables behind the Self-Hosting deployment.
For source development, use Local Setup and the repository's .env.sample. Many values can embed the literal string <subdomain>, which getEnv (utils.ts) replaces with the request tenant at runtime; useful for per-tenant URLs and keys.
Runtime vs build-time
Backend services read everything below from the container environment at startup; restart to apply changes. The Core UI container is different: its entrypoint writes every REACT_APP_* variable into a browser-readable js/env.js at startup, so REACT_APP_* values behave as runtime configuration for erxes/erxes-next-ui even though the same names are baked into the bundle as fallbacks. The standalone apps (client-portal-template, help-center, posclient-front) read NEXT_PUBLIC_* values at their own build or entrypoint time; see their app pages.
Core runtime
| Variable |
Read by |
Required |
Default |
Notes |
NODE_ENV |
All backend + UI |
Yes |
none |
production in deployment; development enables dev remotes, router download, and CORS extras |
PORT |
Each service |
No |
4000 gateway, 3300 core-api, 3301 logs, 3302 automations, 3303+ plugins |
Per-container override |
VERSION |
core-api, gateway, service discovery |
No |
os |
os or saas; switches /initial-setup, plugin discovery, and charge checks |
RELEASE_VERSION |
Plugin APIs on registration |
No |
none |
Values starting with 3. become the releaseVersion used in plugin UI asset URLs; anything else becomes latest |
LOAD_BALANCER_ADDRESS |
Every backend service |
Recommended in production |
derived |
Address registered in Redis; defaults to http://plugin-<name>-api:<port> in production, http://localhost:<port> in dev |
GET_CP_TOKEN |
core-api |
Only to enable the route |
none |
Shared secret for GET /get-client-portal-token?GET_CP_TOKEN= |
TEMPLATE_EXPORT_KEY |
core-api |
For template export |
none |
Passphrase hashed to the AES-256-GCM key that encrypts template exports |
Database
| Variable |
Read by |
Required |
Default |
Notes |
MONGO_URL |
All backend services |
Yes |
mongodb://127.0.0.1:27017/erxes?directConnection=true |
Must reach all replica-set members from the containers; the logs service derives <db>_logs from it |
DB_NAME |
erxes-api-shared (saas) |
saas only |
erxes_<organizationId> |
Tenant database name template |
CORE_MONGO_URL |
backend/saas-migrations scripts |
For those migrations |
none |
Some scripts accept a domain-specific override instead of MONGO_URL |
Redis
| Variable |
Read by |
Required |
Default |
Notes |
REDIS_HOST |
All backend services |
Yes |
none |
Shared ioredis client, BullMQ queues, GraphQL pubsub |
REDIS_PORT |
All backend services |
No |
6379 |
none |
REDIS_PASSWORD |
All backend services |
When Redis requires auth |
none |
Must match the Redis container's requirepass; there is no REDIS_URL |
SKIP_REDIS |
posclient sync utils |
No |
none |
When set, POS config sync throws token not found instead of creating a config for an unknown token |
Redis holds service discovery (erxes-service-*, erxesservice:config:*), session tokens (user_token_*), plugin lists (erxes-active-plugins), and every BullMQ queue. Persist it (--appendonly yes) and use noeviction.
URLs, domains, and CORS
| Variable |
Read by |
Required |
Default |
Notes |
DOMAIN |
gateway, core-api, services |
Yes |
http://localhost:3000 |
Public UI origin; also the CORS allow-list anchor and the stored hostname fallback |
GATEWAY_URL |
core-api email links |
No |
${DOMAIN}/gateway |
Public gateway prefix used in emails, unsubscribe links, webhooks |
WIDGETS_DOMAIN |
gateway, core-api |
No |
http://localhost:3200 |
Widgets origin added to CORS |
ALLOWED_DOMAINS |
gateway, core-api, services |
No |
none |
Extra comma-separated CORS origins |
ALLOWED_ORIGINS |
gateway, core-api, services |
No |
none |
Comma-separated regexes appended to the CORS origin list |
CLIENT_PORTAL_DOMAINS |
logs, automations services |
No |
none |
Extra origins for the background services' CORS |
TRUST_PROXY |
All backend services |
No |
loopback, linklocal, uniquelocal |
Express trust proxy; widen only if your LB appends client IPs |
Auth and sessions
| Variable |
Read by |
Required |
Default |
Notes |
JWT_TOKEN_SECRET |
gateway, core-api, plugins |
Yes |
SECRET |
Signs and verifies user, portal, and app JWTs. Change it and keep it identical on every backend service |
SAME_SITE |
core-api |
No |
none |
Set to none to emit cross-site auth cookies where the code supports it |
WORKOS_API_KEY, WORKOS_PROJECT_ID |
core-api |
For WorkOS SSO |
none |
SSO login mutations |
OAUTH_DEVICE_VERIFICATION_URI |
core-api |
No |
built-in |
OAuth device-flow verification page override |
Plugin and service selection
| Variable |
Read by |
Required |
Default |
Notes |
ENABLED_PLUGINS |
gateway, core-api, dev scripts |
For plugins |
none |
Comma-separated base names (sales,operation); controls gateway waits and /get-frontend-plugins |
ENABLED_PLUGINS_ONLY_API |
gateway, dev scripts |
No |
none |
Backend-only plugins without frontend remotes |
ENABLED_SERVICES |
scripts/start-api-dev.js |
Dev only |
none |
automations,logs → *-service Nx projects; not read by Docker images |
MAX_PLUGIN_RETRY |
gateway |
No |
unlimited |
Bounds plugin-join and /graphql readiness retries at startup |
Gateway tuning
| Variable |
Read by |
Required |
Default |
Notes |
APOLLO_ROUTER_PORT |
gateway |
No |
50000 |
Loopback port for the internal router |
INTROSPECTION |
gateway |
No |
off in production |
true enables GraphQL introspection in production |
GRAPHQL_LIMITER |
gateway |
No |
off |
When set, applies depth/alias/character limits to /graphql |
SUPERGRAPH_POLL_INTERVAL_MS |
gateway (dev) |
No |
10000 |
Dev-mode recompose interval |
DEBUG_GATEWAY_AUTH |
gateway |
No |
false |
Verbose auth, proxy, and limiter request logs |
Workers and retention
| Variable |
Read by |
Required |
Default |
Notes |
LOG_RETENTION_DAYS |
erxes-api-shared logs schema |
No |
365 |
TTL index on the _logs database logs collection |
REVERT_AUTO_JOURNAL_MAX |
erxes-api-shared |
No |
1000 |
Max documents snapshotted per bulk write for the undo journal |
SEGMENT_CONCURRENCY_CHANGED / _FORGET / _REBUILD / _RECONCILE |
logs-service |
No |
10 / 5 / 1 / 3 |
BullMQ concurrency per segment queue |
SEGMENT_RECONCILE_CRON / _TZ / _BATCH / _STEPS, SEGMENT_REBUILD_PAGE, SEGMENT_PREVIEW_BUDGET_MS, SEGMENT_TIME_ZONE |
segment engine |
No |
built-in |
Segment sweep tuning |
TIMEZONE |
sales board/POS report queries |
No |
0 (hour offset) / UTC |
Numeric hour offset in sales date math |
Email
| Variable |
Read by |
Required |
Default |
Notes |
SENDGRID_API_KEY |
core-api auth emails |
For SendGrid |
none |
Throws SENDGRID_API_KEY is missing in environment when a SendGrid path runs without it |
SENDGRID_WEBHOOK_PUBLIC_KEY |
core-api broadcast trackers |
For SendGrid event webhooks |
none |
Verifies inbound event signatures; a deployment-level key marks the webhook shared |
AWS_SES_ACCESS_KEY_ID, AWS_SES_SECRET_ACCESS_KEY, AWS_SES_CONFIG_SET, AWS_REGION |
broadcast transporter |
For SES |
none |
SES sending path |
DEFAULT_FROM_EMAIL |
erxes-api-shared email |
No |
none |
Fallback sender; COMPANY_EMAIL_FROM system config overrides per tenant |
EMAIL_DISPOSABLE_DOMAINS |
core-api intake |
No |
none |
Extra disposable-domain list |
EMAIL_PROVEN_WINDOW_DAYS |
core-api |
No |
180 |
Proven-address window |
EMAIL_RAMP_TIERS, EMAIL_SOFT_BOUNCE_LIMIT |
core-api broadcast |
No |
built-in tiers / 3 |
Sending ramp and bounce suppression |
MAIL_DOMAIN, MAIL_WORKER_URL, MAIL_WEBHOOK_SECRET, MAIL_TENANT, MAIL_SENDING_ACCOUNT_ID, MAIL_SENDING_API_TOKEN, MAIL_SENDING_DAILY_LIMIT, MAIL_INBOUND_RATE_LIMIT, MAIL_RECEIVE_URL |
frontline_api mail channel |
For the mail integration |
none |
Cloudflare Email Routing → worker → /mail/receive; see .env.sample and the frontline plugin guide |
Frontend runtime (REACT_APP_*)
Set on the core-ui container; injected into window.env at startup and falling back to build-time values:
| Variable |
Default |
Notes |
REACT_APP_API_URL |
<proto>//<host>/gateway (prod), http://localhost:4000 (dev) |
Public gateway URL the browser calls; <subdomain> in the value is replaced by the hostname's first label |
REACT_APP_IMAGE_CDN_URL |
none |
Image CDN base |
REACT_APP_GOOGLE_MAP_API_KEY |
none |
Maps integration |
REACT_APP_HIDE_CORE_MODULES |
none |
Hides built-in navigation modules |
REACT_APP_SENTRY_DSN, REACT_APP_SENTRY_ENVIRONMENT |
none |
Frontend Sentry |
Variables on the standalone apps include NEXT_PUBLIC_ERXES_API_URL (client-portal-template, help-center; the latter injects it into env.js via its own docker-entrypoint.sh), NEXT_PUBLIC_ERXES_CP_TOKEN (client-portal-template only), and NEXT_PUBLIC_MAIN_API_DOMAIN, NEXT_PUBLIC_APP_VERSION (posclient-front). Check each app's Dockerfile/entrypoint: a container env override only works where the app injects runtime env; otherwise rebuild after changing them.
Observability
| Variable |
Read by |
Required |
Default |
Notes |
SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_RELEASE, SENTRY_SERVER_NAME, SENTRY_TRACES_SAMPLE_RATE |
All backend services |
No |
off |
Sentry error tracing; tag as plugin/service |
Not env vars
Storage and upload settings are system configs stored in the database, read via configs.getConfigs: UPLOAD_SERVICE_TYPE, AWS_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_COMPATIBLE_SERVICE_ENDPOINT, AWS_FORCE_PATH_STYLE, AWS_PREFIX, GOOGLE_CLOUD_STORAGE_BUCKET, GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_PROJECT_ID, CLOUDFLARE_*, AZURE_STORAGE_*, FILE_SYSTEM_PUBLIC. Set them in Settings → file upload, not in .env.