Stop finding out your site is down from a user's complaint. See what's happening — and get alerted the moment it breaks.
← / → to navigate · EN/VI switch below
Without observability, the first thing you know about an outage is a message from someone who can't use your product.
It quietly stops serving — a crashed process, a full disk, a bad deploy.
They see a blank page or an error — and now <b>they</b> know before you do.
Email, chat, a tweet. Minutes or hours lost — and your reputation with them.
the site broke silently · the user is your only sensor · that's the part observability removes
Everything in this topic exists to answer the same three things, faster and earlier than a user can.
Can a real user reach the site right now?
Even if it loads — is it slow, erroring, or running out of resources?
A notification to your phone — before anyone else notices.
Observability in one sentence: make the invisible visible — and the silent loud.
You never really need all three perfectly — but know what each one is.
Numbers over time — requests/second, latency, CPU, memory. Good for trends and alarms.
What happened — one line per event, with the moment and the message. Good for digging into a specific error.
Is it reachable at all? A checker pings the site on a schedule and shouts when it can't reach it.
Your app produces all three. Different tools collect each one — and different questions pull you to each.
"How is it doing?" — numbers ticking along a timeline.
→ trends + alarms
"What exactly happened?" — the story of one request, one error.
→ dig into one problem
"Is it even there?" — yes/no, checked from the outside.
→ down / up
today we focus on uptime + metrics — the two that keep a live site alive
Observability is two halves that must both exist — one without the other is not enough.
Dashboards of metrics, a log search, a live uptime status. This is the calm, day-to-day view.
Answers: is it slow? is error rate climbing? is the disk filling?
A rule watches a number and, when it crosses a line, sends a message to your phone. This is the part that saves you at 3am.
Answers: which one thing should I go look at right now?
Meet the characters before the story. Each does one job the others can't.
| Tool | Answers | Its job |
|---|---|---|
| Uptime Kuma | "Is it reachable?" | A self-hosted checker that pings your site every minute and alerts you when it can't reach it. |
| Prometheus | "What are the numbers?" | Pulls metrics from your services on a schedule and stores them as time series you can query. |
| Grafana | "Show me a picture" | Reads Prometheus data and turns it into dashboards — graphs of your metrics over time. |
"If you can't see it, you can't fix it — and if you're asleep, you can't see it at all."
The simplest, highest-value sensor: a checker that pings your site every minute.
The key idea: the checker is <b>not</b> your app. It watches from the outside, like a nurse taking a pulse.
Every minute (or every few minutes), it does something a real user would: open a URL, or ping the server.
If the request fails — timeout, wrong status code, unreachable — it records the downtime and fires your alert.
Uptime monitoring is the cheapest, fastest way to catch the worst failure mode: total outage.
A per-minute check means a total outage is known within a minute — not an hour later.
Telegram, Zalo, email, Slack — the alert lands on a device you actually check.
A history of up/down with exact timestamps — useful for "when did it break?" and for trust.
Uptime Kuma is a self-hosted, open-source uptime checker. You run it in one container and it runs in your own stack.
One Docker container on your VPS. Your monitoring data stays on your machine, not a stranger's.
A web UI: paste a URL, pick a check type and an interval, save. No config-language to learn first.
Telegram, Zalo, email, Slack, Discord and more — add a bot token and you're notified on your phone.
One service, a port, a volume for its data. That's the whole deployment.
services:
uptime-kuma:
image: "louislam/uptime-kuma:1" # the official image
container_name: uptime-kuma
restart: unless-stopped # come back after a reboot
ports:
- "3001:3001" # your browser -> the web UI
volumes:
- kuma-data:/app/data # heartbeats + settings survive
then: docker compose up -d → open http://your-vps:3001
Start it, check it's running, and confirm the site answers.
This small loop, running every minute, is the entire product.
Uptime Kuma fires the check: HTTP GET to your URL (or ping, TCP, keyword).
Result + response time are stored → the uptime history and the status page.
After N failed checks, push the alert: Telegram / Zalo / email.
success = silence (you don't want a message every minute) · failure = loud
Uptime says up/down. Metrics say <i>how</i> — and they warn you while it's still up.
They're different shapes of data, and each answers a different question.
A number with a timestamp: 42 requests/sec at 14:03, 180ms latency at 14:04. Small, cheap, and perfect for graphs and thresholds.
A line of text about one event: "500 on /checkout at 14:04:11, KeyError: 'card'". Bigger, richer, and perfect for explaining one specific failure.
You'll never memorize all metrics — these six cover most of what an operator actually watches.
How many requests per second — a sudden drop usually means a problem upstream.
How long a request takes. Slow is a problem even when it's not failing.
What fraction of requests fail (5xx). The number to watch most closely.
How busy the processor is — a long 100% means something is spinning or stuck.
How much RAM is used. A slow climb over days is a leak waiting to crash.
Free space left. A full disk takes a perfectly good app down.
Prometheus asks: "every 15 seconds, go to this address and grab /metrics." That's the whole config.
scrape_configs:
- job_name: "myapp" # a named target to scrape
scrape_interval: 15s # how often to pull
metrics_path: /metrics # the endpoint with the numbers
static_configs:
- targets: ["localhost:5000"] # where the app listens
your app just needs to expose a /metrics endpoint — the next part
This is the part that surprises people: your app does <b>not</b> send metrics out. Prometheus reaches in and pulls them.
A small HTTP endpoint (/metrics) that, when visited, prints the current numbers as plain text.
On its schedule, it visits every configured /metrics, reads the numbers, and stamps them with the time.
No push, no sending. Prometheus keeps a list of /metrics addresses and simply asks, on a schedule.
Exposes /metrics — a page of plain-text numbers. It never phones home.
GET /metrics →
Visits every target on its schedule, reads the numbers, stamps them with the time.
Every number stored with its timestamp → queryable history for dashboards & alerts.
if a target goes away, Prometheus notices the missing pull — that's another built-in alert
You won't write many of these by hand — but recognize the shape: <b>a metric, a window, and a function</b>.
# how many requests per second, averaged over 5 minutes:
rate(http_requests_total[5m])
# and the p95 latency (95% of requests were faster than this):
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
don't memorize the syntax — the idea is "metric + time window + what you want"
Every metric Prometheus keeps is a time series: the same name, measured again and again.
http_requests_total{status="200", path="/"} — labels let you slice the same number many ways.
The number at that moment — 42.7, 180, 0.003…
When it was taken. Together, the three make one point on a graph.
Prometheus has the numbers. Grafana turns them into the thing you actually stare at.
A pairing that comes up everywhere — one stores, the other shows.
The brain that pulls and stores the numbers. Headless — you mostly talk to it with queries.
The face. Point it at Prometheus, and it draws the graphs: latency over time, errors, CPU, memory.
Panels of your metrics, side by side. Green means healthy — you scan in two seconds.
184 ms → 402 ms ↑
0.4% → 3.1%
88%
one dashboard tells a story: latency up, errors up, CPU climbing — something is wrong, and you saw it first
A dashboard only helps if you're looking at it. An alert helps when you're not.
You say: "if error rate stays above 5% for more than 5 minutes…" That's the rule.
…"then send a message to my Telegram." The rule fires, the message lands, you go look.
Three links. Break any one and the alarm is silent.
error_rate climbs from 0.4% to 7% — Prometheus just has the number.
"> 5% for 5 minutes" is true → the alert enters the "firing" state.
Telegram / Zalo / email: "myapp error rate high — go look."
the rule lives in Prometheus · the delivery can reuse Uptime Kuma's channels or its own (webhook → a bot)
Few, loud alerts beat many quiet ones. Start with these four.
| Signal | When | Why it hurts |
|---|---|---|
| Site down | 3 failed checks in a row | Nobody can use anything — the loudest alert you'll ever need. |
| High error rate | > 5% for 5 minutes | The site loads, but a chunk of users are broken. |
| High latency | p95 > 1s for 10 minutes | Slow site → users leave, even without an error. |
| Disk almost full | > 90% used | Takes a healthy app down; logs + data stop writing. |
Everything you've met, running together. Your app plus the three sensors.
One compose file, four services, four ports in your browser. This is a real, working skeleton.
services:
app: # your app, exposing /metrics
image: "myapp:latest"
ports: ["5000:5000"]
uptime-kuma: # is it reachable? (port 3001)
image: "louislam/uptime-kuma:1"
ports: ["3001:3001"]
prometheus: # pulls app:5000/metrics (port 9090)
image: "prom/prometheus:latest"
ports: ["9090:9090"]
volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml"]
grafana: # reads prometheus, draws dashboards (port 3000)
image: "grafana/grafana:latest"
ports: ["3000:3000"]
docker compose up -d → four little URLs: :3001, :9090, :3000 — and your app on :5000
Two independent chains, both starting from your app. One is fast and simple; the other is deep.
Serving users on :5000.
Checks every minute from the outside.
Telegram / Zalo / email — “it's down.”
Exposes /metrics — plain-text numbers.
Stores the numbers as time series.
Draws the dashboards you stare at.
same app, two eyes · uptime for the worst case, metrics for everything in between
Observability isn't a one-time setup — it's a short, repeating loop.
Grafana, two seconds: green = healthy. That's the whole job on a good day.
An alert lands on Telegram/Zalo: “error rate high”. Now you go look — you didn't have to guess.
Which metric moved? When did it start? Does it line up with a deploy? Dashboards + a few logs answer it.
Ship the fix and watch the curve come back down. The graph confirms it — not a feeling.
There's a whole industry under the surface. This deck gives you the map, not the territory.
Recording rules, histogram algebra, alert rules in Prometheus — you'll copy-paste, not hand-write.
Kubernetes, DNS, Consul — how Prometheus finds targets automatically at scale.
Full log and trace collection — the next step when one line of log isn't enough.
Alertmanager escalation, silences, on-call — for teams, not a first solo stack.
Start with one uptime check and one dashboard. Grow from there — the stack earns its keep before you add more.
Next: keep the lights on — the rest of DevOps