Overview
Prometheus collects metrics by scraping HTTP endpoints, and Grafana turns that data into dashboards and alerts. Together they form the most common open-source monitoring stack. This tutorial walks through installing both, instrumenting an application, and building your first dashboard.
How Prometheus Works
| Component | Role |
|---|---|
| Prometheus server | Scrapes and stores time-series data |
| Exporter | Exposes metrics from a system or application |
| Alertmanager | Routes alerts to email, Slack, PagerDuty |
| Grafana | Visualizes metrics as dashboards |
| Pushgateway | Accepts metrics from short-lived jobs |
The model is pull-based: Prometheus reaches out to targets on a schedule, rather than the application pushing data. This makes target health easy to reason about.
Step 1: Run Prometheus Locally
Create a directory and a configuration file prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
- job_name: node
static_configs:
- targets: ['node-exporter:9100']
Start Prometheus with Docker:
docker run -d --name prometheus \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
Open http://localhost:9090 to reach the Prometheus UI. The built-in query console lets you run PromQL expressions directly.
Step 2: Add a Node Exporter
The node exporter exposes CPU, memory, disk, and network metrics from a Linux host.
docker run -d --name node-exporter \
-p 9100:9100 \
--pid host \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
-v /:/rootfs:ro \
prom/node-exporter
Add it to the Prometheus config (already shown above), reload Prometheus, then query:
node_cpu_seconds_total
node_memory_MemAvailable_bytes
node_filesystem_avail_bytes
PromQL Essentials
| Expression | Meaning |
|---|---|
up | Whether each target is reachable (1 or 0) |
rate(metric[5m]) | Per-second rate over a 5-minute window |
sum(metric) | Aggregate across labels |
sum by (instance) (metric) | Aggregate grouped by a label |
avg_over_time(metric[1h]) | Average over a time window |
histogram_quantile(0.95, ...) | 95th percentile from a histogram |
Example: CPU usage per second, averaged across cores.
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
Step 3: Instrument an Application
For a Python Flask app, use the official client library:
pip install prometheus-client flask
from flask import Flask
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import time
app = Flask(__name__)
REQUEST_COUNT = Counter('http_requests_total', 'Total requests', ['method', 'endpoint'])
REQUEST_LATENCY = Histogram('http_request_duration_seconds', 'Request latency')
@app.before_request
def start_timer():
request.start_time = time.time()
@app.after_request
def record_metrics(response):
REQUEST_COUNT.labels(method=request.method, endpoint=request.path).inc()
REQUEST_LATENCY.observe(time.time() - request.start_time)
return response
@app.route('/metrics')
def metrics():
return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST}
@app.route('/')
def index():
return "Hello"
Add the app to the Prometheus config as a new scrape job, pointing at app:5000/metrics.
Step 4: Install Grafana
docker run -d --name grafana \
-p 3000:3000 \
grafana/grafana
Log in at http://localhost:3000 with default credentials admin/admin. Change them immediately.
Step 5: Connect Grafana to Prometheus
- Go to Connections → Data sources → Add data source.
- Select Prometheus.
- Set the URL to
http://prometheus:9090(orhttp://host.docker.internal:9090if running separately). - Click Save & test.
Building Your First Dashboard
| Panel | Query |
|---|---|
| Target status | up |
| Request rate | sum(rate(http_requests_total[5m])) |
| Error rate | sum(rate(http_requests_total{status=~"5.."}[5m])) |
| p95 latency | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) |
| Memory used | node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes |
Save the dashboard with a meaningful name and export the JSON if you want to version it in Git.
Alerting Rules
Create alerts.yml:
groups:
- name: availability
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} is down"
Reference it in prometheus.yml under rule_files, then reload Prometheus. Alerts route through Alertmanager.
Best Practices
- Keep label cardinality low. Do not use user IDs or request IDs as labels.
- Use
rate()on counters and raw values on gauges. - Record expensive queries as recording rules instead of re-running them in every dashboard.
- Set retention and remote-write to long-term storage like Thanos or Mimir for multi-year data.
- Version dashboards as JSON in Git; do not rely on manual edits to production Grafana.
