Install / Docker
Docker Installation
Versioned images with Compose examples for file mode, PostgreSQL, and CP/DP deployments.
Pull the Image
bash
# Docker Hub (public pulls); pin a published tag
docker pull ferrumedge/ferrum-edge:v0.9.10
# Verify
docker run --rm ferrumedge/ferrum-edge:v0.9.10 version
Image Variants
The default image ships a no-op mock eBPF capture backend, which is what you want for ordinary gateway proxying. Real ambient mesh capture needs one of the Linux-only variants.
| Tag | Use it for | Notes |
|---|---|---|
v0.9.10 |
Gateway proxying on any platform | Default. Mock eBPF capture backend — not real capture. |
v0.9.10-ebpf |
Real ambient / node-waypoint eBPF capture | Linux only; kernel ≥ 5.7 with cgroup v2 + bpffs. Distroless — no /bin/sh, no iptables, so FERRUM_NODE_AGENT_FALLBACK_MODE=iptables crash-loops here. |
v0.9.10-ebpf-tools |
iptables fallback and Ambient host-network UDP lifecycle | Same eBPF build on Debian slim, carrying sh, iptables, ip6tables, and ip. |
bash
# Real eBPF capture (Linux only, distroless)
docker pull ferrumedge/ferrum-edge:v0.9.10-ebpf
# eBPF plus iptables/shell fallback tooling
docker pull ferrumedge/ferrum-edge:v0.9.10-ebpf-tools
Required kernel capabilities differ by kernel version — see node agent security. For Kubernetes, see the Kubernetes install guide.
Quick Start Examples
File Mode (no DB)
bash
docker run -d \
--name ferrum-edge \
-p 8000:8000 \
-v "$(pwd)/ferrum.yaml:/etc/ferrum/config.yaml:ro" \
-e FERRUM_MODE=file \
-e FERRUM_FILE_CONFIG_PATH=/etc/ferrum/config.yaml \
ferrumedge/ferrum-edge:v0.9.10
Database Mode
bash
docker run -d \
--name ferrum-edge \
-p 8000:8000 \
-p 8443:8443 \
-e FERRUM_MODE=database \
-e FERRUM_DB_TYPE=postgres \
-e FERRUM_DB_URL="postgres://ferrum:secret@postgres:5432/ferrum" \
-e FERRUM_ADMIN_JWT_SECRET="please-change-me-to-a-32+character-secret" \
-e FERRUM_ADMIN_BIND_ADDRESS=127.0.0.1 \
ferrumedge/ferrum-edge:v0.9.10
Admin API security: in writable
database/cp modes the gateway
refuses to start a plaintext admin listener on a non-loopback address without a
FERRUM_ADMIN_ALLOWED_CIDRS allowlist. To reach admin from outside the container, set
FERRUM_ADMIN_BIND_ADDRESS=0.0.0.0 and serve it over
TLS (FERRUM_ADMIN_TLS_CERT_PATH/FERRUM_ADMIN_TLS_KEY_PATH, publish 9443)
or restrict callers with FERRUM_ADMIN_ALLOWED_CIDRS and publish port 9000.
Include loopback in the allowlist for local health probes. See
docs/docker.md.
Docker Compose — Database Mode (PostgreSQL)
yaml — docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ferrum
POSTGRES_USER: ferrum
POSTGRES_PASSWORD: ferrum_secret
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ferrum"]
interval: 5s
timeout: 5s
retries: 5
ferrum-edge:
image: ferrumedge/ferrum-edge:v0.9.10
depends_on:
postgres:
condition: service_healthy
ports:
- "8000:8000"
- "8443:8443"
environment:
FERRUM_MODE: database
FERRUM_DB_TYPE: postgres
FERRUM_DB_URL: postgres://ferrum:ferrum_secret@postgres:5432/ferrum
FERRUM_ADMIN_JWT_SECRET: "change-this-to-a-32-plus-character-secret"
FERRUM_ADMIN_BIND_ADDRESS: 127.0.0.1
FERRUM_LOG_LEVEL: info
restart: unless-stopped
healthcheck:
test: ["CMD", "/app/ferrum-edge", "health"]
interval: 10s
timeout: 5s
retries: 3
volumes:
postgres_data:
Docker Compose — File Mode (no database)
yaml — docker-compose.file.yml
services:
ferrum-edge:
image: ferrumedge/ferrum-edge:v0.9.10
ports:
- "8000:8000"
volumes:
- ./ferrum.yaml:/etc/ferrum/config.yaml:ro
environment:
FERRUM_MODE: file
FERRUM_FILE_CONFIG_PATH: /etc/ferrum/config.yaml
FERRUM_LOG_LEVEL: info
restart: unless-stopped
Docker Compose — Control Plane / Data Plane
yaml — docker-compose.cpdp.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ferrum
POSTGRES_USER: ferrum
POSTGRES_PASSWORD: ferrum_secret
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ferrum"]
interval: 5s
timeout: 5s
retries: 5
control-plane:
image: ferrumedge/ferrum-edge:v0.9.10
depends_on:
postgres:
condition: service_healthy
ports:
- "50051:50051" # CP gRPC
environment:
FERRUM_MODE: cp
FERRUM_DB_TYPE: postgres
FERRUM_DB_URL: postgres://ferrum:ferrum_secret@postgres:5432/ferrum
FERRUM_ADMIN_JWT_SECRET: "change-this-to-a-32-plus-character-secret"
FERRUM_CP_GRPC_LISTEN_ADDR: "0.0.0.0:50051"
FERRUM_CP_DP_GRPC_JWT_SECRET: "change-this-cp-dp-secret-min-32-chars"
FERRUM_CP_DP_GRPC_ALLOW_PLAINTEXT: "true" # local dev only — use TLS in production
restart: unless-stopped
data-plane-1:
image: ferrumedge/ferrum-edge:v0.9.10
depends_on:
- control-plane
ports:
- "8000:8000"
- "8443:8443"
environment:
FERRUM_MODE: dp
FERRUM_DP_CP_GRPC_URLS: http://control-plane:50051
FERRUM_CP_DP_GRPC_JWT_SECRET: "change-this-cp-dp-secret-min-32-chars"
FERRUM_CP_DP_GRPC_ALLOW_PLAINTEXT: "true"
restart: unless-stopped
data-plane-2:
image: ferrumedge/ferrum-edge:v0.9.10
depends_on:
- control-plane
ports:
- "8001:8000"
- "8444:8443"
environment:
FERRUM_MODE: dp
FERRUM_DP_CP_GRPC_URLS: http://control-plane:50051
FERRUM_CP_DP_GRPC_JWT_SECRET: "change-this-cp-dp-secret-min-32-chars"
FERRUM_CP_DP_GRPC_ALLOW_PLAINTEXT: "true"
restart: unless-stopped
volumes:
postgres_data:
Docker Environment Variables
| Variable | Description | Required |
|---|---|---|
FERRUM_MODE | file | database | cp | dp | mesh | injector | node_agent | migrate | Yes |
FERRUM_FILE_CONFIG_PATH | Config file path (file mode) | file mode |
FERRUM_DB_TYPE | postgres | mysql | sqlite | mongodb | db/cp modes |
FERRUM_DB_URL | Database connection string | db/cp modes |
FERRUM_ADMIN_JWT_SECRET | JWT secret for Admin API (min 32 chars) | db/cp modes |
FERRUM_DP_CP_GRPC_URLS | Comma-separated Control Plane URLs (DP mode, failover order) | dp mode |
FERRUM_CP_DP_GRPC_JWT_SECRET | Shared secret for CP↔DP sync (min 32 chars) | cp/dp modes |
FERRUM_CP_GRPC_LISTEN_ADDR | gRPC listen address (CP mode) | No (def: 50051) |
FERRUM_LOG_LEVEL | error | warn | info | debug | trace | No (def: warn) |
Port reference:
8000 HTTP proxy •
8443 HTTPS proxy •
9000 Admin HTTP •
9443 Admin HTTPS •
50051 CP gRPC