→ From Coder to Shipper
From Coder to Shipper · DevOps

Observability

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

The problem

"Is the site down?"

Without observability, the first thing you know about an outage is a message from someone who can't use your product.

🌐 Your site

It quietly stops serving — a crashed process, a full disk, a bad deploy.

→

👤 A user hits it

They see a blank page or an error — and now <b>they</b> know before you do.

→

📣 They tell you

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

The operator's job

One question, all day

Everything in this topic exists to answer the same three things, faster and earlier than a user can.

Is it up?

Can a real user reach the site right now?

Is it healthy?

Even if it loads — is it slow, erroring, or running out of resources?

Tell me when it breaks

A notification to your phone — before anyone else notices.

1
Part 1 · Why

The big picture

Observability in one sentence: make the invisible visible — and the silent loud.

The vocabulary

Three pillars

You never really need all three perfectly — but know what each one is.

📈 Metrics

Numbers over time — requests/second, latency, CPU, memory. Good for trends and alarms.

📜 Logs

What happened — one line per event, with the moment and the message. Good for digging into a specific error.

💓 Uptime

Is it reachable at all? A checker pings the site on a schedule and shouts when it can't reach it.

One picture

Three answers to "what's happening?"

Your app produces all three. Different tools collect each one — and different questions pull you to each.

📈 Metrics

"How is it doing?" — numbers ticking along a timeline.

→ trends + alarms

📜 Logs

"What exactly happened?" — the story of one request, one error.

→ dig into one problem

💓 Uptime

"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

Two jobs

See it. Then get woken up.

Observability is two halves that must both exist — one without the other is not enough.

👁 See what's happening

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?

🚨 Get alerted when it breaks

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?

The cast

Three tools, one job

Meet the characters before the story. Each does one job the others can't.

ToolAnswersIts 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 entire reason observability exists
2
Part 2 · Uptime

Is it reachable?

The simplest, highest-value sensor: a checker that pings your site every minute.

The mechanism

A checker, outside your app

The key idea: the checker is <b>not</b> your app. It watches from the outside, like a nurse taking a pulse.

⏱ On a schedule

Every minute (or every few minutes), it does something a real user would: open a URL, or ping the server.

📢 On failure

If the request fails — timeout, wrong status code, unreachable — it records the downtime and fires your alert.

Why it's first

Minutes matter

Uptime monitoring is the cheapest, fastest way to catch the worst failure mode: total outage.

🕐 Detect in ~1 minute

A per-minute check means a total outage is known within a minute — not an hour later.

📱 Alert to where you are

Telegram, Zalo, email, Slack — the alert lands on a device you actually check.

📉 Proves it to yourself

A history of up/down with exact timestamps — useful for "when did it break?" and for trust.

Why Uptime Kuma

The easy one

Uptime Kuma is a self-hosted, open-source uptime checker. You run it in one container and it runs in your own stack.

🏠 Self-hosted

One Docker container on your VPS. Your monitoring data stays on your machine, not a stranger's.

🖱 Simple by design

A web UI: paste a URL, pick a check type and an interval, save. No config-language to learn first.

🔔 Alert channels

Telegram, Zalo, email, Slack, Discord and more — add a bot token and you're notified on your phone.

Run it

Uptime Kuma in docker compose

One service, a port, a volume for its data. That's the whole deployment.

docker-compose.yml
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

Try it

Up in three commands

Start it, check it's running, and confirm the site answers.

ops@vps — zsh
❯ docker compose up -d uptime-kuma
[+] Running 1/1
✔ uptime-kuma Started
❯ docker compose ps
NAME STATUS PORTS
uptime-kuma Up 0.0.0.0:3001->3001/tcp
❯ curl -sI https://yoursite.com | head -1
HTTP/2 200
The loop

Check → record → (maybe) alert

This small loop, running every minute, is the entire product.

⏱ Every minute

Uptime Kuma fires the check: HTTP GET to your URL (or ping, TCP, keyword).

→

📊 Record a heartbeat

Result + response time are stored → the uptime history and the status page.

→

🚨 If it failed

After N failed checks, push the alert: Telegram / Zalo / email.

