A decoupled, production-grade observability stack containing OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana (LGTM + OTel).
┌──────────────────────────────────────────┐
│ Application Stack │
│ (FastAPI Backend / React Vite Frontend) │
└────────────────────┬─────────────────────┘
│
OTLP Traces / Metrics / Logs (HTTP 4328 / gRPC 4327)
▼
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Observability Compose Stack │
│ │
│ ┌────────────────────────────────────┐ │
│ │ OpenTelemetry Collector Contrib │ │
│ │ (receivers: OTLP gRPC/HTTP) │ │
│ └───────┬────────────┬───────────┬───┘ │
│ │ │ │ │
│ Traces │ Metrics │ │ Logs │
│ (OTLP) │ (Scrape) │ │ (Loki push) │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Tempo │ │Prometheus│ │ Loki │ │
│ │ (Traces) │ │(Metrics) │ │ (Logs) │ │
│ └─────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Grafana │ │
│ │ (Dashboards) │ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────────────────────────────────┘
personal-lifecycle-observability). Application reloads, restarts, or crashes never disrupt telemetry collection or wipe dashboard history.TraceID), and metrics to traces (Tempo $\to$ Prometheus).| Component | Service Name | Container Port | Host Port (Configurable) | Description |
|---|---|---|---|---|
| OpenTelemetry Collector | otel-collector |
4317 |
4327 (${OTEL_GRPC_PORT:-4327}) |
OTLP gRPC Receiver |
| OpenTelemetry Collector | otel-collector |
4318 |
4328 (${OTEL_HTTP_PORT:-4328}) |
OTLP HTTP Receiver |
| OpenTelemetry Collector | otel-collector |
8889 |
8899 (${OTEL_METRICS_PORT:-8899}) |
Collector Prometheus Metrics Exporter |
| OpenTelemetry Collector | otel-collector |
8888 |
Internal | Collector Internal Health & Pipeline Telemetry |
| Prometheus | prometheus |
9090 |
9091 (${PROMETHEUS_PORT:-9091}) |
Metrics Storage & Query Engine |
| Loki | loki |
3100 |
3101 (${LOKI_PORT:-3101}) |
Log Aggregation Engine |
| Tempo | tempo |
3200 |
3201 (${TEMPO_PORT:-3201}) |
Distributed Tracing Storage |
| Grafana | grafana |
3000 |
3004 (${GRAFANA_PORT:-3004}) |
Unified Dashboard & Explorer UI |
Default Grafana Credentials:
admin/admin
observability/
├── docker-compose.yaml # Standalone Compose project definition
├── .env.example # Configurable port & container name overrides
├── otel-collector-config.yaml # Collector pipelines routing traces, metrics & logs
├── prometheus.yml # Prometheus scrape targets
├── loki-config.yaml # Loki filesystem & TSDB config
├── tempo-config.yaml # Tempo local trace storage & OTLP ingestion
└── grafana/
├── dashboards/
│ └── lifecycle-tracker-overview.json # Golden Signals overview dashboard
└── provisioning/
├── dashboards/
│ └── dashboards.yaml # Auto-provisioning provider for dashboards
└── datasources/
└── datasources.yaml # Correlated Prometheus, Loki & Tempo datasources
# Using root Makefile
make obs-up
# Or directly with Docker Compose
docker compose -f observability/docker-compose.yaml up -d
make obs-ps
# or
docker compose -f observability/docker-compose.yaml ps
make obs-logs
# Or specific component:
docker compose -f observability/docker-compose.yaml logs -f otel-collector
make obs-down
# or
docker compose -f observability/docker-compose.yaml down -v
To verify that the OpenTelemetry Collector and pipelines are actively processing telemetry:
# Send test log payload to Collector via OTLP HTTP
NOW_NANO=$(date +%s000000000)
curl -i -X POST http://localhost:4328/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "verification-test"}},
{"key": "deployment.environment", "value": {"stringValue": "development"}},
{"key": "service.version", "value": {"stringValue": "dev-latest"}}
]
},
"scopeLogs": [{
"scope": {"name": "test-logger"},
"logRecords": [{
"timeUnixNano": "'"${NOW_NANO}"'",
"severityNumber": 9,
"severityText": "INFO",
"body": {"stringValue": "OpenTelemetry Collector verification test log"}
}]
}]
}]
}'
Check Collector logs to confirm receipt:
docker compose -f observability/docker-compose.yaml logs otel-collector
Configure application environment variables:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4328
OTEL_SERVICE_NAME=api-backend
OTEL_RESOURCE_ATTRIBUTES=service.name=api-backend,deployment.environment=development,service.version=dev-latest
Attach the application services to the personal-lifecycle-observability-net network:
services:
backend:
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://personal-lifecycle-otel-collector:4318
networks:
- default
- observability-net
networks:
default:
observability-net:
external: true
name: personal-lifecycle-observability-net