/deployment/docker.llms.txt
Docker
Pull, mount your config, and you're live. eRPC's official image is distroless and ~30 MB — nothing but the proxy binary and TypeScript config support. Expose two ports and traffic flows immediately; an optional compose stack brings up Prometheus and Grafana with 80+ pre-built panels in the same command.
docker run -v $(pwd)/erpc.yaml:/erpc.yaml \
-p 4000:4000 -p 4001:4001 \
ghcr.io/erpc/erpc:latestAgent 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: get eRPC running in Docker
Deploy eRPC using Docker for the first time. Mount my config from my eRPC config, expose the RPC and metrics ports, set GOMEMLIMIT correctly for the container, and pin the image to a specific release tag rather than :latest. Work with my existing eRPC config. Read the full reference first: https://docs.erpc.cloud/deployment/docker.llms.txt
Prompt Example #2: spin up local dev stack with Grafana
Set up a full local dev environment with eRPC, Redis, Prometheus, and Grafana using docker-compose so I can see the 80+ dashboard panels. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/deployment/docker.llms.txt
Prompt Example #3: debug a crashing container
My eRPC Docker container is restarting unexpectedly in production. Help me diagnose whether it is an OOM kill (missing GOMEMLIMIT), a bad config mount, or a graceful shutdown issue. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/deployment/docker.llms.txt
Prompt Example #4: profile memory or CPU in staging
Enable the pprof profiling build of eRPC in my staging Docker container so I can capture heap and CPU profiles to investigate high memory usage. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/deployment/docker.llms.txt
Docker — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.
How it works
Image structure. The Dockerfile uses six named build stages: go-builder compiles both erpc-server (production) and erpc-server-pprof (pprof build tag); ts-core installs shared pnpm dependencies on Node 20 alpine; ts-dev and ts-prod extend ts-core for their respective build targets; symlink creates a /root/erpc-server → /erpc-server compatibility symlink; final copies only the two Go binaries, the symlink, and compiled TypeScript files into a gcr.io/distroless/static-debian12:nonroot base. CGO is disabled and -ldflags="-w -s" strips debug info. The default CMD is /erpc-server.
Ports. The image exposes three ports:
4000— JSON-RPC HTTP ingress (defaultserver.httpPortV4)4001— Prometheus metrics (/metrics)6060— pprof (only functional in theerpc-server-pprofbinary)
Config discovery. At startup eRPC searches for a config file in this order: --config flag → positional arg → ./erpc.yaml, ./erpc.yml, ./erpc.ts, ./erpc.js, then the same paths under / and /root/. The simplest production mount is -v $(pwd)/erpc.yaml:/erpc.yaml.
Compose stack. The repo's docker-compose.yml defines a monitoring service (Grafana on :3000, Prometheus on :9090) and a redis service (redis:latest, port 6379:6379, restart always, on the erpc bridge network). The eRPC container entry is commented out by default (references ghcr.io/erpc/erpc:main) — operators add it back with a volume-mounted config. Additional optional services (Jaeger with OTLP gRPC/HTTP and UI, PostgreSQL, ScyllaDB in Alternator mode) are also present as commented-out blocks. Point monitoring at a running eRPC instance:
SERVICE_ENDPOINT=host.docker.internal SERVICE_PORT=4001 docker compose up monitoringmake down passes --volumes, which destroys prometheus_data and grafana_data named volumes; use docker compose down (no --volumes) to preserve monitoring history.
Distroless/nonroot implications. The final image has no shell, no package manager, and no standard utilities. You cannot exec into it with a shell. Attaching debug tools or copying files at runtime requires a privileged ephemeral sidecar. All file paths must be absolute. If you mount a config that does not exist, the process exits cleanly (non-zero) — it does not hang.
TypeScript configs (erpc.ts) are supported: the image includes compiled TypeScript SDK files and node_modules. Node itself is not present as a standalone binary, but the bundled runtime path invoked by eRPC for TS config evaluation does work. If you strip node_modules from the image, TypeScript configs fail silently and fall back to YAML.
Layer caching. go.mod and go.sum are copied into the builder stage first and go mod download runs before the source copy, so dependency layers are cached across rebuilds (Dockerfile:L16-L21 (opens in a new tab)). The pnpm stages use --mount=type=cache,id=pnpm,target=/pnpm/store to avoid re-downloading npm packages across builds (Dockerfile:L51 (opens in a new tab), L63 (opens in a new tab)).
CLI subcommands. The erpc binary supports three subcommands:
erpc [config]orerpc start— start the servererpc validate [--format json|md]— parse config and run a validation report; exits non-zero on errorserpc dump [--format yaml|json]— parse config, resolve effective selection policies, and dump to stdout
Monitoring container internals. monitoring/Dockerfile pulls Prometheus v3.9.1 and Grafana 12.3.3 (both pinned by digest) into an ubuntu:22.10 base. The entrypoint substitutes SERVICE_ENDPOINT and SERVICE_PORT into prometheus.yml using sed, then starts both processes in the background and tails /dev/null to keep the container alive. If either process crashes, the container stays up but becomes non-functional. This design is intentional for local dev convenience; run separate Prometheus and Grafana containers in production.
Config schema
The deployment layer has no erpc.yaml fields. The following environment variables control the running process:
| Variable | Effect | Source |
|---|---|---|
LOG_LEVEL | Overrides cfg.LogLevel; parsed as zerolog level (trace/debug/info/warn/error) | cmd/erpc/main.go:L354-363 |
LOG_WRITER | If "console" → human-readable zerolog output with 04:05.000ms timestamps; default is JSON | cmd/erpc/main.go:L53-57 |
GOMEMLIMIT | Go soft memory limit (e.g. GOMEMLIMIT=2700MiB). Not set in any manifest — operators must set manually. Rule of thumb: 90% of container memory limit. | n/a |
GOGC | Go GC target percentage. Lower values (25–50) keep heap smaller at slightly higher CPU cost. Default is Go runtime default (100). | n/a |
SERVICE_ENDPOINT | Monitoring container only — injected into Prometheus scrape target by monitoring/scripts/entrypoint.sh. Default target is host.docker.internal:4001. | monitoring/scripts/entrypoint.sh:L1 |
SERVICE_PORT | Same as above. | monitoring/scripts/entrypoint.sh:L1 |
Worked examples
1. Minimal single-container run. The simplest production-ready invocation: mount config, expose both ports, no compose required:
docker run --restart=unless-stopped \
-v $(pwd)/erpc.yaml:/erpc.yaml \
-e GOMEMLIMIT=2700MiB \
-p 4000:4000 -p 4001:4001 \
ghcr.io/erpc/erpc:latestSet GOMEMLIMIT to 90% of your container's memory allowance to prevent Go GC from allowing the heap to reach the hard OOM limit.
2. Full local dev stack with monitoring. Spin up eRPC, Redis, Prometheus, and Grafana together. Add the eRPC service block back into docker-compose.yml, then:
SERVICE_ENDPOINT=erpc SERVICE_PORT=4001 docker compose upOpen http://localhost:3000 for Grafana (default credentials admin/admin). The dashboard auto-loads with 80+ panels.
3. Profiling build. Override CMD to the pprof variant when debugging performance in a staging environment. The binary is always present in every image:
docker run -v $(pwd)/erpc.yaml:/erpc.yaml \
-p 4000:4000 -p 4001:4001 -p 6060:6060 \
--entrypoint /erpc-server-pprof \
ghcr.io/erpc/erpc:latestAccess pprof endpoints at http://localhost:6060/debug/pprof/.
4. make docker-run. The Makefile wraps the single-container run: it maps ports 4000:4000 and 4001:4001, mounts ./erpc.yaml:/erpc.yaml, and explicitly invokes /erpc-server --config /erpc.yaml rather than relying on the image's default CMD. Equivalent to: Makefile:L127-L129 (opens in a new tab)
docker run -p 4000:4000 -p 4001:4001 \
-v ./erpc.yaml:/erpc.yaml \
ghcr.io/erpc/erpc:latest /erpc-server --config /erpc.yaml5. Pinned production image. Never run :latest in production — pin a specific tag or SHA:
docker run -v $(pwd)/erpc.yaml:/erpc.yaml \
-p 4000:4000 -p 4001:4001 \
ghcr.io/erpc/erpc:v0.0.xRequest/response behavior
- Graceful shutdown:
SIGINT/SIGTERMtriggerssignal.NotifyContext; the HTTP server drains in-flight requests, returns 503 on healthcheck, then callsShutdown. [cmd/erpc/main.go:L72-74] - The
/healthcheckendpoint returns 200 while healthy and 503 during drain — wire it to Docker'sHEALTHCHECKinstruction or a load-balancer probe. VERSIONandCOMMIT_SHAbuild args are injected via-Xldflags intocommon.ErpcVersionandcommon.ErpcCommitShaat build time and surfaced in startup logs and the/healthcheckresponse body. [Dockerfile:L24-34]
Best practices
- Always set
GOMEMLIMITto 90% of the container's memory limit. Without it, Go's GC may allow the heap to reach the hard OOM limit, triggering a container restart rather than a managed GC cycle. - Pin the image tag to a specific release (e.g.
ghcr.io/erpc/erpc:v0.0.x) rather than:latestfor reproducible deploys. - Expose port 4001 even if you're not using the monitoring stack yet — it's zero cost and unlocks observability later without a redeploy.
- Use
docker compose down(without--volumes) to stop the local stack;make downpasses--volumesand destroys all Prometheus/Grafana state. - Do not use the monitoring container in production. Prometheus and Grafana share one process group — a crash in either is not independently restarted. Use separate containers for production monitoring.
- Mount a non-existent config → clean exit. If the path you mount at
/erpc.yamldoesn't exist, eRPC exits non-zero immediately — verify your bind-mount paths before starting. - gRPC port needs explicit mapping. The image exposes
4000,4001,6060. If you configure a separateserver.grpcPortV4, add-p <grpc_port>:<grpc_port>explicitly.
Edge cases & gotchas
- No shell in distroless: Cannot exec with a shell; debug tools require privileged sidecars.
make downdestroys volumes:Makefile:L114passes--volumes— this dropsprometheus_dataandgrafana_data. Usedocker compose down(without--volumes) to preserve monitoring state.GOMEMLIMITnot set in any manifest: With a 3 Gi container limit and noGOMEMLIMIT, Go's GC may allow the heap to approach 3 Gi before collecting, triggering container OOM. SetGOMEMLIMIT=2700MiB(90% of limit).- pprof binary always present but never used by default:
/erpc-server-pprofexists in every image but the defaultCMDruns/erpc-server. To use profiling endpoints, overrideCMDto/erpc-server-pprofand expose port6060. SERVICE_ENDPOINTsed-based substitution: If the value contains/or other sed-special characters, substitution breaks. Use a plain hostname or IP, not a URL with a path.- Monitoring container is not HA: Both Prometheus and Grafana run in one container via
tail -f /dev/null; a crash in either is not detected and the container stays running. Not suitable for production. - gRPC port not exposed by default: The image exposes
4000,4001,6060. If you configure a separateserver.grpcPortV4, add-p <grpc_port>:<grpc_port>explicitly. prometheus.yamlat repo root is NOT the monitoring container's config: It is the production Prometheus Operator config. The monitoring container usesmonitoring/prometheus/prometheus.yml. They have different scrape configs and targets.
Observability
All eRPC metrics are emitted at port 4001 path /metrics. The monitoring container's Prometheus scrapes this endpoint at scrape_interval: 10s and evaluation_interval: 10s. The local Prometheus config (monitoring/prometheus/prometheus.yml) defines two scrape jobs: prometheus (self-scrape at localhost:9090) and erpc (dual targets: host.docker.internal:4001 plus a placeholder replaced by SERVICE_ENDPOINT/SERVICE_PORT at container start).
Grafana configuration. monitoring/grafana/grafana.ini sets http_addr=0.0.0.0, http_port=3000, disables anonymous access and user sign-up, enables the publicDashboards feature toggle, and sets default_home_dashboard_path to point at the pre-built eRPC dashboard JSON. The datasource provisioning file (monitoring/grafana/datasources/prometheus.yml) registers http://localhost:9090 as the default Prometheus datasource. The dashboard provider (monitoring/grafana/dashboards/default.yml) scans /etc/grafana/dashboards every 10 seconds for JSON files and loads them with UI editing allowed.
Dashboard template variables. The Grafana dashboard uses $datasource to select the Prometheus datasource and $finality to filter Vendor Share panels by finality type.
Key alert expressions from monitoring/prometheus/alert.rules:
| Alert | Expression basis | Severity |
|---|---|---|
HighErrorRate | erpc_upstream_request_errors_total rate > 5% for 5m | warning |
SlowRequests | erpc_upstream_request_duration_seconds_budget p95 > 1s for 5m | warning |
HighRateLimiting | erpc_upstream_request_self_rate_limited_total rate > 10% for 5m | warning |
NetworkRateLimiting | erpc_network_request_self_rate_limited_total > 10 req/s for 5m | warning |
HighRequestRate | erpc_upstream_request_total rate > 1000 req/s for 5m | warning |
LowRequestRate | erpc_upstream_request_total rate < 1 req/s for 15m | warning |
The Grafana dashboard (monitoring/grafana/dashboards/erpc.json, 80+ panels) covers: request rates and RPS, latency (P50/P90/P99), failover retries by reason, upstream errors by type, block-head lag, cache hit/miss, consensus operations, selection policy, and rate limiter budgets.
Source code entry points
Dockerfile:L70-L88(opens in a new tab) — final distroless stage: what files land in the imageDockerfile:L24-L34(opens in a new tab) —VERSION/COMMIT_SHAldflags injectioncmd/erpc/main.go:L279-L294(opens in a new tab) — config file search order (12 paths)cmd/erpc/main.go:L72-L74(opens in a new tab) — SIGINT/SIGTERM graceful shutdown viasignal.NotifyContextmonitoring/scripts/entrypoint.sh(opens in a new tab) — sed substitution + dual-process startupdocker-compose.yml:L84-L86(opens in a new tab) — named volumesprometheus_dataandgrafana_dataMakefile:L114(opens in a new tab) —make downpasses--volumes, destroying monitoring stateMakefile:L121-L123(opens in a new tab) —make docker-builddefaults tolinux/amd64Makefile:L127-L129(opens in a new tab) —make docker-run: mounts config, maps ports, invokes/erpc-server --config /erpc.yamlmonitoring/grafana/grafana.ini(opens in a new tab) — Grafana server config (port 3000, provisioning, publicDashboards feature toggle)monitoring/grafana/datasources/prometheus.yml(opens in a new tab) — auto-provisions Prometheus datasource atlocalhost:9090monitoring/grafana/dashboards/default.yml(opens in a new tab) — dashboard file provider scanning/etc/grafana/dashboardsevery 10smonitoring/prometheus/prometheus.yml(opens in a new tab) — local scrape config (self atlocalhost:9090+ eRPC athost.docker.internal:4001)
Related pages
- Kubernetes — Deployment, ConfigMap, Service, PodMonitor, and probe tuning.
- Railway — one-click managed deploy with monitoring included.
- Monitoring & metrics — full metrics reference for dashboards and alerting.
- Healthcheck — probe strategy and evaluation logic.
- Authentication — lock down the exposed RPC port before going public.