success = silence (you don't want a message every minute) · failure = loud

3
Part 3 · Metrics

Numbers over time

Uptime says up/down. Metrics say <i>how</i> — and they warn you while it's still up.

Don't mix them up

Metrics vs logs

They're different shapes of data, and each answers a different question.

📈 Metrics — the pulse

A number with a timestamp: 42 requests/sec at 14:03, 180ms latency at 14:04. Small, cheap, and perfect for graphs and thresholds.

📜 Logs — the diary

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.

What to watch

Six numbers worth a dashboard

You'll never memorize all metrics — these six cover most of what an operator actually watches.

🚀 Request rate

How many requests per second — a sudden drop usually means a problem upstream.

⏳ Latency

How long a request takes. Slow is a problem even when it's not failing.

💥 Error rate

What fraction of requests fail (5xx). The number to watch most closely.

🧠 CPU

How busy the processor is — a long 100% means something is spinning or stuck.

🧮 Memory

How much RAM is used. A slow climb over days is a leak waiting to crash.

💾 Disk

Free space left. A full disk takes a perfectly good app down.

Tell Prometheus what to pull

A tiny scrape_config

Prometheus asks: "every 15 seconds, go to this address and grab /metrics." That's the whole config.

prometheus.yml
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

The pull model

Prometheus comes to you

This is the part that surprises people: your app does <b>not</b> send metrics out. Prometheus reaches in and pulls them.

→ The app exposes

A small HTTP endpoint (/metrics) that, when visited, prints the current numbers as plain text.

← Prometheus pulls

On its schedule, it visits every configured /metrics, reads the numbers, and stamps them with the time.

The mechanism

The pull, every 15 seconds

No push, no sending. Prometheus keeps a list of /metrics addresses and simply asks, on a schedule.

🌐 Your app

Exposes /metrics — a page of plain-text numbers. It never phones home.

GET /metrics →

←pulls

⏲ Prometheus

Visits every target on its schedule, reads the numbers, stamps them with the time.

→

🗄 Time series

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

Ask it a question

One PromQL query

You won't write many of these by hand — but recognize the shape: <b>a metric, a window, and a function</b>.

promql
# 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"

What's stored

A number + a when

Every metric Prometheus keeps is a time series: the same name, measured again and again.

🏷 A name + labels

http_requests_total{status="200", path="/"} — labels let you slice the same number many ways.

🔢 A value

The number at that moment — 42.7, 180, 0.003…

⏱ A timestamp

When it was taken. Together, the three make one point on a graph.

4
Part 4 · Dashboards & alerts

Grafana: the picture

Prometheus has the numbers. Grafana turns them into the thing you actually stare at.

Who's who

The data vs the dashboard

A pairing that comes up everywhere — one stores, the other shows.

🗄 Prometheus — the data

The brain that pulls and stores the numbers. Headless — you mostly talk to it with queries.

📊 Grafana — the picture

The face. Point it at Prometheus, and it draws the graphs: latency over time, errors, CPU, memory.

What you'll stare at

A dashboard in one glance

Panels of your metrics, side by side. Green means healthy — you scan in two seconds.

REQUEST LATENCY (p95) — last 6 h

184 ms → 402 ms ↑

ERROR RATE

0.4% → 3.1%

CPU

88%

one dashboard tells a story: latency up, errors up, CPU climbing — something is wrong, and you saw it first

The "page me" part

Alerts: a rule that shouts

A dashboard only helps if you're looking at it. An alert helps when you're not.

📏 A threshold

You say: "if error rate stays above 5% for more than 5 minutes…" That's the rule.

📲 A notification

…"then send a message to my Telegram." The rule fires, the message lands, you go look.

The chain

Number → rule → your phone

Three links. Break any one and the alarm is silent.

🔢 A metric moves

error_rate climbs from 0.4% to 7% — Prometheus just has the number.

→

📏 The rule matches

"&gt; 5% for 5 minutes" is true → the alert enters the "firing" state.

→

📲 You're notified

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)

A sensible start

What to alert on

Few, loud alerts beat many quiet ones. Start with these four.

SignalWhenWhy it hurts
Site down3 failed checks in a rowNobody can use anything — the loudest alert you'll ever need.
High error rate&gt; 5% for 5 minutesThe site loads, but a chunk of users are broken.
High latencyp95 &gt; 1s for 10 minutesSlow site → users leave, even without an error.
Disk almost full&gt; 90% usedTakes a healthy app down; logs + data stop writing.
5
Part 5 · The stack

One VPS, four containers

Everything you've met, running together. Your app plus the three sensors.

The whole thing

App + Uptime Kuma + Prometheus + Grafana

One compose file, four services, four ports in your browser. This is a real, working skeleton.

docker-compose.yml
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

The big picture

Who feeds what

Two independent chains, both starting from your app. One is fast and simple; the other is deep.

Chain 1 · Uptime — “is it there?”

🌐 Your app

Serving users on :5000.

→pings

💓 Uptime Kuma

Checks every minute from the outside.

→on failure

📲 Alerts

Telegram / Zalo / email — “it's down.”

Chain 2 · Metrics — “how is it doing?”

🌐 Your app

Exposes /metrics — plain-text numbers.

←pulls

🗄 Prometheus

Stores the numbers as time series.

→reads

📊 Grafana

Draws the dashboards you stare at.

same app, two eyes · uptime for the worst case, metrics for everything in between

The daily loop

What an operator actually does

Observability isn't a one-time setup — it's a short, repeating loop.

  1. 1

    Glance at the dashboard

    Grafana, two seconds: green = healthy. That's the whole job on a good day.

  2. 2

    Get paged

    An alert lands on Telegram/Zalo: “error rate high”. Now you go look — you didn't have to guess.

  3. 3

    Read the picture

    Which metric moved? When did it start? Does it line up with a deploy? Dashboards + a few logs answer it.

  4. 4

    Fix, then watch it recover

    Ship the fix and watch the curve come back down. The graph confirms it — not a feeling.

Out of scope, on purpose

What we're not learning here

There's a whole industry under the surface. This deck gives you the map, not the territory.

🔍 Deep PromQL

Recording rules, histogram algebra, alert rules in Prometheus — you'll copy-paste, not hand-write.

🧭 Service discovery

Kubernetes, DNS, Consul — how Prometheus finds targets automatically at scale.

📚 Loki & Tempo

Full log and trace collection — the next step when one line of log isn't enough.

🔀 Alert routing

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.
— how operators actually adopt observability

See it. Know it. Fix it.

Next: keep the lights on — the rest of DevOps