Overview
Copy base/backend/.env.sample to base/backend/.env (or wire the same keys through Docker Compose). Plugin-scoped runtime keys use @InjectEnv / the env table — see PluginEnv. Never commit real secrets.
| Topic | Detail |
|---|---|
| File | base/backend/.env (template: base/backend/.env.sample) |
| Compose | Many keys are also set under the backend service environment |
| PluginEnv | Operator-editable plugin keys — different layer from this page |
| Secrets | Local sample values are for development only — rotate for staging / production |
Runtime & identity
| Variable | Description | Example |
|---|---|---|
| NODE_ENV | Node runtime mode | production |
| MODE | App mode (template often uses dev) | dev |
| BASE_VERSION | Base platform version — keep aligned with @quan-erp/* and image tags | 1.0.0 |
| ORGANIZATION_ID | Organization identifier for this deployment | quan-erp |
| DEBUG_LOG | Optional debug scope | plugin-manager |
Service URLs
| Variable | Description | Example |
|---|---|---|
| FRONTEND_URL | Public frontend origin | http://localhost |
| BACKEND_URL | Backend API base | http://localhost:8080 |
| DEV_TOOL_URL | Base dev tools | http://localhost:8081 |
| PLUGIN_SERVER_URL | Plugin server | http://localhost:8082 |
| CORS_ALLOWED_ORIGINS | Comma-separated browser origins allowed to call the API | http://localhost:3000,... |
Auth tokens
Rotate all token secrets before any shared or production deploy.
| Variable | Description | Example |
|---|---|---|
| ACCESS_TOKEN_SECRET | JWT access-token signing secret | change-me |
| ACCESS_TOKEN_SECRET_EXPIRATION_TIME | Access token TTL | 15m |
| REFRESH_TOKEN_SECRET | JWT refresh-token signing secret | change-me |
| REFRESH_TOKEN_SECRET_EXPIRATION_TIME | Refresh token TTL | 30d |
OpenAI-compatible AI
| Variable | Description | Example |
|---|---|---|
| OPENAI_BASE_URL | API base URL | https://api.deepseek.com |
| OPENAI_API_KEY | Provider API key (leave empty if unused) | — |
Database
Inside Compose, hosts are usually service names (pgbouncer / db), not localhost. DB_PORT is often 6432 when traffic goes through PgBouncer.
| Variable | Description | Example |
|---|---|---|
| DATABASE_HOST | Postgres or PgBouncer host (alias used by some entrypoints) | pgbouncer |
| DB_HOST | Postgres or PgBouncer host | pgbouncer |
| DB_PORT | Database port (6432 via PgBouncer, 5432 direct) | 6432 |
| DB_USERNAME | Database username | postgres |
| DB_PASSWORD | Database password | change-me |
| DB_SCHEMA | Schema name | quan-erp |
| DB_SYNC | TypeORM synchronize — local/dev only; use migrations in staging/production | true |
Redis
| Variable | Description | Example |
|---|---|---|
| REDIS_HOST | Redis service name | redis |
| REDIS_PORT | Redis port | 6379 |
| REDIS_PASSWORD | Redis auth password | change-me |
Cluster
| Variable | Description | Example |
|---|---|---|
| CLUSTER_MODE | Enable multi-process cluster | false |
| CLUSTER_MAX_COUNT | Max worker processes when cluster is on | 2 |
Encryption & Firebase
Do not paste production private keys into git, docs, or chat — load them from a secret store or local .env only.
| Variable | Description | Example |
|---|---|---|
| ENV_ENCRYPTION_KEY | Key used to encrypt sensitive env values at rest | change-me-… |
| FIREBASE_CONFIG | Single-line JSON string for the Firebase Admin service account (push / auth) | {"type":"service_account",…} |
Folders & files
| Variable | Description | Example |
|---|---|---|
| AVAILABLE_PLUGINS_FOLDER | Staged plugin builds | /app/available-plugins |
| INSTALLED_PLUGINS_FOLDER | Active installed plugins | /app/installed-plugins |
| APP_DATA_FOLDER | Persistent app data root | /app/data/data/ |
| UPLOAD_FILE_TEMP_FOLDER | Temporary upload staging | /app/data/temp |
| UPLOAD_FILE_FOLDER | Persisted uploads | /app/data/data/ |
| PLUGINS_ENV_FILE | Plugin env JSON file path | /app/data/data/plugin.env.json |
| RUNNER_FLOW_FILE | Workflow definitions directory | /app/data/data/workflows/ |
Admin, limits & subscription
| Variable | Description | Example |
|---|---|---|
| ROOT_ADMIN_USERNAME | Bootstrap root admin username | admin |
| ROOT_ADMIN_PASSWORD | Bootstrap root admin password — change outside local template | change-me |
| MAX_USER_LIMIT | Max users for the org | 5 |
| MAX_ACTIVE_USER_LIMIT | Max concurrently active users | 5 |
| MAX_USER_SESSION_LIMIT_PER_USER | Sessions per user | 1 |
| MAX_ORGANIZATION_BRANCH | Max branches | 1 |
| MAX_ROLE_LIMIT | Max roles | 5 |
| MAX_SUBSCRIPTION_GRACE_PERIOD_DAYS | Grace days after subscription end | 14 |
| DEVELOPER_CONFIG_PASSWORD | Password gate for developer config UI / APIs | change-me |
| SUBSCRIPTION_END_DATE | Subscription end date | 2026-01-01 |
Sample (.env.sample shape)
Illustrative keys matching base/backend/.env.sample. Replace every change-me value. Secrets below are placeholders only.
1NODE_ENV=production
2
3MODE=dev
4BASE_VERSION=1.0.0
5ORGANIZATION_ID=quan-erp
6
7FRONTEND_URL=http://localhost
8BACKEND_URL=http://localhost:8080
9DEV_TOOL_URL=http://localhost:8081
10PLUGIN_SERVER_URL=http://localhost:8082
11
12DEBUG_LOG=plugin-manager
13
14ACCESS_TOKEN_SECRET=change-me
15ACCESS_TOKEN_SECRET_EXPIRATION_TIME=15m
16REFRESH_TOKEN_SECRET=change-me
17REFRESH_TOKEN_SECRET_EXPIRATION_TIME=30d
18
19OPENAI_BASE_URL=https://api.deepseek.com
20OPENAI_API_KEY=
21
22# database
23DATABASE_HOST=pgbouncer
24DB_HOST=pgbouncer
25DB_USERNAME=postgres
26DB_PASSWORD=change-me
27DB_PORT=6432
28DB_SCHEMA=quan-erp
29DB_SYNC=true
30
31# redis
32REDIS_HOST=redis
33REDIS_PORT=6379
34REDIS_PASSWORD=change-me
35
36CLUSTER_MODE=false
37CLUSTER_MAX_COUNT=2
38
39ENV_ENCRYPTION_KEY=change-me-32-char-hex-or-secret
40
41CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,http://localhost,https://localhost
42
43AVAILABLE_PLUGINS_FOLDER=/app/available-plugins
44INSTALLED_PLUGINS_FOLDER=/app/installed-plugins
45
46APP_DATA_FOLDER=/app/data/data/
47UPLOAD_FILE_TEMP_FOLDER=/app/data/temp
48UPLOAD_FILE_FOLDER=/app/data/data/
49PLUGINS_ENV_FILE=/app/data/data/plugin.env.json
50RUNNER_FLOW_FILE=/app/data/data/workflows/
51
52FIREBASE_CONFIG={"type":"service_account","project_id":"your-project",...}
53
54ROOT_ADMIN_USERNAME=admin
55ROOT_ADMIN_PASSWORD=change-me
56
57MAX_USER_LIMIT=5
58MAX_ACTIVE_USER_LIMIT=5
59MAX_USER_SESSION_LIMIT_PER_USER=1
60MAX_ORGANIZATION_BRANCH=1
61MAX_ROLE_LIMIT=5
62MAX_SUBSCRIPTION_GRACE_PERIOD_DAYS=14
63DEVELOPER_CONFIG_PASSWORD=change-me
64SUBSCRIPTION_END_DATE=2026-01-01Critical rules
| Rule | Detail |
|---|---|
| Start from sample | Use .env.sample — do not invent a second naming scheme |
| No secrets in git | Never commit real .env files or Firebase private keys |
| Version alignment | Align BASE_VERSION with base images and @quan-erp/* packages |
| Production harden | Turn off DB_SYNC and rotate secrets before production |
| Two layers | Plugin business config → PluginEnv (@InjectEnv); host infra → this file |