DEV Community

DevKit
DevKit

Posted on

Monitor your VPS in 10 minutes: Prometheus, Grafana and Telegram alerts (free kit)

Most people start thinking about monitoring after the first incident: the disk fills up with logs, the database dies from lack of memory, and you learn about it from your users. Basic protection can be set up in an evening, and faster if you don't assemble the stack by hand.

Here is the minimal setup that is enough for one VPS. Prometheus collects metrics, Grafana shows them, and Alertmanager sends messages to Telegram. Everything runs with Docker Compose. I packaged the ready-made configs into a free kit, Monitoring Lite, and the commands below come from it.

What you get

  • four containers: Prometheus, node-exporter, Alertmanager, Grafana;
  • one Host Overview dashboard: CPU, memory, disk usage by mountpoint, network, load average, uptime;
  • five alerts in Telegram: host down, high CPU, high memory, disk almost full, host rebooted;
  • a Grafana password generated at install time, and ports that are closed to the outside world.

How it fits together

node-exporter reads the host's /proc and /sys and serves metrics over HTTP. Prometheus scrapes them every 15 seconds and stores them. Alert rules are evaluated inside Prometheus: when an expression stays true for the configured time, the alert goes to Alertmanager, which groups messages and sends them to Telegram. Grafana reads data from Prometheus.

The datasource and dashboard are provisioned automatically, so you don't create anything by hand in the UI.

Step 1: a Telegram bot

  1. Message @botfather in Telegram, send /newbot, and pick a name. You get a token like 123456789:AAExxxxxxxx.
  2. Send any message to your new bot. Until you do, it cannot write to you first.
  3. Open https://api.telegram.org/bot<TOKEN>/getUpdates in a browser and find "chat":{"id": ... in the response. That's your chat_id. For a group, add the bot to the group and post something there; the ID will be a negative number.

Step 2: install

You need Docker and Docker Compose. Download the kit: https://devkit-shop.surge.sh/free/devops-monitoring-lite-v1.0.zip, unpack it, and run the installer:

unzip devops-monitoring-lite-v1.0.zip
cd devops-monitoring-lite
TG_BOT_TOKEN=123456789:AAExxxxxxxx TG_CHAT_ID=987654321 ./install.sh
Enter fullscreen mode Exit fullscreen mode

TG_BOT_TOKEN and TG_CHAT_ID are only needed for this run: the installer saves them to .env as TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID. If you don't pass them and they aren't in .env yet, the script asks for the token and chat_id interactively. It:

  1. checks for Docker and docker compose (or the older docker-compose);
  2. creates .env and generates a Grafana admin password in it;
  3. validates the token and chat_id format so a malformed value can't break the YAML;
  4. renders the Alertmanager config from a template with your token and chat_id;
  5. runs docker compose up -d and restarts Alertmanager so it picks up the fresh config.

You can safely re-run it: the Grafana password and other values in .env are kept. To change the token, run the installer again with a new TG_BOT_TOKEN, or edit .env and run ./install.sh.

The token is injected at render time and is not stored in the compose file. The generated alertmanager.yml and .env are both listed in the kit's .gitignore, so they stay out of git.

Step 3: reach Grafana

All ports are bound to 127.0.0.1 on purpose. Prometheus and Alertmanager have no authentication, so they should not be exposed to the internet. From your computer, use an SSH tunnel:

ssh -L 3000:127.0.0.1:3000 user@your-server
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000. The login is admin; the password is in .env:

grep GRAFANA_ADMIN_PASSWORD .env
Enter fullscreen mode Exit fullscreen mode

Find the "Host Overview — Сервер" dashboard in the "Monitoring Lite" folder. It shows CPU, memory, disk and network graphs, load average and uptime (the archive includes a sample screenshot, host-overview.png).

You can open access by IP by setting BIND_ADDR=0.0.0.0 in .env and running docker compose up -d. But this setting is shared, so all four ports go public: 3000, 9090, 9093 and 9100. There's a second trap: ports published by Docker bypass ufw rules, so ufw deny 9090 won't close them. You'd have to block them with your hosting provider's firewall or with rules in the iptables DOCKER-USER chain. If in doubt, keep 127.0.0.1 and use the tunnel.

Step 4: verify the alert path

First, check that Prometheus sees the targets and has loaded the rules:

curl -s http://127.0.0.1:9090/api/v1/targets | grep -o '"health":"[a-z]*"'
curl -s http://127.0.0.1:9090/api/v1/rules | grep -o '"name":"[A-Za-z]*"'
Enter fullscreen mode Exit fullscreen mode

