Skip to content
This page is a translation of the authoritative Chinese source and may lag behind.View the original

Environment variable reference

The official image already ships a built-in runtime configuration — you do not need to write or mount any config file; just override the items you want via environment variables. docker run -e, docker-compose's environment: section, and k8s container env all work. After changing them, docker restart <container-name> takes effect.

This page lists all environment variables the image accepts, their defaults, and value descriptions. The default is the image's built-in value (written in the Dockerfile's ENV); if unset, that default applies.

Business configuration (upstreams, models, access keys, key groups, load balancers, MCP, ACL, scripts, SSO) is not here — it lives in the database and is managed by the console, taking effect immediately upon change, unrelated to environment variables.

Set these first

Under the zero-config main path the image bootstraps the console and admin; the following only need to be set when you have the corresponding requirement:

  • ENCRYPTION_KEY — set before configuring upstream API keys or issuing access keys, otherwise saving errors with encryption_key not set in config; generate with openssl rand -hex 32
  • CONSOLE_PASSWORD — to preset the console admin password instead of the random one printed in the logs (only takes effect on first startup)
  • LICENSE_KEY — activate the license to unlock multi-instance / Redis / PostgreSQL and higher memory limits
  • STORAGE_MODE together with POSTGRES_URL and REDIS_URL — required for multi-instance deployment
  • RESET_ADMIN — a one-time reset when you forget the console password

Two rules for values and defaults

1. Empty ≠ unset ≠ code default. Setting a variable to an empty string (VAR=) means "off / none" and is interpreted as None. In the image you cannot "unset" a variable to fall back to some deeper code default — the image's ENV is the default. For example: the code's built-in default for STREAM_IDLE_TIMEOUT_SECS is 300 seconds, but the image ships 600 seconds; to get 300, write STREAM_IDLE_TIMEOUT_SECS=300 explicitly rather than leaving it empty.

2. List-type variables use comma-separated strings. CORS_ORIGINS, CORS_HEADERS, CORS_METHODS, CORS_EXPOSE_HEADERS, FALLBACK_DNS_SERVERS, and TRUSTED_PROXIES all accept strings in the form a,b,c (auto-split on commas, trimmed, empty items dropped) and also accept true arrays. All other variables are single-valued.

Listening and request limits

VariableDefaultPurpose and values
HOST0.0.0.0Listen address; 127.0.0.1 means localhost only
PORT7890Listen port
MAX_REQUEST_SIZE_MB50Maximum request body (MB), applies to all routes
MAX_RESPONSE_BODY_MB25Upstream response body limit (MB, non-streaming only)
WORKER_THREADS2Number of async runtime worker threads. 2 is the safe lower bound for 0.25 vCPU, not a recommendation — increase it with available CPU (e.g. set 4 for 4 vCPUs)
REQUEST_TIMEOUT_SECSempty (= unlimited)Per-request total duration limit; empty means unenforced
GRACEFUL_SHUTDOWN_TIMEOUT_SECSemptyWait limit for graceful shutdown after receiving a signal
PRE_STOP_DELAY_SECSemptyDelay before shutdown, giving the load balancer time to drain traffic
DATA_PLANE_MAX_CONNECTIONS0Data-plane connection budget: the maximum number of in-flight requests the proxy routes allow; requests exceeding the limit are rejected before being written to disk with an immediate 429 + Retry-After; console / health checks / monitoring are unaffected. 0 = derived automatically at startup from the fd (RLIMIT_NOFILE) limit; the effective value is printed in the startup log. The fd limit itself should be raised — see the fd section of the Docker single-node tutorial
CONNECTION_BUDGET_RESERVE_FDS0When deriving the connection budget automatically, the number of fds reserved for connections opened lazily after startup (PG pools, Redis, log queue); 0 = enumerate automatically

Storage and multi-instance

VariableDefaultPurpose and values
STORAGE_MODEpostgresqlPersistence backend: sqlite (single-node) / postgresql (shared across instances). The image defaults to postgresql + empty POSTGRES_URL; the zero-config path only works because the license layer force-falls-back to sqlite when unlicensed
SQLITE_PATH/var/lib/protoflux/stats.sqliteSQLite file path (single-node mode)
POSTGRES_URLemptyPostgreSQL connection string; the sslmode query parameter controls TLS (disable/require/verify-full). Required for multi-instance
POSTGRES_POOL_SIZE8Maximum concurrent PG connections
POSTGRES_CONSOLE_POOL_SIZE4A separate connection pool for console (login/user/audit) queries, isolated from the data plane — the management entry stays reachable even when the data plane saturates the main pool
POSTGRES_WAIT_TIMEOUT_SECS10Connection-pool acquisition timeout; 0 waits forever
POSTGRES_STATEMENT_TIMEOUT_SECS30Server-side single-statement timeout for the stats pool; 0 = unlimited
REDIS_URLemptyRedis connection string (multi-instance: shared sessions / rate limits / log broadcast / IP bans); if unset, everything is in-memory
REDIS_KEY_PREFIXprotoflux:Redis key prefix, to distinguish multiple gateway deployments sharing one Redis
ENCRYPTION_KEYemptyEncryption key for sensitive data at rest (access keys, upstream API keys); generate with openssl rand -hex 32

