
Backup volume xong mà không ai báo khi container chết thì vẫn là chạy mù. Bài này là stack monitoring Docker tôi hay dựng trên VPS: Portainer (quản lý), Uptime Kuma (uptime + alert), cAdvisor (CPU/RAM) — kèm docker stats khi cần nhìn nhanh.
After a few Compose stacks on a VPS, I used to SSH and run docker ps — until a MySQL container restart loop one night while the site “looked fine” behind CDN cache. Monitoring stopped feeling optional: I need to know what is running, whether URLs return 200, and who is eating RAM.
:::tip Illustrations
Click any figure for fullscreen (Esc to close).
:::
Three questions — three tools
| Question | Tool | Default port |
|---|---|---|
| How are containers/stacks? Logs? Restart? | Portainer CE | 9443 (HTTPS) |
| Is the site/API up? Who pages me? | Uptime Kuma | 3001 |
| Which container burns CPU/RAM/disk I/O? | cAdvisor | 8080 |
:::info Already on Dockge? If you prefer compose-on-disk and a lean UI, see Dockge for homelab Compose. You can still run Uptime Kuma + cAdvisor; Portainer is optional when you want full Docker GUI control. :::
Why not only docker ps?
| Situation | CLI only | + Monitoring stack |
|---|---|---|
| 2–3 containers | Fine | Maybe overkill |
| 10+ services, multiple VPS | Easy to miss failures | Dashboard + uptime history |
| Teammates without SSH | Hard to share | Portainer read-only access |
| Container dies at 3am | Silent | Kuma → Telegram |
No monitoring = you learn from user complaints.
1. Portainer CE — full Docker visibility
Portainer is a web GUI for containers, images, volumes, networks, Compose stacks, logs, console, and stats.
Minimal Compose
mkdir -p ~/monitoring/portainer
cd ~/monitoring/portainer
services:
portainer:
image: portainer/portainer-ce:2.27.3
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
docker compose up -d
docker.sock: control the local engine.portainer_data: users and settings.- Open
https://<VPS-IP>:9443
:::caution Admin account
Create the admin user within ~5 minutes of first start. After that, setup locks — docker compose restart and try again.
:::
I use: Containers → Logs / Stats; Stacks → paste compose for experiments; Inspect when env vars are wrong.
2. Uptime Kuma — “is it alive?”
Uptime Kuma is self-hosted uptime monitoring: HTTP(S), TCP, ping, Docker containers, keywords, SSL expiry.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- uptime_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
uptime_data:
http://<IP>:3001 → create admin → Add New Monitor (HTTP, TCP, Docker, etc.).
Settings → Notifications: Telegram or Discord webhook — Test before attaching monitors.
Status Pages — public /status for customers or your team.
3. cAdvisor — per-container metrics
cAdvisor answers: how much CPU/RAM/network/disk does this container use? Exposes /metrics for Prometheus later.
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.51.0
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
Open http://<IP>:8080 → Docker Containers → pick a name → realtime charts.
:::note Advanced Production pattern: cAdvisor → Prometheus → Grafana. This post stops at the cAdvisor UI; add Prometheus when you need metric-based alerting. :::
4. One Compose file — all three
mkdir -p ~/monitoring
cd ~/monitoring
services:
portainer:
image: portainer/portainer-ce:2.27.3
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- uptime_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.51.0
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
volumes:
portainer_data:
uptime_data:
docker compose up -d
| Service | URL |
|---|---|
| Portainer | https://<IP>:9443 |
| Uptime Kuma | http://<IP>:3001 |
| cAdvisor | http://<IP>:8080 |
Combined RAM is often ~200–300MB — reasonable on a 2–4GB VPS if this is your homelab “nervous system”.
5. Security — do not expose admin UIs raw
:::warning Production These ports are high privilege. Prefer:
- Reverse proxy (NPM, Traefik, Caddy) + HTTPS + auth.
- Firewall: VPN/Tailscale or admin IPs only.
- Local bind:
"127.0.0.1:3001:3001"when proxied through a tunnel. :::
See Tailscale mesh VPN for access without public ports.
6. docker stats — quick checks without deploying
docker stats --no-stream
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
No history, no alerts — but answers most “who filled RAM?” moments.
docker system df
What should you run?
In practice: I often run all three; on a tiny VPS, prioritize Uptime Kuma (alerts) + docker stats (debug), add Portainer when CLI fatigue hits.
Summary
- Portainer — manage, logs, stacks, stats in the browser.
- Uptime Kuma — monitors + Telegram/Discord + status page.
- cAdvisor — per-container resources, stepping stone to Prometheus/Grafana.
- One Compose file — fast bootstrap; lock down with proxy/firewall/VPN.
Your Docker stack now has eyes: running vs dead, HTTP health, and CPU hogs — before the inbox fills with “the site is down.”
Sau khi deploy vài stack Compose trên VPS, tôi từng chỉ SSH vào gõ docker ps — cho đến một đêm MySQL container restart loop mà website vẫn “có vẻ” lên vì cache CDN. Từ đó tôi coi monitoring không phải luxury: ít nhất phải biết cái gì đang chạy, URL còn trả 200 không, và ai đang ăn RAM.
:::tip Ảnh minh họa
Bấm vào hình để xem full màn hình (Esc để thoát).
:::
Ba câu hỏi — ba công cụ
| Câu hỏi | Công cụ | Cổng mặc định |
|---|---|---|
| Container/stack đang thế nào? Log? Restart? | Portainer CE | 9443 (HTTPS) |
| Site/API còn sống? Ai báo khi down? | Uptime Kuma | 3001 |
| Container nào ngốn CPU/RAM/disk I/O? | cAdvisor | 8080 |
:::info Đã dùng Dockge? Nếu bạn ưu tiên compose trên disk + UI gọn, xem bài Dockge — quản lý Docker Compose homelab. Vẫn có thể chạy Uptime Kuma + cAdvisor song song; Portainer là tùy chọn khi cần GUI “toàn quyền” Docker. :::
Tại sao không chỉ docker ps?
| Khi | Chỉ CLI | + Monitoring stack |
|---|---|---|
| 2–3 container | Ổn | Hơi thừa |
| 10+ service, vài VPS | Dễ sót | Dashboard + lịch sử uptime |
| Team không SSH | Khó chia sẻ | Portainer read-only cho người không terminal |
| 3h sáng container chết | Không ai biết | Kuma ping Telegram |
Không monitoring = biết sự cố khi user đã phàn nàn.
1. Portainer CE — mắt nhìn toàn Docker
Portainer là GUI cho Docker: container, image, volume, network, compose stack, log, console, stats.
Compose tối thiểu
mkdir -p ~/monitoring/portainer
cd ~/monitoring/portainer
services:
portainer:
image: portainer/portainer-ce:2.27.3
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
docker compose up -d
docker.sock: Portainer điều khiển engine trên host.portainer_data: user, settings.- Truy cập:
https://<IP-VPS>:9443
:::caution Tài khoản admin
Tạo admin trong ~5 phút sau lần start đầu. Quá hạn, Portainer khóa setup — docker compose restart rồi tạo lại.
:::
Tôi hay dùng: Containers → Logs / Stats; Stacks → paste docker-compose.yml deploy thử; Inspect khi debug env sai.
2. Uptime Kuma — “còn sống không?”
Uptime Kuma (cùng tác giả Dockge) self-host, UI đẹp: HTTP(S), TCP, ping, Docker container, keyword, SSL expiry.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- uptime_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
uptime_data:
http://<IP>:3001 → tạo admin → Add New Monitor:
- HTTP(s) — URL production (
https://blog.example.com), interval 60s, keyword hoặc status 200. - TCP — MySQL
3306, Redis6379nếu expose nội bộ. - Docker — container name (cần socket RO).
Settings → Notifications: Telegram (@BotFather + Chat ID) hoặc Discord webhook → Test trước khi gắn monitor.
Status Pages — slug /status public cho team/khách (giống status page SaaS, nhưng trên VPS của bạn).
3. cAdvisor — metrics từng container
cAdvisor (Google) trả lời: container này dùng bao nhiêu CPU/RAM/network/disk? Endpoint /metrics cho Prometheus sau này.
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.51.0
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
http://<IP>:8080 → Docker Containers → chọn tên → biểu đồ realtime.
:::note Nâng cao Combo chuẩn production: cAdvisor → Prometheus → Grafana. Bài này dừng ở cAdvisor UI; khi cần alert trên metric, thêm Prometheus sau. :::
4. Một file Compose — chạy cả ba
mkdir -p ~/monitoring
cd ~/monitoring
services:
portainer:
image: portainer/portainer-ce:2.27.3
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- uptime_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.51.0
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
volumes:
portainer_data:
uptime_data:
docker compose up -d
| Dịch vụ | URL |
|---|---|
| Portainer | https://<IP>:9443 |
| Uptime Kuma | http://<IP>:3001 |
| cAdvisor | http://<IP>:8080 |
RAM gộp thường ~200–300MB — chấp nhận được trên VPS 2–4GB nếu bạn coi đó là “hệ thần kinh” của homelab.
5. Bảo mật — đừng để lộ thẳng ra Internet
:::warning Production Ba cổng trên là admin surface. Nên:
- Reverse proxy (NPM, Traefik, Caddy) + HTTPS + auth.
- Firewall: chỉ VPN/Tailscale hoặc IP admin.
- Bind local:
"127.0.0.1:3001:3001"nếu chỉ proxy qua tunnel. :::
Tham khảo bài Tailscale mesh VPN nếu bạn muốn vào panel không mở port public.
6. docker stats — kiểm tra nhanh không deploy gì
docker stats --no-stream
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
Không có history, không alert — nhưng cứu được 80% câu “ai đang full RAM?”.
docker system df
Chọn gì cho homelab của bạn?
Thực tế: tôi hay chạy cả ba vì vai trò khác nhau; nếu chỉ một VPS nhỏ, ưu tiên Uptime Kuma (alert) + docker stats (debug), thêm Portainer khi mệt CLI.
Tổng kết
- Portainer — quản lý, log, stack, stats trong browser.
- Uptime Kuma — monitor + Telegram/Discord + status page.
- cAdvisor — resource per container, bước đệm Prometheus/Grafana.
- Compose một file — dựng nhanh; khóa bằng proxy/firewall/VPN.
Giờ stack Docker của bạn có “mắt”: biết container sống/chết, URL còn 200, và ai đang đốt CPU — trước khi inbox đầy tin nhắn “web sập rồi anh ơi”.
