Netdata and Uptime Kuma (covered in our earlier monitoring guide) provide excellent single-server monitoring. For teams running multiple Hong Kong VPS instances, microservices, or applications requiring custom business metrics, a Grafana + Prometheus stack provides the industry-standard observability platform: pull-based metrics collection, flexible PromQL querying, beautiful dashboards, and multi-source alerting.
Architecture Overview
Hong Kong VPS 1 (App) Hong Kong VPS 2 (App)
├── node_exporter :9100 ├── node_exporter :9100
└── app metrics :8080 └── app metrics :8080
│ │
└──────────────────────────────┘
│
┌──────▼──────┐
│ Prometheus │ (scrapes all targets)
│ :9090 │
└──────┬──────┘
│
┌──────▼──────┐
│ Grafana │ (visualises + alerts)
│ :3000 │
└─────────────┘Step 1: Deploy with Docker Compose
mkdir -p /home/deploy/monitoring/{prometheus,grafana,alertmanager}
cd /home/deploy/monitoring
nano docker-compose.ymlversion: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
- '--web.enable-lifecycle'
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "127.0.0.1:3030:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
- GF_SERVER_ROOT_URL=https://grafana.yourdomain.com
- GF_SMTP_ENABLED=true
- GF_SMTP_HOST=smtp.youremail.com:587
- GF_SMTP_USER=${SMTP_USER}
- GF_SMTP_PASSWORD=${SMTP_PASSWORD}
- GF_SMTP_FROM_ADDRESS=grafana@yourdomain.com
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- "127.0.0.1:9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
restart: unless-stopped
ports:
- "127.0.0.1:9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager_data:/alertmanager
volumes:
prometheus_data:
grafana_data:
alertmanager_data:nano .envGRAFANA_PASSWORD=strong_grafana_password
SMTP_USER=your@email.com
SMTP_PASSWORD=your_email_passwordStep 2: Configure Prometheus Scrape Targets
nano prometheus/prometheus.ymlglobal:
scrape_interval: 15s
evaluation_interval: 15s
scrape_timeout: 10s
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
rule_files:
- 'alerts.yml'
scrape_configs:
# Local node metrics
- job_name: 'node-local'
static_configs:
- targets: ['node-exporter:9100']
labels:
instance: 'hk-vps-01'
location: 'hong_kong'
# Remote VPS nodes (install node_exporter on each)
- job_name: 'node-remote'
static_configs:
- targets:
- 'SECOND_VPS_IP:9100'
- 'THIRD_VPS_IP:9100'
# Use basic auth or TLS for remote nodes in production
# Prometheus self-monitoring
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Nginx metrics (requires nginx-prometheus-exporter)
- job_name: 'nginx'
static_configs:
- targets: ['host.docker.internal:9113']
# MySQL metrics (requires mysqld_exporter)
- job_name: 'mysql'
static_configs:
- targets: ['host.docker.internal:9104']
# Custom application metrics
- job_name: 'myapp'
static_configs:
- targets: ['host.docker.internal:8080']
metrics_path: '/metrics'Step 3: Configure Alerting Rules
nano prometheus/alerts.ymlgroups:
- name: hong_kong_vps_alerts
rules:
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU usage on {{ $labels.instance }}"
description: "CPU usage is {{ $value | humanize }}% for 5+ minutes"
- alert: HighMemoryUsage
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 2m
labels:
severity: critical
annotations:
summary: "High memory usage on {{ $labels.instance }}"
description: "Memory usage is {{ $value | humanize }}%"
- alert: LowDiskSpace
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15 for: 5m labels: severity: warning annotations: summary: "Low disk space on {{ $labels.instance }}" description: "Disk space below 15%: {{ $value | humanize }}% remaining" - alert: InstanceDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: "Instance {{ $labels.instance }} is down" description: "{{ $labels.job }} target has been unreachable for 1 minute" - alert: HighNetworkTraffic expr: irate(node_network_receive_bytes_total{device="eth0"}[5m]) > 100000000
for: 5m
labels:
severity: info
annotations:
summary: "High inbound traffic on {{ $labels.instance }}"Step 4: Configure Alertmanager for Telegram Notifications
nano alertmanager/alertmanager.ymlglobal:
resolve_timeout: 5m
route:
group_by: ['alertname', 'instance']
group_wait: 10s
group_interval: 5m
repeat_interval: 4h
receiver: 'telegram'
routes:
- match:
severity: critical
receiver: 'telegram-critical'
repeat_interval: 1h
receivers:
- name: 'telegram'
telegram_configs:
- bot_token: 'YOUR_TELEGRAM_BOT_TOKEN'
chat_id: YOUR_CHAT_ID
message: |
🔔 Alert: {{ .GroupLabels.alertname }}
📍 Instance: {{ .GroupLabels.instance }}
{{ range .Alerts }}
Status: {{ .Status }}
{{ .Annotations.description }}
{{ end }}
- name: 'telegram-critical'
telegram_configs:
- bot_token: 'YOUR_TELEGRAM_BOT_TOKEN'
chat_id: YOUR_CHAT_ID
message: |
🚨 CRITICAL: {{ .GroupLabels.alertname }}
📍 {{ .GroupLabels.instance }}
{{ range .Alerts }}{{ .Annotations.description }}{{ end }}Step 5: Expose Grafana via Nginx
nano /etc/nginx/sites-available/grafanaserver {
listen 443 ssl http2;
server_name grafana.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/grafana.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/grafana.yourdomain.com/privkey.pem;
# Restrict access to known IPs
allow YOUR_HOME_IP;
deny all;
location / {
proxy_pass http://127.0.0.1:3030;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}docker compose up -dStep 6: Import Pre-Built Dashboards
- In Grafana → Dashboards → Import
- Import by ID from grafana.com:
- 1860 — Node Exporter Full (comprehensive server metrics)
- 7362 — MySQL Overview
- 9614 — Nginx by VTS
- 763 — Redis Dashboard
- Select Prometheus as the data source for each dashboard
Conclusion
A Grafana + Prometheus stack on a Hong Kong VPS provides enterprise-grade observability for Asia-Pacific infrastructure — real-time server metrics, custom application dashboards, and intelligent alerting via Telegram or email. Once deployed, this stack monitors every VPS in your fleet from a single Grafana interface.
Deploy your monitoring stack on Server.HK’s Hong Kong VPS plans — reliable uptime and CN2 GIA routing ensure your monitoring infrastructure is accessible from wherever you manage it.
Frequently Asked Questions
How much RAM does a Grafana + Prometheus stack need?
For monitoring 1–5 VPS instances with 30-day retention, the stack requires approximately 1–1.5 GB RAM total (Prometheus ~500 MB, Grafana ~200 MB, node-exporter minimal). A 2 GB RAM VPS dedicated to monitoring comfortably handles a small fleet. For 10+ nodes or longer retention periods, increase Prometheus storage and RAM allocation accordingly.
Can I monitor VPS instances from other providers with this Prometheus setup?
Yes. Install node_exporter on each remote VPS, then add their IP:9100 to the Prometheus scrape config. For security, expose node_exporter only to your monitoring VPS IP (not publicly) — configure UFW to allow port 9100 only from your monitoring server’s IP address.
What is the difference between Grafana + Prometheus and Netdata?
Netdata provides immediate, no-configuration real-time monitoring with a built-in web interface — excellent for single-server monitoring. Grafana + Prometheus requires more setup but provides custom dashboards, PromQL for complex queries, multi-source alerting, and long-term metric retention across multiple servers. Use Netdata for quick per-server visibility; use Grafana + Prometheus for fleet-wide observability and custom business metrics.