What Is Prometheus? Homelab Metrics and Alerts Explained

What Prometheus actually is, how its pull-based scraping collects metrics, what a time series is, PromQL in plain English, and how to write your first alert rule.

On this page
  1. What Prometheus actually is
  2. Scraping: Prometheus comes to you
  3. What Prometheus stores: metrics, labels, and time
  4. PromQL, without the intimidation
  5. Writing your first alert rule
  6. Where Prometheus fits in your monitoring
  7. What’s next

You follow a monitoring guide, install an exporter, and open its page — and there it is: hundreds of raw numbers that vanish and change the moment you refresh. The data exists, but nothing is keeping it, and nothing lets you ask “what was memory doing at 3 a.m.?” That gap — turning a live-but-forgetful page of numbers into stored history you can query and alert on — is exactly what Prometheus fills. This post explains what Prometheus is, the one word that makes it click (scraping), how it stores what it collects, enough PromQL to be dangerous, and how to write your first alert. It’s the collector half of the pair that starts with What Is Grafana? — when you’re ready to build the whole thing, Proxmox Monitoring with Prometheus and Grafana is the hands-on walkthrough.


What Prometheus actually is

Prometheus is, in its own words, “an open-source systems monitoring and alerting toolkit.” The most useful way to think about it is a time-series database that collects its own data. A normal database waits for something to insert rows; Prometheus goes out and fetches the numbers itself on a schedule, then “stores all scraped samples locally and runs rules over this data” — either to record new series or to raise alerts.

That clears up the most common beginner confusion. In a typical homelab monitoring stack, three separate things do three separate jobs:

  • Small agents called exporters sit on each machine and expose its live numbers as a web page. The most common is Prometheus node_exporter, which reports CPU, memory, disk, and network for a Linux host at :9100. It only ever shows right now — refresh and the old values are gone.
  • Prometheus visits every exporter on a schedule, reads that page, and stores the history — it’s the database, keeping each measurement over time so you can look back.
  • Grafana connects to Prometheus, queries it, and draws the picture — it’s the screen you actually look at.
Prometheus reaches out — it pulls, nothing is pushed to itpve-node1node_exporter :9100pve-node2node_exporter :9100nasnode_exporter :9100Prometheus:9090stores historyGET /metricsevery 15sGrafana :3000reads & drawsdata sourceAlertmanager :9093routes notificationsfires alertspeira.dev

Prometheus is the orange box in the middle. Everything to its left is raw measurement; everything to its right consumes what Prometheus has already collected and stored.

Isn't the exporter already doing the work?

The exporter only exposes a snapshot — the numbers as they are this instant, gone on the next request. It has no memory and no query language. Prometheus is what turns a stream of snapshots into history you can look back through, which is why you run one Prometheus for a whole fleet of exporters rather than the other way around.


Scraping: Prometheus comes to you

The word that unlocks Prometheus is scraping, and the surprising part is the direction. “Time series collection happens via a pull model over HTTP” — Prometheus reaches out to each target and reads its numbers. The target never sends anything; it just leaves a /metrics page sitting there, and Prometheus visits on a timer.

You hand Prometheus a list of targets — a host and port that expose /metrics — grouped into named jobs. On every scrape_interval it makes one HTTP request to each target and stores whatever it reads. The built-in default interval is one minute; most homelabs turn it down to 15 seconds. Here is the whole idea as a config file:

First: make these values your own

The addresses below are placeholders. 10.0.0.11 and 10.0.0.12 stand in for your own hosts running node_exporter, and localhost:9090 assumes Prometheus runs on the machine you’re configuring. If a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.

A minimal prometheus.yml — jobs and targets

global:
scrape_interval: 15s      # how often to pull each target; the default is 1m

scrape_configs:
- job_name: prometheus     # Prometheus scraping itself
  static_configs:
    - targets: ["localhost:9090"]

- job_name: node           # every node_exporter you run
  static_configs:
    - targets: ["10.0.0.11:9100", "10.0.0.12:9100"]

The pull model has a quietly useful consequence: because Prometheus is the one making the request, it always knows when a target goes missing. A machine that stops answering can’t hide — its silence is recorded as a special up metric set to 0. A push-based system can’t tell “healthy but quiet” from “dead and gone”; a pull-based one gets that distinction for free, and it’s the basis of the first alert you’ll write below.

What about jobs that don't stick around?

Scraping assumes the target is running long enough to be visited. A backup script that runs for ten seconds and exits would never be caught. That single exception is what the Pushgateway is for — short-lived jobs push their result to it, and Prometheus scrapes the gateway instead. Reach for it only when you genuinely have batch jobs; for anything long-running, plain scraping is the rule.

Prometheus ships with a bare-bones web UI, and the one page you’ll actually open is Status → Targets. It’s the “is my scraping healthy?” screen — every target, its job, whether the last scrape came back UP, and how long ago:

http://10.0.0.68:9090/targets
Prometheus Status Targets page showing the node and prometheus scrape jobs, each reporting 1/1 up with a green UP state badge and a recent last-scrape time
Healthy scraping in the demo lab: each job reports n/n up with a green UP badge and a fresh last-scrape time. A target that fell over would show a red DOWN here — and flip its up metric to 0.

What Prometheus stores: metrics, labels, and time

Everything Prometheus keeps is a time series — and it’s worth 60 seconds to see exactly what one is, because it explains PromQL, dashboards, and alerts all at once. “Every time series is uniquely identified by its metric name and optional key-value pairs called labels.” The metric name says what is measured; the labels pin it to which exact thing; and the series itself is the stream of values over time.

