To monitor Docker containers with Prometheus, cAdvisor and Grafana you run three containers: cAdvisor reads container and host metrics from the kernel, Prometheus scrapes and stores them, and Grafana draws the graphs. This guide builds the whole stack with Docker Compose, wires up the scrape config and imports a dashboard you can read at a glance.
The result is per-container CPU, memory, network and filesystem usage with history, so you can tell whether a container is leaking memory or quietly eating disk before it takes the server down.
Before you start
You need:
- A Linux VPS running Ubuntu 22.04/24.04, Debian 12 or AlmaLinux/Rocky 8–9, with root or
sudo. - Docker Engine and the Compose plugin installed. If not, follow Install Docker and Docker Compose on a Linux VPS.
- Roughly 1–2 GB of free RAM and a few GB of disk for the Prometheus time-series database. The stack itself is small; the database grows with retention.
- Comfort editing YAML files.
Warning: cAdvisor runs with privileged: true and mounts parts of the host filesystem read-only. That is how it reads cgroup and disk statistics. Only run container images you trust, and keep the monitoring ports off the public internet.
Monitor Docker containers with Prometheus and cAdvisor
Create the project directory and the two subfolders you will mount into the containers:
sudo mkdir -p /opt/monitoring/prometheus
cd /opt/monitoringCreate /opt/monitoring/docker-compose.yml:
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
privileged: true
devices:
- /dev/kmsg
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
ports:
- "127.0.0.1:8080:8080"
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.path=/prometheus
- --storage.tsdb.retention.time=15d
ports:
- "127.0.0.1:9090:9090"
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_PASSWORD: change-me-now
volumes:
- grafana-data:/var/lib/grafana
ports:
- "127.0.0.1:3000:3000"
volumes:
prometheus-data:
grafana-data:A few things worth knowing:
- The
127.0.0.1:prefix on each port mapping means the services are only reachable from the server itself. You will tunnel in over SSH or put a reverse proxy in front later. /var/lib/dockermounted read-only is what lets cAdvisor map container IDs to names and images.--storage.tsdb.retention.time=15dkeeps 15 days of metrics. Raise or lower it to match your disk.- The
latesttag is convenient but not reproducible. Once the stack works, pin the exact tags you tested and check the image pages on Docker Hub and the cAdvisor registry for current releases.
Start the stack:
docker compose up -d
docker compose psAll three containers should report running.
Configure Prometheus to scrape cAdvisor
Prometheus reads its targets from /opt/monitoring/prometheus/prometheus.yml. Create it:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: cadvisor
static_configs:
- targets: ["cadvisor:8080"]The target is cadvisor:8080, not localhost:8080. Inside the Compose network, containers reach each other by service name. Prometheus is scraping from inside its own container, so localhost would point at Prometheus itself.
Apply the config without a full restart:
docker compose exec prometheus \
wget -qO- --post-data='' http://localhost:9090/-/reloadIf that returns an error, the --web.enable-lifecycle flag is missing; add it to the Prometheus command list and run docker compose up -d prometheus instead.
Add Grafana and import a dashboard
Open Grafana through an SSH tunnel from your laptop:
ssh -L 3000:127.0.0.1:3000 root@203.0.113.10Then browse to http://localhost:3000 and log in as admin with the password you set in GF_SECURITY_ADMIN_PASSWORD. Change it immediately if you left the placeholder in place.
Now connect the data source:
- Go to Connections → Data sources → Add data source.
- Choose Prometheus.
- Set the connection URL to
http://prometheus:9090— again, the Compose service name. - Click Save & test. You should see "Successfully queried the Prometheus API".
Import a dashboard:
- Go to Dashboards → New → Import.
- Enter dashboard ID
193(the long-standing cAdvisor / Docker monitoring dashboard) and click Load. - Pick your Prometheus data source and click Import.
Dashboard IDs and their queries change over time. If a panel shows no data, search grafana.com/grafana/dashboards for a current cAdvisor dashboard and check which metric names it expects.
To sanity-check the data yourself, open Explore and run:
rate(container_cpu_usage_seconds_total{name!=""}[5m])You should get one series per running container.
Check it works
Work through these on the server:
curl -s http://127.0.0.1:8080/metrics | head -n 20
curl -s http://127.0.0.1:9090/api/v1/targets | grep -o '"health":"[a-z]*"'The first command should print cAdvisor's metric list. The second should show "health":"up" for both the prometheus and cadvisor jobs.
Then generate load so you have something to look at:
docker run --rm -d --name loadtest alpine \
sh -c 'while true; do :; done'Within a minute the Grafana dashboard should show a CPU spike attributed to loadtest. Remove it when you are done:
docker rm -f loadtestTroubleshooting
- cAdvisor restarts in a loop with "failed to start container manager" — it cannot read the host cgroups. Confirm
privileged: trueand the/sys:/sys:romount are present. On some hosts/dev/kmsgdoes not exist; remove thatdevicesentry and recreate the container. - The
cadvisortarget showsdownin Prometheus — the scrape URL is wrong. Usecadvisor:8080inside Compose. If Prometheus runs outside Docker, use the host IP and make sure port 8080 is published on an address Prometheus can reach. - Grafana panels say "No data" — usually the time range or the data source. Check the query in Explore first; if it returns series there, the dashboard is pointing at a different data source or expects metric names your cAdvisor version does not emit.
- Containers appear without names, or most are missing — cAdvisor reads
/var/lib/docker. If Docker uses the containerd snapshotter or runs rootless, that path differs. Check your Docker storage driver and adjust the mount. - Grafana redirects to the wrong host or loops on login — it is behind a reverse proxy without knowing its public URL. Set
GF_SERVER_ROOT_URLandGF_SERVER_DOMAINin the Grafana environment block.
Next steps
- Make sure your services come back after a reboot with Docker restart policies.
- Add liveness checks so a broken container is restarted automatically: Add health checks to Docker containers.
- Keep the host itself visible too — see Monitor CPU, memory, disk and logs on a Linux server.
- Stop logs from filling the disk with View and rotate Docker container logs.
