Run an OpenTelemetry Collector with Docker Compose
Start a Collector from a mounted config file, send it test data, and see it in the debug exporter.
This guide runs a Collector locally from a YAML file, sends it a trace and a metric, and prints what it received. Nothing is configured in a UI: the Collector has none, and everything lives in one file. You need Docker with the Compose plugin and curl.
Choose an image
The project publishes several Collector distributions. The core image (otel/opentelemetry-collector) has a small set of components. The contrib image (otel/opentelemetry-collector-contrib) includes the large community set, and the Kubernetes-focused image (otel/opentelemetry-collector-k8s) includes what is needed to monitor clusters. Many components used later in this series, such as filelog or tail_sampling, are not in the core image. For learning, use contrib. Pin a specific version in real use, as in these examples, and replace the version with the release you want.
Project layout
otel-demo/
├── compose.yaml
└── otel-collector-config.yaml
The Collector config
otel-collector-config.yaml:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
debug:
verbosity: detailed
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
Read it top to bottom:
receiversdefine how data comes in.otlplistens for gRPC on 4317 and HTTP on 4318.processorschange data in flight.batchgroups items to make exports efficient.exporterssend data out.debugprints it to the Collector’s own log, which is what you want while learning.extensionsadd capabilities beside the pipelines.health_checkserves a health endpoint.serviceswitches things on. Defining a component does nothing until it is listed here, and each signal has its own pipeline.
The endpoints use 0.0.0.0 so the container is reachable from outside. The Collector’s default is localhost, which is safer when all senders are local, so bind to 0.0.0.0 only where you need to.
The compose file
compose.yaml:
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.163.0 # pin a version you have tested
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otelcol/config.yaml:ro
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "13133:13133" # health_check
- "8888:8888" # the Collector's own metrics
The config path /etc/otelcol/config.yaml is where the official images look by default. Other ports you may meet are 55679 for the zpages extension and 8889 for a Prometheus exporter.
Validate before starting
The Collector can check a config without running it:
docker run --rm -v $(pwd)/otel-collector-config.yaml:/etc/otelcol/config.yaml:ro \
otel/opentelemetry-collector-contrib:0.163.0 validate --config=/etc/otelcol/config.yaml
A clean exit means the file parses and every referenced component exists in this distribution. Run this in CI too. A common failure is using a component that isn’t in the image you chose.
Start it and check health
docker compose up -d
curl -s http://localhost:13133
docker compose logs otel-collector | tail -n 20
The health endpoint returns a small JSON status, and the log should show the Collector starting its pipelines and listening on 4317 and 4318.
Send test telemetry
Use OTLP over HTTP with plain curl. Send one span:
curl -s -X POST http://localhost:4318/v1/traces \
-H 'Content-Type: application/json' \
-d '{
"resourceSpans": [{
"resource": { "attributes": [{ "key": "service.name", "value": { "stringValue": "demo" } }] },
"scopeSpans": [{
"spans": [{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "eee19b7ec3c1b174",
"name": "hello",
"kind": 1,
"startTimeUnixNano": "1700000000000000000",
"endTimeUnixNano": "1700000001000000000"
}]
}]
}]
}'
An empty JSON object in the response means it was accepted. Now look at the Collector’s log:
docker compose logs otel-collector | grep -A8 'hello'
The debug exporter prints the span, its resource (service.name: demo), and its timing. You have a working pipeline.
Change the config and reload
The Collector reads its file at startup. After editing it:
docker compose restart otel-collector
Keep this folder in Git. Later guides replace debug with real exporters, and add processors and extensions to the same file.
Clean up
docker compose down Popular Guides
-
OpenTelemetry Explained: SDKs, OTLP, and the Collector Understand the moving parts and the three places configuration lives before you write any YAML. -
The Collector Configuration Model: Components, Pipelines, and Providers Learn the rules of the Collector's YAML: naming, pipelines, connectors, merging files, and environment variables. -
Configure SDKs with OTEL_* Environment Variables Set the service name, exporters, sampling, and limits for any language using standard variables. -
Instrument Apps Without Code Changes: Java, Python, and Node.js Attach OpenTelemetry agents with environment variables and command-line flags only. -
Make the Collector Production-Ready: Memory Limits, Batching, and Retries Configure processors, queues, and persistent storage so telemetry survives overload and outages. -
Deploy the Collector on Kubernetes with Helm Install the official chart, use presets for logs and metrics, and override the config through values files. -
Troubleshoot the Collector and SDKs A systematic checklist for when telemetry doesn't arrive: debug exporter, health checks, internal metrics, and common mistakes.