OpenTelemetry OpenTelemetry

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.

10 min Beginner

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:

  • receivers define how data comes in. otlp listens for gRPC on 4317 and HTTP on 4318.
  • processors change data in flight. batch groups items to make exports efficient.
  • exporters send data out. debug prints it to the Collector’s own log, which is what you want while learning.
  • extensions add capabilities beside the pipelines. health_check serves a health endpoint.
  • service switches 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