Anatomy of one time seriesnode_filesystem_avail_bytes{instance=“pve-node1:9100”, mountpoint=“/”}metric namewhat is measuredlabelswhich exact thingOne name, many series — change a label,get a different disk or a different host:…{mountpoint=“/”} ← root filesystem…{mountpoint=“/boot”} ← a different series…{instance=“nas:9100”} ← a different hostvaluetime →each point = one sample (a value + a timestamp)peira.dev

Each sample in that stream is just “a float64 value” paired with “a millisecond-precision timestamp.” That’s the whole storage model: names and labels to find a series, then a long list of timestamped numbers inside it. The labels are what make it powerful — one metric name like node_filesystem_avail_bytes fans out into a separate series per filesystem per host, and you slice across them by matching labels. Hold onto that, because it’s exactly how you query.


PromQL, without the intimidation

PromQL is the language you use to ask Prometheus questions. It looks cryptic at first, but almost everything you’ll do early on is one of two shapes:

  • An instant vector — “a set of time series containing a single sample for each time series” — the value of something right now, one number per series. Typing a bare metric name gives you this.
  • A range vector — “a range of data points over time for each time series” — a window of recent values, which you ask for by appending a duration in square brackets like [5m].

A handful of real queries covers most of what a beginner needs:

Three queries you'll actually use

up
# 1 or 0 for every target — "which machines are alive right now?"

node_filesystem_avail_bytes
# free space on every filesystem of every host, one series each

rate(node_network_receive_bytes_total[5m])
# bytes/sec received, averaged over the last 5 minutes

That last one shows why range vectors exist. node_network_receive_bytes_total is a counter — a number that only ever climbs — so its raw value is meaningless on its own. Wrapping it in rate(...[5m]) measures how fast it climbed over a five-minute window, turning “total bytes ever” into the “megabits per second” you actually wanted. Counters plus rate() is the single most common pattern in all of PromQL.

So why do I still need Grafana?

Prometheus has its own Graph page that runs one PromQL query and draws one result — genuinely useful for exploring. What it can’t do is arrange dozens of pre-written queries on a single screen you revisit forever. That’s the job of a Grafana dashboard: the PromQL is still there, just saved inside each panel so you never retype it. Prometheus answers one question; Grafana shows you all of them at once.


Writing your first alert rule

Storing history is half the point; the other half is being told when a number goes wrong so you’re not babysitting a dashboard. An alerting rule is a PromQL expression Prometheus evaluates on a schedule — when the expression returns anything, that’s an alert. Rules live in their own files, referenced from prometheus.yml, and the canonical first one comes straight from the Prometheus docs:

rules.yml — fire when any target goes dark

groups:
- name: homelab-basics
  rules:
    - alert: InstanceDown
      expr: up == 0
      for: 5m
      labels:
        severity: page
      annotations:
        summary: "Instance {{ $labels.instance }} is down"
        description: "{{ $labels.instance }} of job {{ $labels.job }} has been unreachable for 5 minutes."

Read it line by line and it’s plain: expr is the condition (up == 0 — a target isn’t answering); for: 5m says it must stay true for five minutes before Prometheus counts it as firing; labels tag the alert with metadata like severity that decides where it’s routed; and annotations are the human-readable message, with {{ $labels.instance }} filled in per target.

The for clause is the part beginners skip and then regret. Without it, a single missed scrape during a reboot pages you instantly. With for: 5m, “Prometheus will check that the alert continues to be active during each evaluation for 10 minutes before firing” — swap in your own duration — and until then the alert sits in a pending state, visible but silent. Pending absorbs the flapping; firing is what reaches you.

The life of an alertinactiveup == 0 is falsetruependingcounting for: 5mstill truefiringsent to AlertmanagerAlertmanagerdedupe · grouproute · silenceemail · Slack · webhookPrometheus decides an alert shouldfire; Alertmanager decides how itreaches you.peira.dev

Notice the split at the end: Prometheus raises the alert, but it doesn’t send the email. It hands firing alerts to Alertmanager, a separate service that “handles alerts sent by client applications such as the Prometheus server… deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie,” plus silencing and inhibition. That division is the one real difference from Grafana’s built-in alerting, which can email you directly — with Prometheus, raising and delivering are two jobs done by two services.

up == 0 is the ideal first rule precisely because Prometheus synthesizes an up sample for every target on every scrape, so the expression can never quietly return nothing. When you want the copy-paste version — the rule file, the prometheus.yml wiring, validation, and a hot-reload with no restart — the companion Your First Prometheus Alert Rule playbook has it end to end.


Where Prometheus fits in your monitoring

Prometheus is the collect-store-evaluate core of a small stack, and it pairs with the pieces around it rather than replacing any of them:

  • Exporters sit below it — node_exporter and pve-exporter expose the raw numbers Prometheus scrapes.
  • Grafana sits above it — it reads Prometheus as a data source and turns the series into dashboards you can read at a glance.
  • Alertmanager sits beside it — it takes the alerts Prometheus raises and actually delivers them.
  • Uptime checks answer the simpler “is it up?” question — that’s Uptime Kuma’s job, and it’s the right first monitor before you reach for metrics at all.

You don’t need all of it on day one. Most homelabs start with uptime checks, add Prometheus and Grafana once “is it up?” stops being enough, and wire alert rules only after they know which numbers actually matter.


What’s next

With the vocabulary in place, the hands-on builds are just filling in a shape you already understand:

The official Prometheus documentation is the reference to keep open once you start writing your own queries and rules.


Related posts:

Sources: Prometheus overview, Prometheus data model, Querying basics (PromQL), Configuration, Alerting rules, Alertmanager.

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.