Practical guide to set up Prometheus and Grafana with Docker Compose on a VPS
By Kevin Marcondes · Published on · Updated on · 9 min read
A hands‑on guide to set up Prometheus and Grafana with Docker Compose on a VPS, including Node Exporter, cAdvisor, Alertmanager, alert rules and datasource provisioning. By the end you'll have a working monitoring stack for hosts and containers, ready for reliable metrics, dashboards and alerts.
Before you start: prerequisites and stack architecture
You need a 64‑bit Linux VPS with root or sudo access, Docker and Docker Compose installed, required ports open and a user to operate the stack. Start with modest resources and scale up as metrics volume and retention grow.
The architecture includes Prometheus (collection/storage), Node Exporter (host metrics), cAdvisor (container metrics), Alertmanager (alert routing) and Grafana (dashboards and alerting).
If you still need to install Docker and Compose, follow this guide: install Docker and Docker Compose on Linux. Unsure about sizing? See how to choose the right VPS size.
Default components and ports inside the internal network:
| Component | Port | Role | Internal URL |
|---|---|---|---|
| Prometheus | 9090 | TSDB and scraping | http://prometheus:9090 |
| Alertmanager | 9093 | Groups and delivers alerts | http://alertmanager:9093 |
| Node Exporter | 9100 | Host metrics | http://node-exporter:9100/metrics |
| cAdvisor | 8080 | Container metrics | http://cadvisor:8080/metrics |
| Grafana | 3000 | Dashboards and alerts | http://grafana:3000 |
Prepare directories, variables and permissions on the VPS
Create a project folder, separate configuration from persistent data and keep sensitive variables out of version control. Execute these steps as a sudo user.
We'll use a .env file for Grafana's initial password. Adjust names and paths according to your VPS policies.
- Structure separates versionable configs from persistent volumes.
- Use a password manager to store credentials.
- Avoid weak passwords; enable MFA in Grafana after the first login.
# Crie a estrutura de diretórios
test -d monitoring || mkdir -p monitoring/{prometheus/rules,alertmanager,grafana/provisioning/{datasources,dashboards}}
cd monitoring
# Defina variáveis sensíveis (não comitar este arquivo)
cat > .env << 'EOF'
GF_SECURITY_ADMIN_USER=admin
GF_SECURITY_ADMIN_PASSWORD=altere-esta-senha
EOF
# Opcional: restrinja permissões do .env
chmod 600 .env
Docker Compose: Prometheus, Grafana, Alertmanager, Node Exporter and cAdvisor
This docker-compose creates the network, defines persistent volumes and brings up all services. It enables Prometheus reload and Grafana provisioning.
Replace image tags according to your policy. You can manage containers via UI with Portainer if you prefer.
Following these steps lets you reproducibly set up Prometheus and Grafana with Docker Compose on a VPS.
- Persistent volumes prevent data loss across recreations.
- Pinned image tags reduce surprises from updates.
- Grafana's password is read from .env so it doesn't leak into the repository.
version: "3.8"
networks:
monitoring:
driver: bridge
volumes:
prometheus_data:
grafana_data:
alertmanager_data:
services:
prometheus:
image: prom/prometheus:v2.53.0
container_name: prometheus
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.enable-lifecycle
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus_data:/prometheus
networks: [monitoring]
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: alertmanager
restart: unless-stopped
command: ["--config.file=/etc/alertmanager/alertmanager.yml"]
ports:
- "9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager_data:/alertmanager
networks: [monitoring]
node-exporter:
image: prom/node-exporter:v1.7.0
container_name: node-exporter
restart: unless-stopped
pid: host
command: ["--path.rootfs=/host"]
volumes:
- /:/host:ro,rslave
ports:
- "9100:9100"
networks: [monitoring]
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.2
container_name: cadvisor
restart: unless-stopped
privileged: true
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
networks: [monitoring]
grafana:
image: grafana/grafana:10.4.3
container_name: grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
- GF_USERS_ALLOW_SIGN_UP=false
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on: [prometheus]
networks: [monitoring]
Configure Prometheus: scrape_configs and rules
Prometheus will scrape itself, Node Exporter and cAdvisor. We also point to Alertmanager and load alert rules from the rules directory.
Adjust scrape_interval according to metrics volume and disk size. Shorter intervals increase CPU and storage usage.
- Keep job names short and descriptive.
- Centralize rules in separate files for organization.
- We enable HTTP reload to apply changes without restarting.
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["prometheus:9090"]
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
- job_name: "cadvisor"
static_configs:
- targets: ["cadvisor:8080"]
Alertmanager: routes and alert delivery
Define how alerts are grouped, rate‑limited and delivered (email, webhook, chat). Start simple with SMTP. Replace placeholder values with valid credentials.
Test alert delivery with a deliberately sensitive rule, then tune thresholds to reduce noise.
- Group by alertname and instance to avoid storms.
- Adjust group_wait and repeat_interval according to criticality.
- Use app passwords for SMTP when possible.
route:
receiver: "email-default"
group_by: ["alertname", "instance"]
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receivers:
- name: "email-default"
email_configs:
- to: "[email protected]"
from: "[email protected]"
smarthost: "smtp.seu-dominio.com:587"
auth_username: "[email protected]"
auth_password: "SENHA-OU-TOKEN"
require_tls: true
# Você pode adicionar outros receivers (webhook, chat) conforme necessidade.
Useful alerting rules to get started
Create a rules file with availability and saturation conditions. Avoid noisy alerts at first; refine after observing your environment.
The examples below cover instance down, high CPU, low memory and near‑full disk. Adjust thresholds to fit your reality.
- Use labels for severity and the responsible team.
- Add annotations with clear next steps.
- Test expressions locally in Prometheus's expression browser.
groups:
- name: infra-basics
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Instância fora do ar"
description: "{{ $labels.instance }} não responde a scrape."
- alert: NodeHighCpu
expr: (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "CPU alta no host"
description: "Uso de CPU acima de 80% por 5m em {{ $labels.instance }}."
- alert: NodeLowMemory
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) < 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Memória disponível baixa"
description: "Menos de 10% de memória livre em {{ $labels.instance }}."
- alert: NodeDiskAlmostFull
expr: (node_filesystem_avail_bytes{fstype=~"ext4|xfs",mountpoint!~"/(run|var/lib/docker.*)"} / node_filesystem_size_bytes{fstype=~"ext4|xfs",mountpoint!~"/(run|var/lib/docker.*)"}) < 0.1
for: 15m
labels:
severity: critical
annotations:
summary: "Disco quase cheio"
description: "Menos de 10% livre em {{ $labels.instance }} filesystem."
Provision Grafana: datasource and first dashboards
Provision the Prometheus datasource to avoid manual setup. Then log in to Grafana and import dashboards from the Import menu.
For containers, create panels for CPU, memory and restarts per service. For the host, add load, I/O and filesystem graphs. Start small and iterate.
- A default datasource prevents URL mistakes.
- Organize dashboards by domain: Infra, Docker, App.
- Use dashboard variables to filter by instance.
# grafana/provisioning/datasources/datasource.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
Bring services up, check metrics and validate alerts
Now bring everything up and validate. These commands change host services; run them with care.
Open the local UIs to check: Prometheus on :9090, Alertmanager on :9093, Grafana on :3000. Test config reload and verify that rules are loaded.
With this you complete the end‑to‑end setup of Prometheus and Grafana with Docker Compose on a VPS and validate the monitoring flow.
- Use docker compose logs to troubleshoot startup errors.
- In Prometheus check Status > Targets and Status > Rules.
- Trigger a temporary alert to validate delivery (e.g., lower thresholds).
# Subir em background
docker compose up -d
# Verificar status
docker compose ps
# Listar alvos no Prometheus (espera-se 200 OK)
curl -sS http://localhost:9090/targets | head -n 20
# Checar Alertmanager
curl -sS http://localhost:9093 | head -n 5
# Recarregar configuração do Prometheus após editar arquivos
echo "Reload Prometheus" && curl -X POST http://localhost:9090/-/reload
# Logs de um serviço específico
docker compose logs -f prometheus
Hardening, backups and next steps
To expose Grafana safely, put it behind a reverse proxy with TLS and authentication. The Let's Encrypt guide helps issue valid certificates.
Restrict external ports with a firewall; only open what's necessary. Back up volumes and your versioned configuration files regularly.
Once stable, consider separating metrics by environment and tuning retention according to available disk.
- Protect Grafana behind a proxy and TLS: see Get free SSL with Let's Encrypt.
- Apply UFW rules: see How to set up a firewall on Ubuntu with UFW.
- Snapshot/backup prometheus_data and grafana_data volumes.
- Define a retention window that matches your disk budget.
- Implement remote write if you need centralized metrics.
Frequently asked questions
What VPS size should I use for Prometheus and Grafana?
It depends on scrape_interval, number of targets and retention. For a few hosts and 15s intervals, modest CPU and memory are usually enough. Monitor host CPU, memory and I/O and scale vertically. As data grows, increase disk first, then CPU/RAM.
How do I persist data across container recreations?
Use named Docker volumes in Docker Compose for Prometheus, Grafana and Alertmanager. They live outside container lifecycle. Back up volumes and configuration files (.yml) regularly. Before major changes, create a VPS disk snapshot.
How do I secure Grafana in production?
Place Grafana behind a reverse proxy with TLS, restrict access via firewall, disable public sign-up, enable MFA and use strong passwords. If exposing to the internet, use valid certificates and keep Grafana updated. Review folder permissions and team access regularly.
Can I monitor multiple VPS instances with this setup?
Yes. Install Node Exporter on each VPS and add their IPs/ports to Prometheus's scrape_configs. For public exposure use firewall rules, TLS/ACLs or a secure tunnel. Exporters can also run as containers on each remote host.
Which notification channels can I use in Alertmanager?
Email via SMTP is the simplest starting point. You can add webhooks to integrate with chat or automation. Define routes by severity and service, group alerts to reduce noise and tune timeouts (group_wait, repeat_interval) based on criticality.
How do I update images without losing data?
Update tags in docker-compose.yml, back up volumes and run docker compose pull && docker compose up -d. Data persists in volumes. Validate updates in staging when possible and monitor logs and metrics after the upgrade.
Need this stack adapted to your environment?
If you want help reviewing rules, sizing retention or integrating alerts into your workflow, contact me to unlock the next step in your monitoring.
Talk about monitoringSee also
- Pre-deploy checklist and quick rollback plan for PHP applications in production
- Security checklist for CI/CD pipelines with self‑hosted runners and secrets management
- Guide: Centralize logs with Fluentd and Elasticsearch for PHP apps on a VPS
- Guide: Organizing PostgreSQL Migrations and Rollbacks for Small Teams