Collector topology: one root, many branches
Loggerhead runs one OTLP receiver, the root, inside the app. Every other collector is a branch that forwards into it. This page shows how to connect a branch and how to keep records attributed.
Why one root
Section titled “Why one root”The backend is the only process that writes to DuckDB. The root receiver lives in that process and binds to localhost. A branch never touches the database. It speaks plain OTLP to the root, so a standard upstream OpenTelemetry Collector, an SDK exporter, or otel-cli can all be a branch.
Docker host ─┐ Kubernetes ──┼── OTLP ──▶ Loggerhead ROOT receiver ──▶ DuckDB (one writer) Other hosts ─┘ 127.0.0.1:<effective port>Two rules for every branch
Section titled “Two rules for every branch”Use the effective port
Section titled “Use the effective port”Read the port from the Collectors page, or from GET /api/health, and put that port in every branch config. Do not hard-code 14317. See Ports and the effective port.
Stamp the collector ID
Section titled “Stamp the collector ID”Stamp loggerhead.collector.id with the resource processor. The root attributes each record to its branch, and the Collectors page shows each branch with its own health and throughput.
Run a Docker branch
Section titled “Run a Docker branch”Save this file as branch.yaml and start the upstream collector image with it. The container reaches the root on the host through host.docker.internal.
receivers: otlp: protocols: grpc: { endpoint: 0.0.0.0:4317 }
processors: resource: attributes: - key: loggerhead.collector.id value: docker-mac action: upsert
exporters: otlp: endpoint: host.docker.internal:14317 # the effective port tls: { insecure: true }
service: pipelines: logs: { receivers: [otlp], processors: [resource], exporters: [otlp] } traces: { receivers: [otlp], processors: [resource], exporters: [otlp] } metrics: { receivers: [otlp], processors: [resource], exporters: [otlp] }Address table
Section titled “Address table”Where the root is reachable from, by the kind of branch that sends to it.
| Branch runs on | Address to use | Notes |
|---|---|---|
| The same Mac | 127.0.0.1:<port> |
Loopback. Fastest path. |
| A container on this Mac | host.docker.internal:<port> |
Docker Desktop and OrbStack proxy it. |
| A Kubernetes cluster | A tunnel to 127.0.0.1:<port> |
The root binds to loopback only. |
| Another machine | Not supported yet | Run a branch on that machine and tunnel. |
Known limits
Section titled “Known limits”- The root binds to loopback only. A branch on another machine needs a tunnel.
- One root per Mac. Two apps cannot share a database.