Both targets (Prometheus itself and node-exporter) should report "health":"up", and the second command should list five rule names. Now send a test alert:

./scripts/send-test-alert.sh
Enter fullscreen mode Exit fullscreen mode

The script POSTs to the Alertmanager API. Within 30–60 seconds, a Telegram message should arrive with the header "🔴 ВНИМАНИЕ" (Russian for "ALERT"), followed by the line "TestAlert (warning)" and the text "Это тестовый алерт" ("This is a test alert"). The delay comes from group_wait: 30s, which lets Alertmanager batch related alerts into one message.

If nothing arrives, check the logs:

docker compose logs alertmanager | grep -i telegram
Enter fullscreen mode Exit fullscreen mode

Most often the cause is that you never messaged the bot, or the token has a typo.

The rules

The rules live in config/prometheus/alert_rules.yml.

Alert Condition for
InstanceDown up{job="node-exporter"} == 0 2 min
HighCPU average CPU load > 90% 10 min
HighMemory more than 90% of RAM used, based on MemAvailable 5 min
DiskSpace disk more than 85% full 5 min
HostRebooted time() - node_boot_time_seconds < 600 none

The for clause prevents false alarms: a short CPU spike during a build or a backup won't wake you up at night. HostRebooted has no for, so it fires right after boot and stays active for the first 10 minutes.

The DiskSpace rule ignores tmpfs, overlay, squashfs, devtmpfs, all fuse.* types (such as fuse.sshfs), nfs and nfs4, cifs, smb3 and autofs. Without this filter, it fires on system and network mounts you don't manage. For memory, the rule uses MemAvailable rather than MemFree: Linux uses spare memory for cache, so "free" memory is almost never large.

Thresholds are edited in the same file. After a change, reload the rules without restarting:

curl -X POST http://127.0.0.1:9090/-/reload
Enter fullscreen mode Exit fullscreen mode

This works because Prometheus runs with --web.enable-lifecycle.

Adding a second server

Run node-exporter on the second server and add it to config/prometheus/prometheus.yml under the node-exporter job as a separate entry with its own label:

  - job_name: node-exporter
    static_configs:
      - targets: ["node-exporter:9100"]
        labels:
          instance_name: "main-server"
      - targets: ["10.0.0.5:9100"]
        labels:
          instance_name: "server-2"
Enter fullscreen mode Exit fullscreen mode

Keep the instance_name label on the first server as is; it's already in the kit's config. Then run docker compose restart prometheus. Don't expose port 9100 to the internet; send the traffic over a VPN or private network.

Common problems

The Grafana container won't start. Check docker compose logs grafana. The usual cause is permissions on a volume left over from a previous install. If you don't need the data, ./uninstall.sh --purge-data followed by ./install.sh helps.

A Prometheus target is DOWN. Open the targets page through the tunnel at http://127.0.0.1:9090/targets; it shows the reason, such as connection refused or context deadline exceeded. Usually the service hasn't started yet, or there's a typo in the address in prometheus.yml.

Port already in use. If Grafana or Prometheus is already running on the server, change the relevant variables in .env (GRAFANA_PORT, PROMETHEUS_PORT, ALERTMANAGER_PORT, NODE_EXPORTER_PORT) and run docker compose up -d. Remember to change the port in the curl commands and the SSH tunnel from this article too.

Telegram wasn't configured during install. Alertmanager still starts, just without sending anything. Fill in TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env and run ./install.sh again.

Stopping and removing

./uninstall.sh              # stop, keep data
./uninstall.sh --purge-data # stop and delete data
Enter fullscreen mode Exit fullscreen mode

What's not in this kit

The kit is deliberately limited to one server. It doesn't watch Docker containers, check website availability or SSL certificate expiry, collect logs, or handle backups. If you need those, you can add them yourself: cAdvisor for containers, blackbox-exporter for sites, Loki for logs.

The full version

The paid DevOps Monitoring Starter Kit covers those gaps: cAdvisor, blackbox-exporter for site and SSL checks, three dashboards (host, Docker containers, sites and SSL), 11 alert rules, a backup script, and optionally Loki with Promtail for logs and HTTPS via Caddy. Details are at https://devkit-shop.surge.sh/, where you can pay with USDT (TRC20) or, in Russia, by card.

Disclosure: this article and the kit were prepared by an AI assistant working on behalf of the kit's author, and the commands were checked against the kit files.

Top comments (0)