👍 What we like
- ✓Complete self-hosted stack using only Docker containers
- ✓Combines availability monitoring with deep system metrics
- ✓Instant Telegram alerts for proactive issue notification
- ✓Visual dashboards via Grafana for easy data interpretation
👎 What to watch
- ✕Requires manual configuration of Prometheus scrape targets
- ✕Initial setup exposes ports directly before reverse proxy config
- ✕Admin password must be manually changed in compose file
- ✕Telegram integration requires external account setup
📑 Contents ▾
- 01 Prerequisites
- 02 Stack Architecture
- 03 Step 1: Prepare the directory structure and docker-compose
- 04 Step 2: Configure Prometheus
- 05 Step 3: Verify metric collection
- 06 Step 4: Configure Uptime Kuma
- 07 Step 5: Configure Grafana
- · Add Prometheus as a data source
- · Import a ready-made dashboard
- 10 Step 6: Set up Telegram alerts
- · Create a Telegram bot
- · Alerts in Uptime Kuma
- · System alerts via Grafana
- 14 Stack Verification
- 15 Best Practices and Security
- 16 FAQ
- · Don’t Uptime Kuma and Grafana do the same thing?
- · What load does this stack put on the server?
- · Why not Netdata, which installs in one command?
- · How do I monitor my Docker containers themselves?
- · Do alerts work if the monitoring server itself goes down?
- 22 Related Topics
A running homelab is good. A homelab where you know, to the second, that a service has crashed, the disk is filling up, or RAM is spiking, is a homelab you control. Too many self-hosters discover a service is dead… only when they try to use it. Monitoring is not a luxury reserved for enterprises: with two or three containers, you get complete visibility and alerts that warn you before your users do.
In this guide, we build a complete, 100% self-hosted monitoring stack via Docker: Uptime Kuma for availability monitoring (are services responding?), Prometheus + node_exporter for system metrics (CPU, RAM, disk, network), and Grafana for beautiful dashboards. We finish with Telegram alerts to notify you instantly. By the end, you’ll know at a glance if everything is fine, and you’ll be notified if it isn’t.
Prerequisites
-
A Linux server or homelab with Docker and Docker Compose installed. See the Docker installation in our Caddy reverse proxy + Docker guide.
-
Ideally, a VPS or dedicated machine for monitoring (a small node is sufficient: the stack consumes very little). For hardware/VPS selection, see best VPS for self-hosting.
-
A reverse proxy (Caddy) if you want to expose the interfaces over HTTPS, which is recommended.
-
A Telegram account and app, to receive alerts.
-
30 to 45 minutes.
If you are unsure about the tools, our comparison Uptime Kuma vs Grafana vs Netdata explains why these building blocks are complementary rather than competitive.
Stack Architecture
Before diving in, let’s understand what each component does:
-
Uptime Kuma: Availability probes (HTTP, TCP, ping, DNS…). Answers the question “is my service responding?”. Simple interface, built-in alerts.
-
node_exporter: A lightweight agent that exposes system metrics from a machine (CPU, RAM, disk, network) in Prometheus format.
-
Prometheus: A time-series database that periodically collects (“scrapes”) metrics from node_exporter.
-
Grafana: Visualization. Connects to Prometheus to display dashboards and trigger alerts based on thresholds.
We deploy everything in a single docker-compose.yml for simplicity.
Step 1: Prepare the directory structure and docker-compose
Create a working directory:
mkdir -p ~/monitoring && cd ~/monitoring
mkdir -p prometheus
Create the composition file:
nano docker-compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- uptime-kuma-data:/app/data
ports:
- "3001:3001"
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
command:
- '--path.rootfs=/host'
pid: host
volumes:
- '/:/host:ro,rslave'
ports:
- "9100:9100"
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
ports:
- "9090:9090"
grafana:
image: grafana/grafana-oss:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_PASSWORD=ChangeThisPassword
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
depends_on:
- prometheus
volumes:
uptime-kuma-data:
prometheus-data:
grafana-data:
Security: Change
GF_SECURITY_ADMIN_PASSWORDto a strong password. The ports are exposed here in plain text for setup; in production, place these services behind an HTTPS reverse proxy and do not publish the ports directly to the Internet (remove theports:sections once you have routed them through Caddy on a shared Docker network).
Step 2: Configure Prometheus
Prometheus needs to know what to collect. Create its config file:
nano ~/monitoring/prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
Since all containers are on the same Docker network (the default project network for compose), Prometheus reaches node-exporter by its service name. Start the stack:
docker compose up -d
Verify that everything starts:
docker compose ps
All four containers should be running.
Step 3: Verify metric collection
Verify that node_exporter is exposing its metrics:
curl -s http://localhost:9100/metrics | head -5
You will get metric lines in Prometheus format. Then, open Prometheus in your browser at http://SERVER_IP:9090, go to Status > Targets: the node target should be in state UP. If it is DOWN, check the service name and port in prometheus.yml.
Step 4: Configure Uptime Kuma
Open http://SERVER_IP:3001. On first launch, create the admin account (username + strong password).
Then add your first probes (“monitors”):
-
Click Add New Monitor.
-
HTTP(s) Type for a website or web service: enter the URL (e.g.,
https://cloud.example.com), a descriptive name, and the check interval (60s is a good default). -
TCP Port Type for a service without a web interface (e.g., a database): specify the host and port.
-
Ping Type to check if a machine responds on the network.
Uptime Kuma immediately displays an availability history, uptime percentage, and response time. Create a public status page via Status Pages if you want to share the status of your services.
Tip: For a scheduled task (like a restic backup) to report that it executed successfully, create a Push type monitor in Uptime Kuma. Your script calls the provided URL at the end; if the call does not arrive within the allotted time, Uptime Kuma alerts you. Useful for detecting silently failed backups.
Step 5: Configure Grafana
Open http://SERVER_IP:3000 and log in with admin and the password defined in the compose file.
Add Prometheus as a data source
-
Menu Connections > Data sources > Add data source.
-
Choose Prometheus.
-
In Prometheus server URL, enter
http://prometheus:9090(container name resolution on the Docker network). -
Click Save & test: you should see “Successfully queried the Prometheus API”.
Import a ready-made dashboard
No need to build everything from scratch: the community offers excellent dashboards. For node_exporter, import the famous Node Exporter Full dashboard (ID 1860):
-
Menu Dashboards > New > Import.
-
Enter ID
1860in the field, click Load. -
Select your Prometheus data source, then Import.
You instantly get a complete dashboard: CPU usage, memory, disk space, I/O, network traffic, system load. It is the control panel for your server.
Step 6: Set up Telegram alerts
The point of monitoring is to be notified. We configure Telegram in both tools.
Create a Telegram bot
-
In Telegram, search for @BotFather, start it, and send
/newbot. -
Choose a name and username for the bot. BotFather gives you a token (something like
123456789:AAH...). -
Start a conversation with your new bot (send it any message).
-
Retrieve your chat ID: open in a browser
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesand locate thechat.idfield in the JSON response.
Alerts in Uptime Kuma
-
In Uptime Kuma: Settings > Notifications > Setup Notification.
-
Type Telegram, paste the Bot Token and Chat ID.
-
Click Test to receive a verification message, then Save.
-
Edit your monitors and check this notification: you will be alerted on every UP → DOWN transition and upon recovery.
System alerts via Grafana
To be notified when disk usage exceeds 85% or RAM exceeds 90%, create an alert rule in Grafana:
-
Alerting > Contact points > Add contact point, type Telegram, enter token and chat ID, test.
-
Alerting > Alert rules > New alert rule. Define a PromQL query, for example for root disk usage:
100 - ((node_filesystem_avail_bytes{mountpoint="/"} * 100) / node_filesystem_size_bytes{mountpoint="/"})
- Set the condition “IS ABOVE 85” over a 5-minute window, associate the Telegram contact point, and save.
You will now receive a message as soon as a critical threshold is crossed, before it becomes an incident.
Stack Verification
Run through the checklist:
# All four services are running
docker compose ps
# Prometheus has the node target UP (returns "up")
curl -s 'http://localhost:9090/api/v1/query?query=up{job="node"}' | grep -o '"value":\[[^]]*\]'
# node_exporter responds
curl -sI http://localhost:9100/metrics | head -1
Perform a real test: intentionally stop a service monitored by Uptime Kuma (docker stop <service>), wait for the check interval, and confirm that you receive the Telegram alert, followed by the recovery message after restart.
Best Practices and Security
-
Do not expose ports in plain text. Place Grafana, Prometheus, and Uptime Kuma behind your Caddy reverse proxy over HTTPS. Prometheus, in particular, has no native authentication: it should never be publicly accessible.
-
Back up the volumes. Uptime Kuma configurations, Grafana dashboards, and Prometheus history live in Docker volumes. Include them in your backup strategy (see encrypted backups with restic).
-
Monitor remote machines. Install node_exporter on each machine in your homelab and add them as targets in
prometheus.yml. Secure inter-machine scraping via WireGuard VPN rather than in plain text. -
Define reasonable retention.
--storage.tsdb.retention.time=30dprevents Prometheus from filling the disk. Increase if you need long-term history, while monitoring disk space. -
Useful alerts, not noisy ones. Too many alerts kill alerting. Reserve notifications for actionable events (service down, full disk, expiring certificate) and group the rest into dashboards.
FAQ
Don’t Uptime Kuma and Grafana do the same thing?
No, they are complementary. Uptime Kuma answers “is it responding?” (availability, from the outside) with near-instant setup. Grafana + Prometheus answer “how is it doing internally?” (detailed system metrics, trends, capacity). Using them together provides a complete view. Our comparison Uptime Kuma vs Grafana vs Netdata details the strengths of each.
What load does this stack put on the server?
Modest. node_exporter is negligible, Uptime Kuma takes ~100 MB of RAM, and Prometheus and Grafana consume based on the number of targets and retention, but a few hundred MB are sufficient for a typical homelab. The main disk usage is for Prometheus history, controlled by the retention period. A small VPS or mini-PC is more than enough.
Why not Netdata, which installs in one command?
Netdata is excellent for ultra-detailed real-time diagnostics on a single machine, with trivial installation. But for centralizing multiple machines, retaining long-term history, and customizing dashboards and alerts, the Prometheus + Grafana combination remains more flexible and standard. There is nothing stopping you from running Netdata locally alongside Grafana centrally.
How do I monitor my Docker containers themselves?
Add cAdvisor to the stack: it exposes per-container metrics (CPU, RAM, network, I/O) that Prometheus collects just like node_exporter. Then import a Grafana dashboard dedicated to Docker containers (several are available in the gallery). You can then distinguish which container is consuming what.
Do alerts work if the monitoring server itself goes down?
No, and this is a classic limitation: a system cannot alert on its own failure. The solution is external supervision. Use a third-party service (or a second instance of Uptime Kuma on another machine/VPS) to monitor the availability of your monitoring server. Thus, if the entire stack goes down, you are still notified.
Related Topics
Your homelab is now under surveillance: availability, system metrics, and instant alerts. You will know before anyone else when something is wrong. To follow new monitoring tools, vulnerabilities, and VPS deals, join our Telegram watch bot.