Skip to content

Prometheus & Grafana ​

docker-exporter speaks cAdvisor's metric language, so if you already run Prometheus and Grafana you can wire it in without rebuilding dashboards.

Prometheus scrape config ​

yaml
scrape_configs:
  - job_name: docker
    static_configs:
      - targets: ["docker-exporter:9713"]

A 15 s scrape interval is fine for container metrics (Prometheus's own built-in default is actually 1 m). Avoid going below 5 s — each scrape issues a stats and an inspect call per container, so very short intervals mostly add load on the Docker daemon for little practical gain.

Grafana dashboards and PromQL queries ​

docker-exporter uses cAdvisor-compatible metric names (container_cpu_usage_seconds_total, container_memory_working_set_bytes, container_network_receive_bytes_total, …). Most existing cAdvisor/Docker Grafana dashboards work against it with little or no query changes — name-keyed panels are drop-in, while panels keyed on cAdvisor-only labels (the per-core cpu label, or a cgroup-path id) may need minor edits. Health is the exception: docker-exporter publishes container_health_status with a status label, where cAdvisor has container_health_state, so health panels need their query rewritten.

A few queries to start from:

promql
# Working set per container (what docker stats shows)
container_memory_working_set_bytes

# CPU cores used per container (rate of the cumulative counter)
rate(container_cpu_usage_seconds_total[5m])

# Network receive throughput
rate(container_network_receive_bytes_total[5m])

# Containers that stopped but shouldn't have (ignores one-shots)
container_state{restart_policy!="no"} == 0

# One-shot jobs (migrations, batch) that ran and failed
container_exit_code{restart_policy="no"} != 0

Official dashboard

A ready-made Grafana dashboard is on the roadmap and will be published on grafana.com. Until then, point any cAdvisor-based dashboard at your Prometheus data source — the metric names line up.

Alerting example ​

Four starter rules: alert when docker-exporter can't reach the Docker daemon, when a container that shouldn't stop does, when a one-shot job exits non-zero, and when a container keeps restarting. The third is the complement of the second — ContainerStopped has to ignore one-shots (they are supposed to sit Exited), which leaves their exit code as the only thing that can tell you the job failed.

yaml
groups:
  - name: docker-exporter
    rules:
      - alert: DockerExporterDown
        expr: docker_exporter_up == 0
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "docker-exporter cannot reach the Docker daemon"

      - alert: ContainerStopped
        expr: container_state{restart_policy!="no"} == 0
        for: 10m
        labels: { severity: warning }
        annotations:
          summary: "Container {{ $labels.name }} is not running"

      - alert: ContainerOneShotFailed
        expr: container_exit_code{restart_policy="no"} != 0
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "One-shot {{ $labels.name }} exited {{ $value }}"

      - alert: ContainerRestarting
        expr: changes(container_start_time_seconds[1h]) > 3
        labels: { severity: warning }
        annotations:
          summary: "Container {{ $labels.name }} restarted {{ $value }} times in the last hour"

container_start_time_seconds moves on every start, a manual docker restart included, so changes(container_start_time_seconds[1h]) > 0 catches a single restart.

Why memory reads zero on a Pi → · docker-exporter vs cAdvisor → · Metrics reference →