# CLI & env vars > Source: https://docs.erpc.cloud/operation/cli > Start, validate, or inspect your eRPC config from the command line — then deploy with confidence knowing exactly what the engine will run. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # CLI & env vars Three commands cover the full config lifecycle: `start` runs the proxy, `validate` catches mistakes before they reach production, and `dump` shows the exact effective config — defaults filled, TypeScript resolved — that the engine would actually load. Point any of them at a YAML, TypeScript, or JavaScript file, or skip the file entirely and spin up a quick test node with `--endpoint`. ## Quick taste Illustrative, not a tuned production config — validate a config file in CI: **Config path:** `cli` **YAML — `erpc.yaml`:** ```yaml # shell erpc validate --config ./erpc.yaml --format json | jq .errors ``` **TypeScript — `erpc.ts`:** ```typescript // shell erpc validate --config ./erpc.ts --format json | jq .errors ``` ## Agent reference Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly: **Prompt Example #1: set up config with CI validation and safe env var injection** ```text I want to validate my eRPC config in CI before every deploy, and inject secrets like API keys and Redis passwords safely via environment variables. My config is my eRPC config. Read the full reference first: https://docs.erpc.cloud/operation/cli.llms.txt ``` **Prompt Example #2: tune metrics cardinality and histogram buckets** ```text Audit the metrics section of my eRPC config: adjust histogram bucket boundaries to match our typical latency distribution (10ms–10s), drop the user and composite labels to reduce cardinality, and make sure errorLabelMode is right for Grafana. Reference: https://docs.erpc.cloud/operation/cli.llms.txt ``` **Prompt Example #3: debug a TypeScript config that silently drops a function** ```text My eRPC TypeScript config's selectionPolicy evalFunc seems to be ignored at runtime — the dump output shows a sentinel but requests aren't being filtered. Walk me through why TS functions can be silently dropped and how to verify they were registered correctly. Config: ./eRPC config. Reference: https://docs.erpc.cloud/operation/cli.llms.txt ``` **Prompt Example #4: configure graceful shutdown for Kubernetes rolling deploys** ```text My Kubernetes deployment sees connection errors during rolling updates. Configure the waitBeforeShutdown and waitAfterShutdown values in my eRPC config, and set up INSTANCE_ID via the downward API so shared-state UpdatedBy does not collide across pods. Reference: https://docs.erpc.cloud/operation/cli.llms.txt ``` **Prompt Example #5: explain which logLevel and LOG_LEVEL to use in each environment** ```text I have three environments (dev, staging, prod) and I'm unsure which log level to set in the config file vs which to override at runtime with LOG_LEVEL. Also explain why validate and dump always suppress logs. My config lives at my eRPC config. Reference: https://docs.erpc.cloud/operation/cli.llms.txt ``` --- ### CLI & env vars — full agent reference ### How it works **Startup sequence.** `main()` registers a `signal.NotifyContext` on SIGINT/SIGTERM (`cmd/erpc/main.go:L72`), builds a CLI app via `github.com/urfave/cli/v3`, and calls `cmd.Run`. For the `start`/default action, `baseCliAction` calls `getConfig` then `erpc.Init`. `erpc.Init` (`erpc/init.go:L19`) runs in order: 1. Set zerolog global level from `cfg.LogLevel`. 2. Log the final config as JSON at INFO (may contain redacted secrets). 3. Install histogram buckets and label filter. 4. Register a global network-alias resolver for metrics label consistency. 5. Init EVM JSON-RPC cache if configured (failures are warnings, not fatal). 6. Init shared-state registry if configured (same warn-on-fail pattern). 7. Build `ERPC` core: tracing, rate limiters, proxy pool, shared-state fallback, vendors registry, projects registry. 8. Call `erpcInstance.Bootstrap(appCtx)` — eager network bootstrap in background goroutines. 9. Start HTTP server goroutine. 10. Optionally start a standalone gRPC server if gRPC is enabled and does not share the HTTP v4 port. 11. Start Prometheus metrics server. 12. Block on `<-appCtx.Done()`, then sleep `server.waitAfterShutdown` before returning. **Config file search order.** When `--config` and a positional argument are both absent and `--require-config` is not set, `getConfig` probes (`cmd/erpc/main.go:L279-L293`): 1. `./erpc.yaml` 2. `./erpc.yml` 3. `./erpc.ts` 4. `./erpc.js` 5. `/erpc.yaml` 6. `/erpc.yml` 7. `/erpc.ts` 8. `/erpc.js` 9. `/root/erpc.yaml` 10. `/root/erpc.yml` 11. `/root/erpc.ts` 12. `/root/erpc.js` First path where `fs.Stat` succeeds wins. If none match and no `--endpoint` flag was supplied, eRPC starts with a synthetic `main` project using public RPC providers. **YAML loading.** `LoadConfig` reads the file, runs `os.ExpandEnv` on raw bytes (`common/config.go:L102`), then decodes with `gopkg.in/yaml.v3` in strict mode (`KnownFields(true)` — unknown keys are fatal). After decode: legacy migration hook (`LegacyTranslateFn`, wired to `legacy.TranslateFromConfig` at `cmd/erpc/main.go:L36`) fires first, potentially emitting zerolog Warn messages for deprecated fields, then `SetDefaults`, then `Validate`. Tests that exercise legacy YAML must set `LegacyTranslateFn` manually. **TypeScript/JS loading.** `loadConfigFromTypescript` (`common/config.go:L2686`): 1. esbuild compiles the file as an IIFE bundle (`Bundle:true`, `Format:IIFE`, `Target:ES2020`, `Platform:Node`, `GlobalName:"exports"`). 2. The `tsLoaderWalker` JS fragment is appended — it depth-first walks `exports.default`, assigns IDs (`fn_0`, `fn_1`, …) to every function-typed leaf, registers them in `globalThis.__erpcFns[id]`, and stamps `fn.__erpcFnId = id`. 3. Combined source is compiled to a `sobek.Program` (`userScript`) and run in a temporary runtime. 4. `JSON.stringify` with a replacer converts functions to `"__ts_fn__:fn_"` sentinels; functions not reached by the walker are dropped silently. 5. The resulting JSON is decoded through `yaml.Decoder` with `KnownFields(true)`. 6. `cfg.UserScript` is set; the policy-engine pool re-runs this program in each acquired sobek runtime to reconstruct live `__erpcFns` references — no `.toString()` round-trip, closures preserved. **`validate` subcommand.** Loads config, calls `erpc.GenerateValidationReport` which builds a resource tree and checks for orphan rate-limit budgets, public endpoints, and static-analysis issues. Output is JSON (default) or Markdown. Exits `0` when `errors` is empty, `1` otherwise. All zerolog output is silenced via `zerolog.SetGlobalLevel(zerolog.Disabled)` before `getConfig` — no warnings from `SetDefaults` or `Validate` appear on stderr. **`dump` subcommand.** Loads config, calls `policy.ResolveEffectiveSelectionPolicies` to fill in the effective `selectionPolicy` per network as the engine would derive it, then marshals to YAML (default) or JSON. Useful for verifying what a TypeScript config actually produces. Log output is also suppressed. **Graceful shutdown.** On SIGINT/SIGTERM: `signal.NotifyContext` cancels `appCtx`. The HTTP server's goroutine wakes, sleeps `server.waitBeforeShutdown` (default 10 s — gives Kubernetes time to mark the pod NotReady), then calls `srv.Shutdown(30 s budget)`. The metrics server shuts down with a 5 s budget. The OTel tracer provider shuts down in a goroutine with a 5 s budget. `erpc.Init` blocks on `<-appCtx.Done()` then sleeps `server.waitAfterShutdown` (default 10 s) before returning, allowing in-flight requests to drain. **pprof.** A `//go:build pprof` file registers standard `net/http/pprof` routes and starts a listener at `0.0.0.0:` (default `6060`; override with `ERPC_PPROF_PORT`). Mutex and block profiling are enabled at rate 1. The standard binary does **not** include pprof — build with `-tags pprof` to activate. ### Config schema **CLI subcommands** — [`cmd/erpc/main.go:L200-244`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L200-L244) | Subcommand | Flags | Behavior | |------------|-------|----------| | `erpc [config]` (root / no subcommand) | `--config`, `--endpoint/-e`, `--require-config` | Same as `start`: load config, run `erpc.Init`. | | `erpc start` | `--endpoint/-e` (repeatable), `--require-config`; root `--config` also honored via flag lookup | Start the proxy service. | | `erpc validate` | `--format json\|md` (default `json`) | Validate config; exit 1 on any errors; logs suppressed. | | `erpc dump` | `--format yaml\|json` (default `yaml`) | Dump effective config with resolved selection policies; exit 1 on load/marshal error or unsupported format. | **CLI flags** — [`cmd/erpc/main.go:L76-94`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L76-L94) | Flag | Type | Default | Behavior / footguns | |------|------|---------|---------------------| | `--config` | string | `""` | Path to config file. When non-empty, sets `requireConfig=true`; no fallback to auto-discovery. If the path does not exist, exits 1001. | | `--endpoint` / `-e` | []string | `[]` | Zero or more upstream endpoint URLs. Validated with `url.ParseRequestURI`; invalid URLs abort with exit 1001. Injected as synthetic upstreams into the first project when no providers/upstreams exist. | | `--require-config` | bool | `false` | If `true` and no config file found, aborts. Skips auto-discovery and `--endpoint`-only mode. | | `validate --format` | string | `"json"` | Output format: `json` or `md`. | | `dump --format` | string | `"yaml"` | Output format: `yaml`, `yml`, or `json`. Any other value triggers `"unsupported format"` and exits 1. | **`--set` / `-s` is not available.** Commented out at [`cmd/erpc/main.go:L86-90`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L86-L90). Passing it produces a framework-level flag error before any application code runs. **Top-level `Config` fields** — [`common/config.go:L38-68`](https://github.com/erpc/erpc/blob/main/common/config.go#L38-L68) | YAML path | Type | Default | Behavior / footguns | |-----------|------|---------|---------------------| | `logLevel` | string | `"INFO"` | Zerolog level. Invalid value defaults to `debug` with a warning. Overridable at runtime by `LOG_LEVEL` env var. Valid: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`, `disabled`. | | `clusterKey` | string | `"erpc-default"` | Logical replica group ID for shared-state scoping. Propagated to `database.sharedState.clusterKey` only if that field is empty. Precedence: explicit `database.sharedState.clusterKey` > `clusterKey` > hard-coded default. | | `server` | \*ServerConfig | empty struct then `SetDefaults` | HTTP/gRPC server config (bind addresses, timeouts, graceful-shutdown windows). See [HTTP server](/operation/http-server.llms.txt). | | `healthCheck` | \*HealthCheckConfig | mode=`networks`, defaultEval=`any:initializedUpstreams` | Health check endpoint behaviour. | | `admin` | \*AdminConfig | nil | Admin JSON-RPC API; CORS defaults to `*` origin with no credentials when section is present. | | `database` | \*DatabaseConfig | nil | EVM JSON-RPC cache and/or shared-state connector. | | `projects` | []\*ProjectConfig | synthetic `main` project with public + envio providers when empty | Ordered list of project configs. When empty, `SetDefaults` injects a `main` project. | | `rateLimiters` | \*RateLimiterConfig | nil | Rate limiter budgets. Budget IDs referenced by projects/upstreams/networks must exist here. | | `proxyPools` | []\*ProxyPoolConfig | nil | HTTP proxy pool definitions. | | `tracing` | \*TracingConfig | nil | OTel tracing; defaults when set: protocol=grpc, endpoint=localhost:4317, sampleRate=1.0, serviceName=erpc. | | `metrics.enabled` | \*bool | `true` (non-test builds) | If `false` or nil, metrics server is not started. In test builds the default is nil. | | `metrics.port` | \*int | `4001` | Prometheus scrape port. | | `metrics.hostV4` | \*string | `"0.0.0.0"` | IPv4 bind address for the metrics server. | | `metrics.hostV6` | \*string | `"[::]"` | IPv6 bind address. | | `metrics.errorLabelMode` | LabelMode | `"compact"` | Controls the `error` label on request-error counters. `"compact"` condenses codes; `"verbose"` emits full class names. | | `metrics.histogramBuckets` | string | `""` (built-in defaults) | Comma-separated float64 bucket boundaries. Non-float values are rejected by validation. | | `metrics.histogramDropLabels` | []string | nil | Label names removed from every histogram to reduce cardinality. | | `metrics.histogramLabelOverrides` | map[string][]string | nil | Per-metric label keep-list; key is metric name without `erpc_` prefix. | **Environment variables** | Env var | Where applied | Effect | |---------|---------------|--------| | `LOG_LEVEL` | `init()` and after config decode | Zerolog global level. Applied twice: first during config-loading so those logs respect it; second after decode to override `cfg.LogLevel`. Invalid value falls back to `debug`. Valid: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`, `disabled`. | | `LOG_WRITER` | `init()` | When `"console"`, switches from JSON to a human-readable colored writer (time format `"04:05.000ms"`). Must be set before process start; cannot be changed at runtime. | | `ERPC_NOLOGS` | `init()` (build tag `!test`) | When `"1"`, silences zerolog globally and replaces the writer with `io.Discard`. Intended for test runs. | | `ERPC_NOMETRICS` | `init()` (build tag `!test`) | When `"1"`, swaps the Prometheus default registry with a no-op. Irreversible within the process. | | `ERPC_PPROF_PORT` | `init()` (build tag `pprof`) | pprof listener port; default `6060`. Only effective when built with `-tags pprof`. Binds `0.0.0.0` — all interfaces. | | `ERPC_IGNORE_LOCAL_ENDPOINT_VALIDATION` | config analyzer | When `"true"`, skips validation of non-HTTP and local (loopback/localhost) upstream endpoints in `erpc validate`. [[`erpc/config_analyzer.go:L1024-1031`](https://github.com/erpc/erpc/blob/main/erpc/config_analyzer.go#L1024-L1031)] | | `INSTANCE_ID` | shared-state + consensus | First in the instance-identity chain. Used as `UpdatedBy` in shared-state writes and as `{instanceId}` in consensus dispute-log filenames. | | `POD_NAME` | shared-state + consensus | Second in the chain (Kubernetes pod name). | | `HOSTNAME` | shared-state + consensus | Third in the chain (OS hostname). | | `FORCE_TEST_LISTEN_V4` | `common/defaults.go` | When `"true"` in test builds, forces `server.listenV4=true`. | | `$VAR` / `${VAR}` in YAML | before YAML parse | `os.ExpandEnv` on raw bytes. Unset vars resolve to empty string. Footgun: if the expanded value contains `:` or `{`, quote the YAML value. | | `$VAR` / `${VAR}` in `server.responseHeaders` | HTTP server construction | Expanded once at server startup via `os.ExpandEnv`. If the expanded value is empty, the header entry is silently dropped from all responses. Runs after full config decode; distinct from YAML-level expansion. [[`erpc/http_server.go:L135-148`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L135-L148)] | | `$VAR` / `${VAR}` in provider-generated upstream endpoints | provider resolution | Expanded when the vendor's `GenerateConfigs` returns each `UpstreamConfig.Endpoint` string. Distinct from YAML-level expansion — runs at request time, not config-load time. [[`thirdparty/provider.go:L67-71`](https://github.com/erpc/erpc/blob/main/thirdparty/provider.go#L67-L71)] | | `process.env.` in TS | sobek runtime creation | Full `os.Environ()` snapshot passed to TS/JS config runtime as a `process.env` object; also exposed as a flat `env` array of `"KEY=VALUE"` strings. A snapshot — not a live view. Because `.env` is loaded in `init()` before the TS bundle is evaluated, `.env` values are available here too. | **Instance-identity priority chain.** Two functions resolve instance ID: - Shared-state: `INSTANCE_ID` → `POD_NAME` → `HOSTNAME` → `os.Hostname()` → `"unknown"`. Whitespace is trimmed at each step. [`data/shared_state_registry.go:L82-98`](https://github.com/erpc/erpc/blob/main/data/shared_state_registry.go#L82-L98) - Consensus: same env-var priority order but final fallback is SHA-256(unixNano+pid) truncated to 8 hex chars (computed once via `sync.Once`). Characters problematic for filenames (`:`, `/`, `\`, ` `, `*`, `?`, `"`) are sanitized to `_` before the value is used as the `{instanceId}` token in dispute-log filenames. [`consensus/export_utils.go:L26-48`](https://github.com/erpc/erpc/blob/main/consensus/export_utils.go#L26-L48) Setting `INSTANCE_ID` explicitly avoids both failure modes. **`validate` command output schema** (JSON): ```json { "errors": [], "warnings": [], "notices": [], "resources": { "totals": { "projectsTotal": 0, "networksTotal": 0, "upstreamsTotal": 0, "rateLimitBudgetsTotal": 0 }, "tree": { "projects": [{ "id": "", "networks": [{ "id": "", "upstreams": [{ "id": "" }] }] }], "rateLimiters": { "budgets": [{ "id": "", "rulesCount": 0 }] } } } } ``` Exit `0` if `errors` is empty, exit `1` otherwise. **Exit codes** — [`util/exit.go:L7-10`](https://github.com/erpc/erpc/blob/main/util/exit.go#L7-L10) - `1001` (`ExitCodeERPCStartFailed`) — CLI/config load failure or `erpc.Init` error. - `1002` (`ExitCodeHttpServerFailed`) — HTTP or gRPC server fatal error. ### Worked examples All patterns below are distilled from real production fleets; comments explain the non-obvious choices. **1. CI validation gate before deploy.** Production deployments run `erpc validate` in the pipeline before any image build or Helm upgrade. Exit code 0 means no errors; pipe through `jq .errors` for a clean failure message in CI logs without JSON noise: ```bash # exits 0 when errors=[], exits 1 when errors is non-empty erpc validate --config ./erpc.yaml --format json | jq '.errors' # Markdown for PR comments or human-readable output in CI erpc validate --config ./erpc.yaml --format md ``` **2. Production log level: error in prod, info in staging, info in dev.** Real fleets keep `logLevel: error` in production (high-QPS internal workloads) to suppress routine health-check noise, `info` in staging and dev where visibility matters. `LOG_LEVEL=debug` at runtime (without a deploy) is the escape hatch when diagnosing live issues: **Config path:** `logLevel` **YAML — `erpc.yaml`:** ```yaml # Production — suppress routine INFO chatter; errors surfaced by structured log logLevel: error # Staging / dev — info gives bootstrap/network visibility without trace flood # logLevel: info ``` **TypeScript — `erpc.ts`:** ```typescript // Production logLevel: 'error', // Staging / dev // logLevel: 'info', ``` **3. Tuned metrics for multi-region fleet.** Production edge deployments drop the `user` and `composite` histogram labels (high-cardinality from many API keys and composite request patterns) and set explicit bucket boundaries that match real p50/p99 latencies observed in the fleet. `errorLabelMode: compact` is always used in production to keep Prometheus cardinality bounded: **Config path:** `metrics` **YAML — `erpc.yaml`:** ```yaml metrics: enabled: true port: 4001 hostV4: "0.0.0.0" # Bucket edges aligned to observed p50 (~100ms) and p99 (~3s) latencies. # Coarser than the default to keep time-series count down on large fleets. histogramBuckets: "0.020,0.100,0.300,0.500,1,3,6,10" # compact keeps error labels bounded; verbose creates a label value per # error class — too many on multi-upstream deployments. errorLabelMode: compact # Drop high-cardinality labels from histograms only; counters keep them. histogramDropLabels: - user - composite ``` **TypeScript — `erpc.ts`:** ```typescript metrics: { enabled: true, port: 4001, hostV4: '0.0.0.0', // Bucket edges aligned to observed p50 (~100ms) and p99 (~3s) latencies. histogramBuckets: '0.020,0.100,0.300,0.500,1,3,6,10', errorLabelMode: 'compact', histogramDropLabels: ['user', 'composite'], }, ``` **4. Graceful shutdown for Kubernetes rolling deploys.** Both Kubernetes and PaaS production deployments use 30s for both shutdown windows. `waitBeforeShutdown` gives Kubernetes time to update endpoints before the server starts refusing connections; `waitAfterShutdown` lets in-flight requests drain. `INSTANCE_ID` via the downward API prevents `UpdatedBy` collisions across pods in shared-state writes: **Config path:** `server` **YAML — `erpc.yaml`:** ```yaml server: # Allow Kubernetes to mark the pod NotReady before traffic stops arriving. # Without this, rolling deploys produce connection errors during the gap. waitBeforeShutdown: 30s # Drain in-flight requests before the process exits. waitAfterShutdown: 30s ``` **TypeScript — `erpc.ts`:** ```typescript server: { // Allow Kubernetes to mark the pod NotReady before traffic stops arriving. waitBeforeShutdown: '30s', // Drain in-flight requests before the process exits. waitAfterShutdown: '30s', }, ``` Kubernetes pod spec fragment for the downward API: ```yaml env: - name: INSTANCE_ID valueFrom: fieldRef: fieldPath: metadata.name # stable pod name; avoids UpdatedBy = "unknown" ``` **5. Per-environment clusterKey for shared-state isolation.** Each deployment uses a distinct `clusterKey` so prod, staging, and dev never share lock state. The top-level key propagates automatically unless `database.sharedState.clusterKey` is set explicitly: **Config path:** `database.sharedState` **YAML — `erpc.yaml`:** ```yaml database: sharedState: # Unique per deployment — prod/stage/dev must not share lock state. # If this key collides across two fleets, they race each other's locks. clusterKey: prod-us-east-1 connector: driver: redis redis: uri: \${SHARED_STATE_REDIS_URL} ``` **TypeScript — `erpc.ts`:** ```typescript database: { sharedState: { // Unique per deployment — prod/stage/dev must not share lock state. clusterKey: 'prod-us-east-1', connector: { driver: 'redis', redis: { uri: '\${SHARED_STATE_REDIS_URL}' }, }, }, }, ``` **6. Quick smoke-test with a bare endpoint, no config file.** Spin up eRPC against a single node to verify routing — no YAML required. Useful for local testing before writing a full config: ```bash erpc --endpoint https://mainnet.infura.io/v3/$INFURA_KEY # equivalent: erpc start --endpoint https://mainnet.infura.io/v3/$INFURA_KEY ``` **7. Inspect the effective config produced by a TypeScript config.** TypeScript arrow functions in `selectionPolicy.evalFunc` show up as `__ts_fn__:fn_N` sentinels; everything else renders as plain YAML. Use this to verify closures were detected by the walker: ```bash erpc dump --config ./erpc.ts --format yaml # or JSON for programmatic diffing: erpc dump --config ./erpc.ts --format json | jq . ``` ### Request/response behavior The CLI layer itself does not process JSON-RPC requests. The behaviors below govern how the bootstrap interacts with the process environment. - **`.env` file** in `os.Getwd()` is loaded in `init()` before `main()` runs; values are available for YAML `$VAR` substitution. Non-existence is silently ignored; other errors are logged at ERROR. [[`cmd/erpc/main.go:L42-46`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L42-L46)] - **`LOG_LEVEL` env var overrides `cfg.LogLevel`** after config load; a config-file `logLevel: error` can be trumped by `LOG_LEVEL=debug` without editing the file. [[`cmd/erpc/main.go:L354-363`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L354-L363)] - **`validate` and `dump` suppress all log output.** `zerolog.SetGlobalLevel(zerolog.Disabled)` is called before `getConfig` runs (`validate`: `cmd/erpc/main.go:L109`; `dump`: `cmd/erpc/main.go:L156`); `LOG_LEVEL` has no effect during these commands. - **YAML strict-mode** rejects unknown fields (`KnownFields(true)`); this applies to the JSON round-trip from TypeScript configs too. [[`common/config.go:L103`](https://github.com/erpc/erpc/blob/main/common/config.go#L103)] - **Shorthand upstream URLs** (`alchemy://KEY`, `infura://KEY`, etc.) are converted to `ProviderConfig` and removed from `p.Upstreams` at `SetDefaults` time; after load they appear in `p.Providers`, not `p.Upstreams`. [[`common/defaults.go:L1164-1172`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1164-L1172)] - **Key validation rules** enforced by `Config.Validate()`: `server.maxTimeout` must be non-zero; each project needs at least one upstream or provider; `selectionPolicy.evalTimeout` must be strictly less than `evalInterval`; `onlyNetworks` and `ignoreNetworks` on a provider are mutually exclusive; `database.sharedState.lockMaxWait` and `updateMaxWait` must each be less than `fallbackTimeout`; `database.sharedState.lockTtl` must be at least as long as `fallbackTimeout`; connector-level failsafe may not use `consensus` or `hedge.quantile`. [[`common/validation.go:L15`](https://github.com/erpc/erpc/blob/main/common/validation.go#L15)] ### Best practices - Run `erpc validate --config ./erpc.yaml` in every CI pipeline before shipping config changes — it catches unknown field typos, missing rate-limit budget references, and `selectionPolicy` compile errors without starting a server. - Use `erpc dump --config ./erpc.ts --format yaml` when debugging TypeScript configs to see what the engine actually sees; TS function sentinels (`__ts_fn__:fn_N`) confirm functions were detected correctly. - Always quote YAML values that may contain env vars with special characters: `password: "${REDIS_PASSWORD}"` not `password: ${REDIS_PASSWORD}` — a `:` in the expanded value will break YAML parsing silently. - Set `INSTANCE_ID` (or `POD_NAME` via the Kubernetes downward API) explicitly in every deployment — without it, shared-state `UpdatedBy` falls back to `"unknown"` and collides across pods; consensus dispute-log filenames get unique hashes but shared-state writes do not. - Do not build with `-tags pprof` in production unless you immediately firewall-restrict `ERPC_PPROF_PORT` — the listener binds `0.0.0.0`, not localhost. - Set `LOG_WRITER=console` only for local development; JSON output is required by every log-aggregation platform (Datadog, Grafana Loki, etc.) and the env var cannot be changed at runtime. - In Kubernetes, add a `preStop` hook or rely on `server.waitBeforeShutdown` (default 10 s) to let the pod reach NotReady before requests stop arriving — skip this and you will see connection errors during rolling deploys. ### Edge cases & gotchas 1. **`--config` sets `requireConfig=true` implicitly.** If the specified path does not exist, the process exits 1001 with no fallback to auto-discovery. [[`cmd/erpc/main.go:L300-302`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L300-L302)] 2. **Positional argument works in all subcommands.** `erpc validate ./config.yaml` and `erpc dump ./config.yaml` both work — first positional arg is treated as a config path with `requireConfig=true`. [[`cmd/erpc/main.go:L303-305`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L303-L305)] 3. **YAML strict-mode rejects unknown fields.** A typo in any YAML key is a fatal error — this also applies to the JSON round-trip from TypeScript configs. 4. **Quoted YAML values required for env vars that may contain special chars.** `password: "${REDIS_PASSWORD}"` is safe; `password: ${REDIS_PASSWORD}` is not if the value contains `:` or `{`. 5. **TypeScript default-export must be the last statement.** If a later expression overwrites `exports.default`, the earlier config object is lost. Error: `"config object must be default exported from TypeScript code AND must be the last statement in the file"`. [[`common/config.go:L2715-2717`](https://github.com/erpc/erpc/blob/main/common/config.go#L2715-L2717)] 6. **TS functions in bare arrays are not walked.** The `tsLoaderWalker` only descends into object properties, not bare array function elements. In practice `selectionPolicy` functions are always object values so this rarely matters. 7. **Orphaned TS function silently dropped.** A function not reached by the walker is dropped by the JSON.stringify replacer — the config field will be absent with no error. [[`common/config.go:L2736-2739`](https://github.com/erpc/erpc/blob/main/common/config.go#L2736-L2739)] 8. **`validate`/`dump` suppress all log output.** `zerolog.SetGlobalLevel(zerolog.Disabled)` is called before `getConfig`. Warnings from `SetDefaults`, legacy migration, and validation do not appear. Use `jq .errors` on the JSON output. `LOG_LEVEL` has no effect here. 9. **`LOG_WRITER=console` must be set before process start.** Configured in `init()`, not hot-reloadable. In log-aggregation environments keep the default JSON writer. 10. **pprof binds `0.0.0.0`.** All interfaces, not localhost-only. Firewall-restrict or avoid `-tags pprof` in production. 11. **`ERPC_NOMETRICS=1` is irreversible.** Swaps the Prometheus default registry process-wide; cannot be undone within the same process. 12. **`INSTANCE_ID` not stable across restarts = orphaned shared-state locks.** Use a deployment-stable value (pod name, container ID). 13. **`dump --format csv` (or any unknown format) exits 1 via a distinct code path.** A marshal failure means config loaded but serialisation failed; an unsupported-format failure means serialisation was never attempted. Valid values: `yaml`, `yml`, `json`. 14. **Shared-state falls back to `"unknown"` while consensus generates a hash.** In environments with no env vars and `os.Hostname()` failing, shared-state `UpdatedBy` is `"unknown"` (collides across pods). Consensus dispute-log filenames get a unique 8-char hash. [[`data/shared_state_registry.go:L82-98`](https://github.com/erpc/erpc/blob/main/data/shared_state_registry.go#L82-L98)] [[`consensus/export_utils.go:L26-48`](https://github.com/erpc/erpc/blob/main/consensus/export_utils.go#L26-L48)] 15. **Provider endpoint env expansion runs after YAML-level expansion.** Pipeline: (1) `os.ExpandEnv` on raw YAML bytes → YAML decode → `SetDefaults` (providers registered). Then at request time: (2) provider's `GenerateConfigs` builds endpoint strings → `expandEnvVars` calls `os.ExpandEnv`. Double-expansion is possible if the vendor builds an endpoint that itself contains `$VAR` literals. [[`thirdparty/provider.go:L67-71`](https://github.com/erpc/erpc/blob/main/thirdparty/provider.go#L67-L71)] 16. **`--set` / `-s` is not implemented.** The flag was planned as a Helm-style dot-path override but is commented out. Passing `-s` or `--set` produces `"flag provided but not defined: -s"` from the CLI framework. [[`cmd/erpc/main.go:L86-90`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L86-L90)] 17. **Metrics server disabled in test builds.** `MetricsConfig.SetDefaults` sets `Enabled=true` only when `!util.IsTest()`. Tests that need metrics must set `metrics.enabled: true` explicitly. [[`common/defaults.go:L750-752`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L750-L752)] 18. **gRPC shares the HTTP handler when both addresses resolve to the same port.** No separate `grpcServer.Start` goroutine runs. [[`erpc/init.go:L125`](https://github.com/erpc/erpc/blob/main/erpc/init.go#L125)] 19. **Eager network bootstrap runs on `appCtx`; novel chains trigger the lazy path.** Novel chain IDs arriving before bootstrap finishes block the request until init completes or the request context times out. [[`erpc/networks_registry.go:L195-212`](https://github.com/erpc/erpc/blob/main/erpc/networks_registry.go#L195-L212)] 20. **TypeScript `process.env` is a snapshot, not a live view.** Built once when the sobek runtime is created from `os.Environ()`; a restart-less env var change requires all policy engine runtimes to be recreated. ### Observability The bootstrap/CLI layer emits no Prometheus metrics of its own. Key structured log messages (zerolog JSON unless `LOG_WRITER=console`): | Log message | Level | When | |-------------|-------|------| | `"executing command"` (fields: `action`, `version`, `commit`) | INFO | Start of every CLI action | | `""` with `config` field | INFO | Final config logged before init — may contain redacted secrets | | `"initializing eRPC core"` | INFO | Before `NewERPC` | | `"initializing transports"` | INFO | After core init, before server start | | `"shutting down gracefully..."` | INFO | On context cancel | | `"no projects found in config; will add a default 'main' project"` | WARN | Synthetic project injected | | `"no providers or upstreams found in project; will use default 'public' endpoints repository"` | WARN | Public fallback provider injected when project has no upstreams or providers | | `"failed to initialize evm json rpc cache: ..."` | WARN | Cache init failure (non-fatal) | | `"failed to initialize shared state registry: ..."` | WARN | Shared-state init failure (non-fatal) | | `"invalid log level '...', defaulting to 'debug'"` | WARN | Invalid `LOG_LEVEL` or `logLevel` value | | `"pprof server started at http://localhost:"` | INFO | Only when built with `-tags pprof` | | `"networks bootstrap completed"` | INFO | After all configured networks initialised | ### Source code entry points - [`cmd/erpc/main.go:L72-L305`](https://github.com/erpc/erpc/blob/main/cmd/erpc/main.go#L72-L305) — signal handling, CLI command definitions, `getConfig`, auto-discovery, `--endpoint` injection - [`erpc/init.go:L19-L180`](https://github.com/erpc/erpc/blob/main/erpc/init.go#L19-L180) — `Init(ctx, cfg)`: full startup + graceful drain sequence - [`common/config.go:L95-L110`](https://github.com/erpc/erpc/blob/main/common/config.go#L95-L110) — `LoadConfig`: `os.ExpandEnv`, YAML decode, legacy migration, `SetDefaults`, `Validate` - [`common/config.go:L2686-L2740`](https://github.com/erpc/erpc/blob/main/common/config.go#L2686-L2740) — `loadConfigFromTypescript`: esbuild, `tsLoaderWalker`, sobek eval, sentinel lifecycle - [`common/defaults.go:L49-L176`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L49-L176) — `Config.SetDefaults`: full defaults cascade, synthetic `main` project injection - [`common/validation.go:L15`](https://github.com/erpc/erpc/blob/main/common/validation.go#L15) — `Config.Validate`: exhaustive validation rules - [`cmd/erpc/pprof.go`](https://github.com/erpc/erpc/blob/main/cmd/erpc/pprof.go) — build-tag `pprof`: `init()` registering pprof routes and starting `0.0.0.0:` listener - [`cmd/erpc/initflags.go`](https://github.com/erpc/erpc/blob/main/cmd/erpc/initflags.go) — build-tag `!test`: `ERPC_NOLOGS` and `ERPC_NOMETRICS` init hooks - [`util/exit.go:L7-L10`](https://github.com/erpc/erpc/blob/main/util/exit.go#L7-L10) — exit codes `1001` (start failed) and `1002` (HTTP/gRPC server fatal) - [`erpc/config_analyzer.go`](https://github.com/erpc/erpc/blob/main/erpc/config_analyzer.go) — `GenerateValidationReport`, `RenderValidationReportJSON`, `RenderValidationReportMarkdown` - [`common/runtime.go`](https://github.com/erpc/erpc/blob/main/common/runtime.go) — `NewRuntime()`: creates a sobek runtime, populates `process.env` map and `env` array from `os.Environ()`; used for TypeScript selection-policy evaluation - [`common/compiler.go`](https://github.com/erpc/erpc/blob/main/common/compiler.go) — `CompileTypeScript` (esbuild IIFE bundle), `CompileFunction` (sobek single-function eval), `CompileProgram` (sobek.Compile with paren-wrap) ### Related pages - [HTTP server](/operation/http-server.llms.txt) — server bind addresses, timeouts, and graceful shutdown knobs. - [Deployment](/deployment/docker.llms.txt) — Docker and Kubernetes deployment patterns, including `POD_NAME` downward API setup. - [Projects](/config/projects.llms.txt) — the config structure that `start` and `validate` both load. - [Rate limiters](/config/rate-limiters.llms.txt) — budget IDs that validation checks for orphan references. - [Observability](/operation/metrics.llms.txt) — Prometheus metrics exposed on `metrics.port`. --- ## Navigation (machine-readable surface) - Up: [All pages index](https://docs.erpc.cloud/llms.txt) - Root index of every page: [llms.txt](https://docs.erpc.cloud/llms.txt) · everything in one file: [llms-full.txt](https://docs.erpc.cloud/llms-full.txt) ### Sibling pages - [Admin API](https://docs.erpc.cloud/operation/admin.llms.txt) — A built-in operator control plane — inspect topology, cordon sick upstreams without restarts, and manage API keys, all over a secure JSON-RPC 2.0 endpoint. - [Batching & multiplexing](https://docs.erpc.cloud/operation/batch.llms.txt) — Send one request, get back a merged response — eRPC parallelises inbound batch arrays, re-batches calls to supporting upstreams, and collapses identical in-flight requests so each unique call hits the network exactly once. - [Cordoning](https://docs.erpc.cloud/operation/cordoning.llms.txt) — Pull any upstream out of routing instantly with one admin call — no metric window to wait for, no config redeploy required. - [Directives](https://docs.erpc.cloud/operation/directives.llms.txt) — Send an HTTP header or query param and change routing, caching, validation, or consensus for exactly that one request — no restarts, no config changes. - [Healthcheck](https://docs.erpc.cloud/operation/healthcheck.llms.txt) — One endpoint that tells Kubernetes exactly when your pod is ready, draining, or broken — with eight probe strategies from "any upstream alive" to live chain-ID verification. - [Monitoring & metrics](https://docs.erpc.cloud/operation/monitoring.llms.txt) — Every subsystem in eRPC — upstreams, cache, rate limits, consensus, hedging — emits Prometheus metrics. One scrape target, full visibility, zero instrumentation work. - [Production checklist](https://docs.erpc.cloud/operation/production.llms.txt) — Go live confidently — a short list of settings that separate a hardened eRPC deployment from a dev-mode one. - [Tracing & logging](https://docs.erpc.cloud/operation/tracing.llms.txt) — Every request, cache lookup, and upstream call becomes a searchable span — shipped to any OTel backend. Secrets never leave the process. - [URL structure](https://docs.erpc.cloud/operation/url.llms.txt) — One URL pattern routes every chain — domain and network aliases let you publish clean, memorable endpoints without touching your app code.