ποΈ Architecture β How the Stack Connects
Understanding the architecture before installing prevents confusion later. This chapter explains every connection, every port, and every data flow in plain language.
πΊοΈ Full Stack Architectureβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β YOUR SERVER / VM β
β β
β βββββββββββββββ βββββββββββββββ β
β β healthtune β β trackx β β Your Node.js apps β
β β API (PM2) β β API (PM2) β managed by PM2 β
β ββββββββ¬βββββββ ββββββββ¬βββββββ β
β β OTEL SDK β OTEL SDK β
β ββββββββββ¬βββββββββββ β
β β gRPC :4317 β
β βΌ β
β ββββββββββββββββββ β
β β OTEL Collector β receives traces from apps β
β ββββββββββ¬ββββββββ β
β β forwards traces β
β βΌ β
β ββββββββββββββββββ β
β β Tempo β :3200 stores traces β
β ββββββββββ¬ββββββββ β
β β β
β βββββββββββββββββΌββββββββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β ββββββββ ββββββββββββββ ββββββββββββββββββββ β
β βProm. β β Loki β β Grafana β :3000 β
β β:9090 β β :3100 β β (Dashboards) β Browser β
β ββββ¬ββββ βββββββ¬βββββββ ββββββββββββββββββββ β
β β scrapes β receives logs β
β βΌ βΌ β
β ββββββββ ββββββββββββββ β
β βNode β β Promtail β reads PM2 log files β
β βExpo. β ββββββββββββββ β
β β:9100 β β
β ββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ Component Rolesβ
Prometheus (Metrics)β
- Pull model β Prometheus goes out and fetches metrics from targets
- Scrapes
/metricsendpoint on your apps and Node Exporter - Stores data in its own time-series database (TSDB) on disk
- Query language: PromQL
Node Exporter (Host Metrics)β
- A small agent that exposes system metrics at
http://localhost:9100/metrics - Prometheus scrapes this to get CPU, RAM, disk, network stats
- You never interact with it directly β just let it run
Loki (Logs)β
- Push model β Promtail pushes logs to Loki
- Does NOT index full log content (much cheaper than Elasticsearch)
- Indexes only labels (
app,level,host, etc.) - Query language: LogQL
Promtail (Log Shipper)β
- Reads log files from disk (PM2 logs, Docker logs, syslog)
- Attaches labels to each log line and pushes to Loki at
http://loki:3100 - Similar role to Filebeat in the ELK stack
Tempo (Traces)β
- Push model β apps send traces via OTEL protocol
- Stores traces efficiently on disk (designed for high volume)
- Can receive traces via OTLP gRPC (4317) or HTTP (4318)
OTEL Collector (Optional but Recommended)β
- Acts as a middleman between your apps and Tempo
- Lets you add processors (sampling, filtering, enriching)
- Without it: apps send directly to Tempo on port 4317
- With it: apps β Collector β Tempo (more control)
Grafana (Visualization)β
- Connects to all data sources: Prometheus, Loki, Tempo
- You build dashboards with panels (graphs, tables, logs, trace views)
- The only tool you open in a browser
π Data Flow by Typeβ
Metrics Flowβ
App exposes /metrics endpoint
β (Prometheus scrapes every 15s)
Prometheus stores in TSDB
β (Grafana queries via PromQL)
Grafana dashboard panel
Logs Flowβ
PM2 writes logs to /home/user/.pm2/logs/*.log
β (Promtail tails the files)
Promtail adds labels and pushes
β (HTTP push to Loki port 3100)
Loki indexes labels + stores log lines
β (Grafana queries via LogQL)
Grafana logs panel
Traces Flowβ
User HTTP request hits your app
β (OTEL SDK creates a trace)
tracing.js sends to port 4317
β (OTEL Collector β Tempo)
Tempo stores trace
β (Grafana Tempo data source)
Grafana trace visualization
π Complete Port Mapβ
| Port | Container | Protocol | Direction | Used By |
|---|---|---|---|---|
3000 | Grafana | HTTP | Inbound | Your browser |
9090 | Prometheus | HTTP | Inbound | Browser / Grafana |
3100 | Loki | HTTP | Inbound | Promtail push / Grafana |
3200 | Tempo | HTTP | Inbound | Grafana queries |
4317 | OTEL Collector / Tempo | gRPC | Inbound | Your Node.js apps |
4318 | OTEL Collector | HTTP | Inbound | Apps (HTTP alternative) |
9100 | Node Exporter | HTTP | Inbound | Prometheus scrape |
9080 | Promtail | HTTP | Internal | Metrics about Promtail |
Minimum ports to expose externally (firewall rules):
3000β open Grafana in your browser4317β apps send traces (if apps are on a different server)
All other ports can stay internal (container-to-container via Docker network).
π³ Docker Networking Ruleβ
When containers talk to each other inside Docker Compose, use the service name β not localhost:
| From | To | Use This URL |
|---|---|---|
| Grafana β Prometheus | http://prometheus:9090 | |
| Promtail β Loki | http://loki:3100 | |
| OTEL Collector β Tempo | http://tempo:4317 | |
| Prometheus β Node Exporter | http://node-exporter:9100 |
But from your PM2 apps on the host machine, use localhost:
// In tracing.js (app runs on host, not in Docker)
url: 'http://localhost:4317'
πΎ Data Storage (What Lives Where)β
All persistent data uses Docker named volumes:
| Tool | What It Stores | Volume |
|---|---|---|
| Prometheus | Time-series metrics | prometheus_data |
| Loki | Log chunks + index | loki_data |
| Tempo | Trace blocks | tempo_data |
| Grafana | Dashboards, users, settings | grafana_data |
β οΈ
docker compose down -vpermanently deletes all volumes. Use only for a clean wipe. Usedocker compose down(no-v) to stop containers while keeping all data.
π§± Deployment Optionsβ
| Option | Setup | Best For |
|---|---|---|
| Docker Compose on one VM | This guide | Small-medium teams, simplest |
| Separate VMs per tool | Manual config | Scaling independently |
| Kubernetes with Helm | kube-prometheus-stack chart | K8s environments |
| Grafana Cloud (managed) | Zero ops | Teams avoiding self-hosting |
This documentation covers Docker Compose on one server.