Skip to content
Back to skills

Jaeger

BSecurity

Collects, stores and visualizes distributed traces with Jaeger v2 so you can follow a request across microservices and find latency bottlenecks. Use when a user needs to deploy Jaeger, send OpenTelemetry traces to it, pick a storage backend such as Elasticsearch or OpenSearch, configure sampling, or query traces through the API.

  • 142 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 29, 2026
developmentjavascriptpythongojavabashnodeflaskdockergitapi

Works with

  • terminal
  • cli
  • api

Security analysis

B88/100
  • mediumUses curl or wget to download content
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill jaeger --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jaeger?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Jaeger
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-jaeger/badge)](https://www.skillsdirectory.com/skills/terminalskills-jaeger)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: jaeger
description: >-
  Collects, stores and visualizes distributed traces with Jaeger v2 so you can follow a request across microservices and find latency bottlenecks. Use when a user needs to deploy Jaeger, send OpenTelemetry traces to it, pick a storage backend such as Elasticsearch or OpenSearch, configure sampling, or query traces through the API.
license: Apache-2.0
compatibility: "Jaeger v2 (2.21 checked), Docker; OpenTelemetry SDKs. Jaeger v1 is archived"
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: devops
  tags: ["jaeger", "tracing", "distributed-tracing", "opentelemetry", "observability"]
  repository: https://github.com/jaegertracing/jaeger
---

# Jaeger

## Overview

Jaeger is a CNCF graduated tracing backend: it receives spans, stores them and serves a UI and query API. Jaeger v2 (current line, 2.21.0 in September 2026) is built on the OpenTelemetry Collector: one binary, one image (`jaegertracing/jaeger`), configured only through a YAML file, and it receives OTLP natively. The v1 images (`all-in-one`, `jaeger-collector`, `jaeger-query`) and their `SPAN_STORAGE_TYPE` / `COLLECTOR_OTLP_ENABLED` environment variables are gone from the current docs (v1.76 is the archived last line), and the old Jaeger client libraries are deprecated: instrument with OpenTelemetry SDKs. Docs: https://www.jaegertracing.io/docs/latest/.

## Instructions

### Task A: Deploy

All-in-one with in-memory storage (data is lost on restart), good for a laptop:

```bash
docker run --rm --name jaeger -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 5778:5778 -p 9411:9411 \
  cr.jaegertracing.io/jaegertracing/jaeger:2.21.0
```

UI at http://localhost:16686, OTLP gRPC on 4317, OTLP HTTP on 4318, remote sampling on 5778. Pin a version tag instead of `latest`. Any other role or storage needs `--config /path/config.yaml` mounted into the container. The config uses the OpenTelemetry Collector layout: `receivers`, `processors`, `exporters`, `service.pipelines`, plus Jaeger extensions `jaeger_storage` (backends), `jaeger_query` (UI and API) and `remote_sampling`. Values can use `${env:NAME:-default}`; `--set receivers.otlp.protocols.grpc.endpoint=0.0.0.0:4317` overrides one key. Inside a container, receivers must listen on `0.0.0.0`.

Persistent single-node setup with Badger (development or small installs):

```yaml
# config.yaml
service:
  extensions: [jaeger_storage, jaeger_query, remote_sampling]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger_storage_exporter]
extensions:
  jaeger_query:
    storage:
      traces: main_store
  jaeger_storage:
    backends:
      main_store:
        badger:
          directories: { keys: /badger/key, values: /badger/data }
          ephemeral: false
          ttl: { spans: 168h }
  remote_sampling:
    file: { path: /etc/jaeger/sampling.json, reload_interval: 30s }
    http: { endpoint: 0.0.0.0:5778 }
receivers:
  otlp:
    protocols:
      grpc: { endpoint: 0.0.0.0:4317 }
      http: { endpoint: 0.0.0.0:4318 }
processors:
  batch:
exporters:
  jaeger_storage_exporter:
    trace_storage: main_store
```

```yaml
# docker-compose.yml
services:
  jaeger:
    image: jaegertracing/jaeger:2.21.0
    command: ["--config", "/etc/jaeger/config.yaml"]
    ports: ["16686:16686", "4317:4317", "4318:4318", "5778:5778"]
    volumes:
      - ./config.yaml:/etc/jaeger/config.yaml:ro
      - ./sampling.json:/etc/jaeger/sampling.json:ro
      - jaeger-data:/badger
volumes:
  jaeger-data:
```

The image runs as uid 10001: a fresh named volume is owned by root and Jaeger fails with `mkdir /badger/key: permission denied`. Fix once with `docker run --rm -v <project>_jaeger-data:/badger alpine:3 chown -R 10001 /badger`.

Production storage: Cassandra, Elasticsearch and OpenSearch are the supported distributed backends (the Jaeger team recommends OpenSearch over Cassandra at scale); ClickHouse is experimental behind a feature gate; Kafka is for buffering. Swap the backend block for Elasticsearch:

```yaml
  jaeger_storage:
    backends:
      main_store:
        elasticsearch:
          server_urls: ["http://elasticsearch:9200"]
          indices:
            index_prefix: "jaeger-main"
            spans: { date_layout: "2006-01-02", rollover_frequency: "day", shards: 3, replicas: 1 }
```

A second backend under `jaeger_query.storage.traces_archive` gives an archive store for traces saved manually from the UI. Collector and query are stateless and can be scaled out; for separate roles reuse one config per role.

### Task B: Instrument with OpenTelemetry (Python)

```bash
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc opentelemetry-instrumentation-flask opentelemetry-instrumentation-requests
```

```python
# tracing.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

def init_tracing(service_name: str):
    resource = Resource.create({
        "service.name": service_name,
        "service.version": "1.2.0",
        "deployment.environment.name": "production",
    })
    provider = TracerProvider(resource=resource)
    provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://jaeger:4317", insecure=True)))
    trace.set_tracer_provider(provider)
    RequestsInstrumentor().instrument()
    return trace.get_tracer(service_name)

# after creating the Flask app: FlaskInstrumentor().instrument_app(app)
```

Add custom spans with `with tracer.start_as_current_span("save_order") as span: span.set_attribute("order.id", order_id)`. Without code changes, `opentelemetry-instrument python app.py` with `OTEL_SERVICE_NAME=order-service OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317` also works.

### Task C: Instrument with OpenTelemetry (Node.js)

```bash
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/resources @opentelemetry/semantic-conventions
```

```javascript
// tracing.js: load first, e.g. node --require ./tracing.js server.js
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { resourceFromAttributes } = require('@opentelemetry/resources')
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions')

const sdk = new NodeSDK({
  resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'api-gateway', [ATTR_SERVICE_VERSION]: '3.1.0' }),
  traceExporter: new OTLPTraceExporter({ url: 'http://jaeger:4317' }),
  instrumentations: [getNodeAutoInstrumentations({ '@opentelemetry/instrumentation-fs': { enabled: false } })],
})
sdk.start()
process.on('SIGTERM', () => sdk.shutdown())
```

In OpenTelemetry JS 2.x the `Resource` class is gone; `new Resource(...)` throws, use `resourceFromAttributes`.

### Task D: Sampling

Jaeger's `remote_sampling` extension serves per-service strategies from a file (reloaded on change). With no file, every service gets probabilistic 0.001 (0.1%). `ratelimiting` works per service but not per operation.

```json
{
  "service_strategies": [
    { "service": "payment-service", "type": "probabilistic", "param": 1.0 },
    { "service": "api-gateway", "type": "ratelimiting", "param": 5 }
  ],
  "default_strategy": {
    "type": "probabilistic",
    "param": 0.1,
    "operation_strategies": [{ "operation": "/health", "type": "probabilistic", "param": 0.001 }]
  }
}
```

Check what an SDK would receive: `curl -s "http://localhost:5778/api/sampling?service=payment-service"`. SDKs only follow these strategies when configured with a Jaeger remote sampler (for example `OTEL_TRACES_SAMPLER=jaeger_remote` with `OTEL_TRACES_SAMPLER_ARG=endpoint=http://jaeger:5778,pollingIntervalMs=60000,initialSamplingRate=0.25` in SDKs that support it); otherwise set head sampling in the SDK. Tail-based sampling is available through a tail sampling processor in the pipeline, at the cost of memory in Jaeger. An adaptive mode computes rates from traffic.

### Task E: Query traces via the API

The stable HTTP API is `/api/v3` (OTLP JSON); gRPC `jaeger.api_v3.QueryService` is on 16685. The `/api/*` JSON that the UI uses is internal and may change.

```bash
curl -s http://localhost:16686/api/v3/services
curl -s "http://localhost:16686/api/v3/operations?service=order-service"
# traces slower than 500 ms in the last hour; the response is OTLP resourceSpans
curl -s "http://localhost:16686/api/v3/traces?query.service_name=order-service&query.start_time_min=$(date -u -d '-1 hour' +%FT%TZ)&query.start_time_max=$(date -u +%FT%TZ)&query.duration_min=500ms" \
  | jq -c '.result.resourceSpans[].scopeSpans[].spans[] | {traceId, name}'
# one trace by its 32-hex id
curl -s http://localhost:16686/api/v3/traces/5b8efff798038103d269b633813fc60c
```

`query.attributes` takes URL-encoded JSON such as `{"http.status_code":"500"}`; `query.start_time_min` and `query.start_time_max` are required, and an empty result is HTTP 404 with `No traces found`.

## Examples

### Example 1: Try tracing locally in two minutes

**User request:** "Run Jaeger on my laptop and send one test span so I can see the UI"

```bash
docker run -d --rm --name jaeger -p 16686:16686 -p 4318:4318 cr.jaegertracing.io/jaegertracing/jaeger:2.21.0
curl -s -X POST http://localhost:4318/v1/traces -H 'Content-Type: application/json' -d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"order-service"}}]},"scopeSpans":[{"spans":[{"traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b174","name":"POST /api/orders","kind":2,"startTimeUnixNano":"1791028939000000000","endTimeUnixNano":"1791028940000000000"}]}]}]}'
```

**Result:** the curl prints `{"partialSuccess":{}}`; http://localhost:16686 lists `order-service` and the 1-second `POST /api/orders` trace (pick a recent timestamp, or widen the UI time range). `docker stop jaeger` removes the container and its data.

### Example 2: Find slow checkouts in a Flask service

**User request:** "Trace my Flask order service and show me which step makes /api/orders slow"

Add Task B's `init_tracing("order-service")`, wrap `validate_order`, `save_to_database` and `notify_payment_service` in `start_as_current_span`, call the endpoint, then search `order-service` with Min Duration `500ms` in the UI or use the Task E query.

**Result:** each trace shows one `POST /api/orders` root span with three child spans; the widest bar names the slow step (for example `notify_payment_service` at 480 ms of 520 ms), with `order.id` attributes to search by.

## Guidelines

- Do not copy v1 compose files: `jaegertracing/all-in-one`, `jaeger-collector` and `jaeger-query` images and `SPAN_STORAGE_TYPE` are v1. v2 reads no storage env vars; use the YAML config.
- Memory storage and Badger are for development; use Elasticsearch/OpenSearch or Cassandra in production and set a retention (7 to 14 days is typical).
- Ports 4317/4318 (OTLP) and 16686 have no authentication by default: bind to localhost or put them behind a proxy or private network.
- Always sample payment and auth paths at 100% and health checks near zero; 0.1% default means a quiet service may show no traces.
- Legacy ports (14250 gRPC, 14268 Thrift HTTP, 6831/6832 UDP) exist only for old Jaeger clients; new code uses OTLP.
- Traces exported through a collector in front of Jaeger work the same way: point the collector's OTLP exporter at `jaeger:4317`.

Files in this skill

  • SKILL.md6.7 KB
  • _scores.json1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…