Prometheus Prometheus

Monitor a Host and Its Containers with node_exporter and cAdvisor

Run Prometheus, node_exporter, and cAdvisor together, scrape both, and confirm the targets are healthy.

14 min Beginner

Host-level metrics (CPU, memory, disk, network) and per-container metrics come from two different exporters. This guide runs Prometheus alongside both with Docker Compose, configures Prometheus to scrape them, and confirms it’s working before handing the data to a dashboard in the next guide.

The three pieces

  • Prometheus itself: scrapes targets on a schedule and stores the results.
  • node_exporter: exposes the host operating system’s own metrics, CPU, memory, disk, network, at /metrics on port 9100.
  • cAdvisor (Container Advisor, maintained by Google): exposes per-container resource usage, CPU, memory, I/O, at /metrics on port 8080.

Both exporters need visibility into things a normal container can’t see: node_exporter needs the host’s root filesystem and process namespace to report accurately on the host, not just its own container, and cAdvisor needs access to the container runtime’s own data to report on every container running on the host, not just itself.

Docker Compose for all three

version: "3"

volumes:
  prometheus-data:

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.retention.time=15d"

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    pid: host
    volumes:
      - /:/host:ro,rslave
    command:
      - "--path.rootfs=/host"

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

node-exporter’s pid: host and the read-only root mount are what let it report on the host rather than on its own isolated container view; --path.rootfs=/host tells it where that mounted root actually landed inside the container. cadvisor needs its own set of read-only mounts into the host’s runtime state for the same reason: without them, it can only see itself, not the other containers on the host. Nothing here needs write access to the host, every mount is read-only, which is worth keeping in mind if a security review asks what these containers can actually do.

Configure Prometheus to scrape both

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "cadvisor"
    static_configs:
      - targets: ["cadvisor:8080"]

global.scrape_interval isn’t Prometheus’s built-in default (that’s one minute); setting it explicitly to 15 seconds, as most getting-started examples do, is a convention worth following since it’s frequent enough to give responsive dashboards without generating excessive scrape volume. Each job_name is an arbitrary label you choose, attached to every series that job scrapes, which is how you’d later distinguish “this metric came from node-exporter” from “this metric came from cadvisor” in a query. Targets are addressed by their Compose service name (node-exporter, cadvisor) because containers on the same Compose network resolve each other by service name automatically; there’s no need for container IP addresses or localhost here.

Bring it up and confirm the targets are healthy

docker compose up -d

Open http://localhost:9090/targets in a browser. Each configured job should show as UP. If one doesn’t, check that container’s own logs first, and confirm you can reach its /metrics endpoint directly (curl http://localhost:9100/metrics from the host, or docker exec into the Prometheus container and curl the target’s Compose service name) before assuming the problem is in Prometheus’s configuration rather than the target itself.

From Prometheus’s own Graph page, try a quick sanity check:

up{job="node-exporter"}

up is a synthetic metric Prometheus generates for every target itself, 1 if the last scrape succeeded, 0 if it didn’t, which makes it a fast way to check target health directly through PromQL rather than the Targets page.

A note before you go further

Neither Prometheus’s UI nor cAdvisor’s ships with authentication or TLS by default. That’s a reasonable default for something meant to be scraped and queried from inside a trusted network, but it means these ports shouldn’t be exposed directly to the public internet without adding access control, whether that’s a reverse proxy with authentication in front of them, or simply keeping them reachable only from an internal network in the first place.

With both exporters reporting into Prometheus, the next guide covers turning this into an actual dashboard with Grafana.

Popular Guides