Deployment
Docker
AI agents: fetch https://docs.erpc.cloud/deployment/docker.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /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:latest

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: 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 (default server.httpPortV4)
  • 4001 — Prometheus metrics (/metrics)
  • 6060 — pprof (only functional in the erpc-server-pprof binary)

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 monitoring

make 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] or erpc start — start the server
  • erpc validate [--format json|md] — parse config and run a validation report; exits non-zero on errors
  • erpc 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:

VariableEffectSource
LOG_LEVELOverrides cfg.LogLevel; parsed as zerolog level (trace/debug/info/warn/error)cmd/erpc/main.go:L354-363
LOG_WRITERIf "console" → human-readable zerolog output with 04:05.000ms timestamps; default is JSONcmd/erpc/main.go:L53-57
GOMEMLIMITGo 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
GOGCGo GC target percentage. Lower values (25–50) keep heap smaller at slightly higher CPU cost. Default is Go runtime default (100).n/a
SERVICE_ENDPOINTMonitoring 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_PORTSame 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:latest

Set 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 up

Open 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:latest

Access 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.yaml

5. 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.x

Request/response behavior

  • Graceful shutdown: SIGINT/SIGTERM triggers signal.NotifyContext; the HTTP server drains in-flight requests, returns 503 on healthcheck, then calls Shutdown. [cmd/erpc/main.go:L72-74]
  • The /healthcheck endpoint returns 200 while healthy and 503 during drain — wire it to Docker's HEALTHCHECK instruction or a load-balancer probe.
  • VERSION and COMMIT_SHA build args are injected via -X ldflags into common.ErpcVersion and common.ErpcCommitSha at build time and surfaced in startup logs and the /healthcheck response body. [Dockerfile:L24-34]

Best practices

  • Always set GOMEMLIMIT to 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 :latest for 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 down passes --volumes and 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.yaml doesn'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 separate server.grpcPortV4, add -p <grpc_port>:<grpc_port> explicitly.

Edge cases & gotchas

  1. No shell in distroless: Cannot exec with a shell; debug tools require privileged sidecars.
  2. make down destroys volumes: Makefile:L114 passes --volumes — this drops prometheus_data and grafana_data. Use docker compose down (without --volumes) to preserve monitoring state.
  3. GOMEMLIMIT not set in any manifest: With a 3 Gi container limit and no GOMEMLIMIT, Go's GC may allow the heap to approach 3 Gi before collecting, triggering container OOM. Set GOMEMLIMIT=2700MiB (90% of limit).
  4. pprof binary always present but never used by default: /erpc-server-pprof exists in every image but the default CMD runs /erpc-server. To use profiling endpoints, override CMD to /erpc-server-pprof and expose port 6060.
  5. SERVICE_ENDPOINT sed-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.
  6. 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.
  7. gRPC port not exposed by default: The image exposes 4000, 4001, 6060. If you configure a separate server.grpcPortV4, add -p <grpc_port>:<grpc_port> explicitly.
  8. prometheus.yaml at repo root is NOT the monitoring container's config: It is the production Prometheus Operator config. The monitoring container uses monitoring/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:

AlertExpression basisSeverity
HighErrorRateerpc_upstream_request_errors_total rate > 5% for 5mwarning
SlowRequestserpc_upstream_request_duration_seconds_budget p95 > 1s for 5mwarning
HighRateLimitingerpc_upstream_request_self_rate_limited_total rate > 10% for 5mwarning
NetworkRateLimitingerpc_network_request_self_rate_limited_total > 10 req/s for 5mwarning
HighRequestRateerpc_upstream_request_total rate > 1000 req/s for 5mwarning
LowRequestRateerpc_upstream_request_total rate < 1 req/s for 15mwarning

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

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.