Introduction
Jaeger has become the go-to tool for distributed tracing in microservices architectures. In 2026, observability requirements demand more than a basic deployment: you need intelligent sampling, multi-service correlation, and seamless integration with CI/CD pipelines. This tutorial guides you step by step through setting up a production-ready solution. You'll learn how to instrument Node.js/TypeScript applications, configure high-performance collectors, and optimize trace storage costs. Each step includes concrete, working examples you can copy directly.
Prerequisites
- Docker and Docker Compose v2.20+
- Solid knowledge of TypeScript and OpenTelemetry
- Kubernetes cluster (optional for production)
- Node.js 20+ and npm
Production Deployment with Docker Compose
version: '3.8'
services:
jaeger-collector:
image: jaegertracing/jaeger-collector:1.55
environment:
- COLLECTOR_OTLP_ENABLED=true
- SPAN_STORAGE_TYPE=elasticsearch
ports:
- "14268:14268"
- "4317:4317"
- "4318:4318"
jaeger-query:
image: jaegertracing/jaeger-query:1.55
environment:
- SPAN_STORAGE_TYPE=elasticsearch
ports:
- "16686:16686"This file deploys a production-ready Jaeger collector and query service. Port 4317 handles OTLP gRPC and 4318 handles OTLP HTTP for modern ingestion.
Verifying the Deployment
Run docker compose up -d. Access the Jaeger UI at http://localhost:16686. Verify that your services appear in the service selector.
Advanced OpenTelemetry Instrumentation
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'api-users',
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();This module configures the OpenTelemetry SDK with automatic framework detection. It sends traces to the Jaeger collector via OTLP by default.
Configuring Probabilistic Sampling
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/core';
const sampler = new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(0.1), // 10% of root traces
});
// Pass this into your NodeSDK optionsA 10% sampling rate drastically reduces data volume while maintaining a representative view of traffic. Use ParentBasedSampler to respect parent service sampling decisions.
Adding Custom Tags and Spans
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('user-service');
export async function getUser(id: string) {
const span = tracer.startSpan('getUser');
span.setAttribute('user.id', id);
span.setAttribute('user.tier', 'premium');
try {
return await db.findUser(id);
} finally {
span.end();
}
}Custom attributes let you filter and analyze traces in the Jaeger UI. Always end spans in a finally block to prevent leaks.
Configuring the Collector with Elasticsearch
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
probabilistic_sampler:
hash_seed: 22
sampling_percentage: 15
exporters:
elasticsearch:
endpoints: ["http://elasticsearch:9200"]
service:
pipelines:
traces:
receivers: [otlp]
processors: [probabilistic_sampler]
exporters: [elasticsearch]This advanced configuration enables sampling at the collector level and stores traces in Elasticsearch for long-term retention and complex queries.
Best Practices
- Always propagate trace context between services using W3C TraceContext headers
- Use consistent, hierarchical span names
- Set span size limits to avoid oversized traces
- Monitor collector latency and queue sizes
- Version your attribute schemas to simplify historical analysis
Common Mistakes to Avoid
- Forgetting to enable OTLP on the collector (ports 4317/4318)
- Setting an overly low sampling rate in test environments
- Failing to instrument databases and message queues
- Ignoring storage backend connection errors
Going Further
Explore our comprehensive courses on observability and distributed tracing: https://learni-group.com/formations. You'll learn Jaeger integration with Grafana Tempo and trace analysis with OpenSearch.