A server without monitoring is flying blind: full disks, RAM bottlenecks, or a crashed container only become noticeable once a service goes down — or worse, when a customer reports it. In this tutorial you'll build a complete monitoring stack on your VPS: Prometheus collects metrics, Grafana visualizes them in dashboards, and Alertmanager sends you a notification before something breaks.
30 minIntermediateTested on Ubuntu 24.04, DockerUpdated 2026-07-07
In short
Start five containers via Docker Compose (Prometheus, Node Exporter, cAdvisor, Grafana, Alertmanager), import two ready-made Grafana dashboards, enable four alert rules and you'll be warned by email or Telegram before CPU, RAM, or disk space run out.
Collects and stores metrics (time-series database)
9090
Node Exporter
Provides host metrics: CPU, RAM, disk, network
9100
cAdvisor
Provides per-container metrics for Docker
8080
Grafana
Dashboards and visualization
3000
Alertmanager
Sends notifications (email, Telegram, …)
9093
The principle is simple: Node Exporter and cAdvisor expose metrics as HTTP endpoints, Prometheus polls ("scrapes") them every 15 seconds and stores them. Grafana reads from Prometheus and draws graphs. If a value crosses a defined threshold, Prometheus fires an alert rule, and Alertmanager delivers it.
Create the file docker-compose.yml in ~/monitoring:
Two details matter here:
All ports are bound to 127.0.0.1. This means Prometheus, Grafana, and Alertmanager are not reachable from the internet, only locally or via a reverse proxy. If you want to use the web interfaces without a domain, reach them via an SSH tunnel: ssh -L 3000:localhost:3000 user@server. You should have a properly configured firewall with ufw in place regardless.
--storage.tsdb.retention.time=30d limits the retention period to 30 days. On a VPS with limited disk space, you can reduce this.
Change the default password
The Grafana admin password appears in plain text in the compose file. Set your own strong password here before the first start, and change it again after your first login to Grafana. Never expose Grafana with default credentials behind a public domain.
Create prometheus/alert-rules.yml with four rules that make sense on any server:
The for clause prevents false alarms: a brief CPU spike during a backup won't trigger an alert yet. Only once the condition persists for the defined duration does the alert fire.
Create alertmanager/alertmanager.yml. The example below sends alerts by email. If you're running your own mail server, keep in mind you'll need a correct PTR record for outbound mail:
Telegram alternative: If you don't have SMTP access, you can send alerts directly to a Telegram bot:
You get a bot token from Telegram's @BotFather, and the chat ID from @userinfobot.
All five containers should show as running. First check whether Prometheus can reach all targets — via SSH tunnel or reverse proxy at http://localhost:9090/targets. All three jobs (prometheus, node, cadvisor) must have status UP.
Open Grafana at http://localhost:3000 (or your domain) and log in with the credentials from the compose file.
Add a data source:
Connections → Data sources → Add data source → Prometheus
Enter http://prometheus:9090 as the URL (container name, not localhost!)
Save & test — you should see a green confirmation
Import dashboards: Instead of building dashboards yourself, import two proven community dashboards via Dashboards → New → Import by entering the respective ID:
1860 — "Node Exporter Full": the standard dashboard for host metrics including CPU, RAM, disk I/O, network, and filesystems
14282 — cAdvisor dashboard: shows CPU and RAM usage per container
After importing, select your Prometheus data source for each — done! You now have a complete view of your server and every individual container.
If you followed our Caddy tutorial, a single entry in the Caddyfile is enough to serve Grafana over HTTPS with an automatic certificate:
Then run docker compose restart caddy (or systemctl reload caddy). Prometheus and Alertmanager generally don't need a public interface — for occasional access, an SSH tunnel is enough, which reduces the attack surface.
Never trust an alerting system you've never triggered. The easiest test is the TargetUnreachable rule:
After about two minutes (the rule's for duration), the alert should switch to FIRING in Prometheus under Alerts, and shortly after the email or Telegram message should arrive. Afterward:
Alertmanager automatically sends a "Resolved" notification.
On a typical 2-vCPU VPS, the entire stack runs at around 300–500 MB RAM and minimal CPU load. cAdvisor accounts for the largest share; if you only need host metrics and no container details, you can leave it out and save noticeably more. Prometheus's storage requirements grow with the number of metrics, but with 30 days of retention and this setup it typically stays under 2 GB on disk.
With five containers and three configuration files, you now have monitoring that prevents many outages before they happen: you can see trends (the disk will be full in three weeks), catch memory leaks in containers, and get notified as soon as a service goes down. Round out this setup with regular backups — monitoring warns you, backups save you.
In the next part of our "Production-Ready Server" series, we'll extend the stack with centralized logging using Loki, so that in Grafana you'll see not just that a container has problems, but also why — straight from its logs.
How many resources does the monitoring stack need?
Around 300–500 MB RAM and minimal CPU load. If you skip cAdvisor and only collect host metrics, you can get by with noticeably less. On disk, Prometheus typically stays under 2 GB with 30 days of retention.
Can I also monitor additional servers with this?
Yes. Install just the Node Exporter on each additional server and add it as an extra target in prometheus.yml. You should secure the connection via a private network, VPN, or firewall rule so the exporter isn't publicly reachable.
Why am I not receiving alert emails?
First check at http://localhost:9093 whether the alert is arriving at Alertmanager. If it is, the issue lies with mail delivery: SMTP credentials, port 587/465, and (if using your own mail server) the PTR record are the most common causes.
No. For everyday use, an SSH tunnel is enough (ssh -L 3000:localhost:3000 user@server). A public domain via Caddy is more convenient, but should then be secured with a strong password and ideally 2FA.