About ENCRYPTION_KEY

  • Inject it only via -e / secret — do not write it into the image layer or compose plaintext
  • Be sure to back up this key: losing it = already-encrypted data is permanently unreadable
  • In a multi-instance deployment, all instances must use the same key
  • The gateway can start with an empty value, but the moment it needs to read/write an encryption key it fail-closes with an error — set it before configuring upstream API keys

Console

VariableDefaultPurpose and values
CONSOLE_ENABLEDtrueWhether the console is enabled; false means no admin is created and /console is unreachable
CONSOLE_PASSWORDemptyInitial password for the console admin protoflux, only takes effect on first startup (when the console user table is empty); later changes are ignored. If empty, a strong random password is generated on first startup and printed once to the log
CONSOLE_SECRET_KEYemptyBearer token for the console API (long-lived, for scripts/CI to call /console/api/* directly; matching it grants admin rights). Empty = this auth method is disabled and not auto-generated (what is auto-generated is the admin's initial password via CONSOLE_PASSWORD). When non-empty it must be ≥ 12 characters
CONSOLE_ALLOW_REMOTEtrueWhether remote access to the console is allowed. Set false and access from the host under Docker Desktop/bridged networking returns 403
CONSOLE_MAX_FAILURES5Consecutive login-failure limit; reaching it bans the source IP
CONSOLE_BAN_DURATION300Login ban duration (seconds)
CONSOLE_IP_BAN_ENABLEDtrueWhether IP banning is enabled
CONSOLE_SESSION_AUTO_RENEWtrueWhether sessions auto-renew

CORS and network trust

VariableDefaultPurpose and values
CORS_ORIGINSemptyAllowed cross-origin origins (comma-separated); empty = browser CORS stays off
CORS_HEADERSemptyAllowed request headers (comma-separated)
CORS_METHODSemptyAllowed methods (comma-separated)
CORS_EXPOSE_HEADERSemptyResponse headers the frontend may read (comma-separated)
CORS_MAX_AGE7200Preflight result cache seconds
CORS_CREDENTIALSfalseWhether credentials are allowed
TRUSTED_PROXIESemptyTrusted proxy IP/CIDR list (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/16); only when set is X-Forwarded-For trusted to resolve the real client IP
IP_RATE_LIMIT_RPMempty (= unlimited)Global per-minute request limit per IP; empty means no per-IP rate limiting
METRICS_AUTH_TOKENemptyAuth token for the /metrics endpoint; when set, Authorization: Bearer <token> is required

Upstream retry and routing affinity

VariableDefaultPurpose and values
UPSTREAM_IDLE_CONNECTIONS16Connection pool size per upstream host
UPSTREAM_SEND_TIMEOUT_SECS180Timeout for sending the request body + waiting for response headers (TTFB)
UPSTREAM_READ_TIMEOUT_SECS600Per-read timeout for a single response chunk (reset each chunk); must be greater than STREAM_IDLE_TIMEOUT_SECS
UPSTREAM_USER_AGENTProtofluxDefault User-Agent used toward upstreams
FALLBACK_DNS_SERVERS1.1.1.1,8.8.8.8,119.29.29.29,223.5.5.5Fallback DNS pool for upstreams without their own DNS config (comma-separated); empty = disables the fallback
MAX_UPSTREAM_RETRIES3Maximum upstream retries per request (before the first byte)
MAX_KEY_ROTATIONS0Maximum key rotations per request (when an upstream has multiple keys); 0 = unlimited (rotate through all available keys)
LB_AFFINITY_TTL_SECS300Load-balancing affinity binding lifetime in seconds; 0 = permanent binding
LOAD_WINDOW_SECS60Window seconds for computing upstream load
KEY_BINDING_TTL_SECS1800Lifetime in seconds of the API-key-to-upstream auto binding
UPSTREAM_BAD_LINK_BUDGETempty (= auto)Per-upstream bad-link budget: the maximum number of in-flight requests on that upstream that may pile up after exceeding the threshold without receiving a first response. Once the budget is reached the upstream is excluded from candidates; when all candidates are saturated the request is rejected immediately with 503 + Retry-After instead of queuing and dragging down healthy traffic. Empty = auto, half the initial permit count; 0 = disabled. Pure real-time in-flight counting — a request's slot is released the moment it finishes (any way), and the upstream is back to full capacity as soon as it recovers, with no failure memory
UPSTREAM_BAD_LINK_THRESHOLD_SECS60Bad-link threshold (seconds): an in-flight request that has received no first response beyond this duration counts as one bad link for that upstream
UPSTREAM_DISPATCH_DEADLINE_SECS300Total time budget for a single request's entire outbound attempt chain (retries + key rotations + connection-pool recovery). When exceeded, the attempt chain is terminated and 504 + Retry-After is returned — a hung request's held permits are guaranteed to be released within a bounded time. 0 = disabled (escape hatch only)

Streaming

VariableDefaultPurpose and values
STREAMING_KEEPALIVE_SECONDS15Interval for the SSE :keep-alive comment; must be shorter than the reverse proxy's idle timeout (Nginx defaults to 60s, so 15s is safe)
STREAMING_BOOTSTRAP_RETRIES1Retries before the first byte; no retries after the first chunk is sent
STREAMING_LOG_TIMEOUT_SECS3600Timeout for the SSE stream-log background task (releases zombie tasks whose "sender is gone")
STREAM_IDLE_TIMEOUT_SECS600Maximum idle gap between two data chunks; exceeding it drops the stream
MAX_STREAM_DURATION_SECS3600Hard cap on the total duration of a single SSE stream

Log retention and disk

VariableDefaultPurpose and values
LOG_LEVELinfoLog level (error/warn/info/debug/trace)
LOG_FORMATjsonLog format (json/plain)
LOG_MAX_BODY_SIZE_MB25Per-log captured request/response body limit (MB)
LOG_BODY_TO_TERMINALfalseWhether to also write the log body to stdout
LOG_PERSIST_REQUEST_LOGSfalseWhether to persist request logs to disk/database
LOG_RETENTION_DAYS7Request-log retention days; 0 = disable auto-cleanup
OMS_RETENTION_DAYS30OpenAI message-store retention days; 0 = disable auto-cleanup
OTEL_ENDPOINTemptyOpenTelemetry export endpoint; empty means no reporting
OTEL_SERVICE_NAMEprotofluxService name reported to OTEL
SENTRY_DSNemptyBackend Sentry project DSN (Rust panic/error); empty means the backend doesn't report (compiled into the binary by default). Injected at image build time via --build-arg SENTRY_DSN (CI uses secrets.SENTRY_DSN), overridable at runtime via -e
SENTRY_FRONTEND_DSNemptyFrontend Sentry project DSN (browser JS errors); empty means client-config falls back to SENTRY_DSN (same project for frontend and backend). Set a different project to isolate frontend noise
SENTRY_ENVIRONMENTproductionSentry environment tag
SENTRY_RELEASEemptySentry release identifier; empty defaults at runtime to protoflux@<version>
SENTRY_TRACES_SAMPLE_RATE0Sentry performance sampling rate (0.0–1.0); 0 = errors/crashes only
LOG_QUEUE_DIRemptyDisk directory for the log queue; empty = fall back to the system temp directory. Ignored when REDIS_URL is set (multi-instance uses Redis Pub/Sub)
LOG_QUEUE_SEGMENT_SIZE_MB64Log-queue single-segment file size (MB)
LOG_QUEUE_MAX_DISK_SIZE_MB1024Log-queue total disk cap (MB)
LOG_STREAM_BODY_MAX_DISK_MB1024SSE stream-log body disk spill cap (MB)
LOG_REQUEST_BODY_MAX_DISK_MB1024Request-body disk storage cap (MB)
LOG_ACCUMULATOR_MAX_DISK_MB256Log-accumulator disk cap (MB)
LOG_STREAM_BODY_BATCH_SIZE_KB64SSE stream-log body batch write size (KB)
LOG_STREAM_BODY_LINGER_MS5SSE stream-log body linger milliseconds
LOG_STREAM_BODY_CHANNEL_CAPACITY_CHUNKS2048SSE stream-log body channel capacity (chunks)

Memory admission and spill

Under memory pressure the gateway uses admission control + disk spill to keep the process from being killed. Defaults suffice in most cases; only tune under burst load or container OOM.

VariableDefaultPurpose and values
MEMORY_SOFT_LIMIT_MB0Soft memory limit (MB); 0 = no soft limit (the container cgroup is the backstop)
MEMORY_QUEUE_TIMEOUT_SECS300Timeout for a memory-queue item waiting for admission
MEMORY_QUEUE_MAX_DEPTHempty (= auto)Depth cap of the slow-path waiting queue (counts requests waiting for a processing permit, not concurrent processing — four states): empty = auto, initial permit count × 4 (bounded safe default); -1 = unlimited (explicit escape hatch); 0 = zero wait (no queueing allowed; requests that can't get a permit are rejected immediately); n = cap of n. Requests beyond the cap are rejected immediately with 429 + Retry-After (reason queue_full), without waiting for MEMORY_QUEUE_TIMEOUT_SECS. Concurrent processing capacity is determined by processing permits, not limited by this item
SPILL_WRITE_CONCURRENCY4Concurrent write count for disk spill
SPILL_WRITE_STALL_TIMEOUT_SECS10Entry write-stall timeout (seconds); reaching it with zero write progress while a slot is waiting rejects with 503 + Retry-After (hung-volume defense), range 1–300
MEMORY_HOLD_SECS5Hold seconds for a memory item
MEMORY_RATE_ESCALATE_MB40Memory rate-escalation threshold (MB)
MEMORY_ADMISSION_ENABLEDtrueWhether admission control is enabled. Feed-forward admission makes per-request structural decisions (admit / spill-to-queue), closing the burst blind spot where a batch of requests all read a stale Normal RSS and then pass through with zero cost
MEMORY_FORCE_SPILL_BODY_MB0Force-spill body threshold (MB); 0 = don't force

Note: MEMORY_ADMISSION_HANDLER_PERMIT (per-handler MB budget per slot) has been removed — concurrency is no longer derived from a static budget but driven by the predictive ConcurrencyController (target = budget / measured per-request cost). To pin concurrency manually, use the TOML memory_admission_handler_permits (>0 disables the controller).

Script sandbox

For script-sandbox limits see Script runtime limits and configuration.

VariableDefaultPurpose and values
SCRIPT_MAX_OPERATIONS2000Maximum operations per QuickJS script execution
SCRIPT_MEMORY_LIMIT_MB64Interpreter heap cap (MB)
SCRIPT_LAZY_BODYtrueWhether to lazily project the request/response body

Per-key rate limiting

Field descriptions are in Audit and security configuration.

VariableDefaultPurpose and values
RATE_LIMIT_ENABLEDfalseWhether per-key rate limiting is enabled
RATE_LIMIT_RPM120Per-key per-minute request limit
RATE_LIMIT_WINDOW_SECS60Rate-limit window seconds

Image-level variables

These are read directly by the process and are not in the config-field table.

VariableDefaultPurpose and values
LICENSE_KEYemptyLicense key (Ed25519). Empty = unlicensed: 512MB memory cap and Redis/PostgreSQL disabled. Activating unlocks multi-instance and higher memory limits
RESET_ADMINemptyOne-time reset when you forget the console password: set it to a new password and restart to change the admin protoflux password to that value. Takes effect only once (the process writes a one-time marker) — see Docker single node
TZUTCContainer timezone
MALLOC_CONFbackground_thread:true,narenas:1,...jemalloc config (background threads, arena count, dirty-page reclaim, profiling)
DEVemptyPresence-only (setting it enables it, value irrelevant). Proxies /console/* to the Vite dev server — local development only

Config-file-only fields

The following fields can only be configured via server.toml; the official image does not expose them as environment variables, so they are not in the env-var tables above. They can be set when deploying via config file (not the image env-var path):

Field (TOML)DefaultPurpose
server.max_global_concurrency1000Global concurrency cap for all proxy routes; reaching it returns 503 overloaded_error; set 0 to disable
streaming.max_concurrent_streams200Global concurrency cap for SSE streaming requests; reaching it returns 503
server.script_pool_size4Number of concurrent script execution slots; each slot's heap is bounded by SCRIPT_MEMORY_LIMIT_MB
sso_credentials.refresh_enabledtrueMaster switch for the upstream SSO credential background refresh task; hot-read each scan cycle, can be stopped without downtime
sso_credentials.refresh_interval_secs60Refresh scan interval seconds (lower bound 5)
sso_credentials.refresh_lead_secs300Refresh lead window: credentials expiring within this many seconds are refreshed immediately
sso_credentials.refresh_max_parallel4Global concurrent refresh-call cap per scan (per-credential concurrency is always 1, not configurable)

These concurrency/slot caps are not fixed values; the sources of 503 are in Error codes → Sources of 503.

Per-section reference pages

FAQ

Q: I want to change a deeper parameter that isn't on this page. This page is the full set of environment variables the image exposes. A few more fields can only be configured via server.toml (see config-file-only fields); deeper tuning items are not exposed — contact support if needed.

Q: How do changes take effect? After changing environment variables, docker restart <container-name> suffices. Console-side business configuration (upstreams, models, keys, etc.) takes effect immediately.

Q: Startup reports a config error. The image self-checks configuration at startup; errors in the log give the specific field and reason. Change the corresponding environment variable as prompted, then docker restart <container-name>.

Next: Logs and body storage / Script runtime limits and configuration / Audit and security configuration / Pricing and billing fields / MCP configuration / Load-balancing fields for specific config references.