← All articles

How to Monitor GitHub Actions with Prometheus and Grafana

By

GitHub Actions monitoring with Prometheus starts with an exporter that receives workflow events or polls the GitHub API. Prometheus stores the metrics, and Grafana turns them into dashboards for execution duration and outcomes.

The exporter, Prometheus, and Grafana OSS are free to use without a subscription. You provide the infrastructure and maintain the collection pipeline. If you already operate Prometheus, that can be a reasonable trade-off.

This guide compares open-source exporters, walks through a webhook-based setup, and explains when a managed tool such as Pipetrics reduces the work involved.

Which GitHub Actions Prometheus exporter should you choose?

Several projects use similar names but collect different data. A polling exporter periodically requests GitHub API data. A webhook exporter receives events as workflows execute and needs an HTTPS endpoint reachable by GitHub.

Exporter on GitHubGitHub starsCollection approachWhat to evaluate
Labbs/github-actions-exporter256GitHub API pollingWorkflow status and duration gauges, runner status, token and GitHub App authentication. Its latest published release when checked was v1.9.0 from November 2023; review compatibility with current GitHub APIs.
cpanato/github_actions_exporter79Webhooks for execution metrics; optional API polling for billingWorkflow and job metrics, duration histograms, and separate webhook and metrics ports. This tutorial uses v0.11.0, released in October 2026.
kaidotdev/github-actions-exporter7GitHub API collectionCounts of runners and runs by status. Check whether aggregate counts answer your questions about individual workflows.
gravitational/gha-exporter6GitHub API polling with GitHub App authenticationWorkflow, job, and step timings plus a configurable initial window and optional backfill.

The right choice depends on the questions you need to answer and the infrastructure you can operate. Use the following comparison to narrow the options. We use cpanato for the worked example because its workflow and job metrics support the dashboard below and it has a recent release; the example is not a ranking of every use case.

Compare collection, permissions, and historical coverage

Decision pointLabbscpanatogravitationalkaidotdev
Data you can inspectPer-run workflow status and duration gauges; runner statusWorkflow and job event counters and duration histogramsWorkflow, job, and step timingsAggregate run and runner counts by status
How data arrivesPeriodic GitHub API requestsIncoming workflow webhooks; API polling is optional for billingPeriodic GitHub API requestsGitHub data collection; the README focuses on exposed aggregate gauges
Credentials and setupPAT or GitHub App; review permissions for the enabled collectorsShared webhook secret for execution metrics; separate credentials for optional billingGitHub App with Actions read permission, installed in the repository owner's organizationReview the repository manifests and implementation; the README does not provide a detailed authentication guide
Public webhook endpointNot required by its documented polling approachRequired for the execution metrics in this guideNot required by its documented polling approachNot described in the README's setup instructions
Historical collectionv1.9.0 source requests runs created within the previous 12 hours; this is not a full archiveThe webhook collection path does not automatically import historical runsConfigurable initial lookback; optional backfill of completed runs in that windowHistorical import behavior is not documented in the README
Metric interpretation to checkStatus codes and gauge lifetime; per-run labels can increase cardinalityEvent duplication, counter resets, and the timestamp meaning of duration metricsWindow size, completed-run recovery, and how backfilled observations enter PrometheusAggregate gauges show status counts; they do not provide an execution ledger

Choose an exporter by the job you need it to do

  • Consider Labbs for polling-based workflow and runner visibility. It has the largest star count in this shortlist and documents several authentication options. Check its older release against current GitHub endpoints, and validate the status gauges before building failure alerts.
  • Consider cpanato for event-based dashboards. It suits teams that can expose a signed HTTPS webhook and want workflow and job duration histograms. Account for duplicate deliveries and in-memory state.
  • Evaluate gravitational when configurable lookback and recovery matter. Its documented backfill option can help collect recently completed runs after downtime. Confirm that the chosen window covers your longest workflows and expected interruptions.
  • Consider kaidotdev for a small aggregate status dashboard. Its README exposes run and runner counts by status and a Kubernetes installation path. Inspect the implementation if you need detailed authentication, history, or per-workflow behavior.

Before committing to a project, compare a small set of its metrics with GitHub's own run records. Test a restart, a missed collection interval, and a rerun. Choose the exporter whose behavior matches your requirements, even when another repository has more stars.

How the monitoring pipeline works

GitHub sends workflow events to the exporter's HTTPS webhook endpoint. The exporter updates in-memory counters and histograms. Prometheus scrapes its private metrics endpoint, and Grafana queries Prometheus.

The exporter runs outside GitHub Actions. You do not need to add a monitoring step to every workflow, but you do need a continuously available service. Starting it only inside a CI job would leave gaps whenever that job was absent.

The v0.11.0 documentation separates the webhook listener on port 8065 from the metrics listener on port 9101. GitHub needs access to the webhook; Prometheus needs access to the metrics.

Set up the exporter, Prometheus, and Grafana

1. Prepare a host and webhook address

You need Docker with Compose, permission to create a repository webhook, and a host that can stay online. Point a public DNS name such as ci-metrics.example.com at that host. Allow inbound TCP ports 80 and 443 for Caddy's automatic HTTPS. Use a host where those ports are available.

Replace the example domain with one you control. If you already have an HTTPS reverse proxy, you can use it instead of the Caddy service below.

Run these commands in Bash to generate a webhook secret and choose a Grafana administrator password:

export EXPORTER_DOMAIN=ci-metrics.example.comexport GITHUB_WEBHOOK_TOKEN="$(openssl rand -hex 32)"read -r -s -p "Grafana admin password: " GRAFANA_ADMIN_PASSWORDprintf '\n'export GRAFANA_ADMIN_PASSWORD

Keep the webhook secret in your password manager or secret store; you will enter the same value in GitHub. Do not commit credentials to Git. Retain the environment values securely so that later Compose commands can use them.

2. Create the Compose configuration

Save the following as compose.yml:

services:  exporter:    image: ghcr.io/cpanato/github_actions_exporter:v0.11.0    restart: unless-stopped    environment:      GITHUB_WEBHOOK_TOKEN: ${GITHUB_WEBHOOK_TOKEN:?Set GITHUB_WEBHOOK_TOKEN}    ports:      - "127.0.0.1:9101:9101"  prometheus:    image: prom/prometheus:latest    restart: unless-stopped    command:      - --config.file=/etc/prometheus/prometheus.yml      - --storage.tsdb.path=/prometheus      - --storage.tsdb.retention.time=30d    volumes:      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro      - prometheus_data:/prometheus    ports:      - "127.0.0.1:9090:9090"  grafana:    image: grafana/grafana:latest    restart: unless-stopped    environment:      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Set GRAFANA_ADMIN_PASSWORD}    volumes:      - grafana_data:/var/lib/grafana    ports:      - "127.0.0.1:3000:3000"  caddy:    image: caddy:2    restart: unless-stopped    environment:      EXPORTER_DOMAIN: ${EXPORTER_DOMAIN:?Set EXPORTER_DOMAIN}    ports:      - "80:80"      - "443:443"    volumes:      - ./Caddyfile:/etc/caddy/Caddyfile:ro      - caddy_data:/data      - caddy_config:/configvolumes:  prometheus_data:  grafana_data:  caddy_data:  caddy_config:

Prometheus, Grafana, and Caddy use floating image tags in this introductory example. Before a long-lived deployment, select current supported releases, review security updates, and pin versions or image digests.

The named Prometheus volume preserves collected data when the container is recreated. The 30-day retention setting is a storage policy; it does not import 30 days of previous GitHub runs.

This setup collects execution events without a GitHub API token. The exporter's optional billing collection has separate credential and permission requirements and is outside this example.

3. Configure scraping and HTTPS routing

Create prometheus.yml in the same directory:

global:  scrape_interval: 30sscrape_configs:  - job_name: github-actions    static_configs:      - targets: ["exporter:9101"]

Create Caddyfile:

{$EXPORTER_DOMAIN} {    handle /gh_event {        reverse_proxy exporter:8065    }    handle {        respond "Not found" 404    }}

Only the webhook path is routed publicly. The metrics endpoint and monitoring interfaces are bound to the host's loopback address. The exporter verifies the webhook signature using your shared secret.

Start the services:

docker compose config --quietdocker compose up -ddocker compose logs --tail=50 exporter caddy

For a remote host, run an SSH tunnel from your computer to access the monitoring interfaces:

ssh -L 3000:127.0.0.1:3000 -L 9090:127.0.0.1:9090 user@your-host

4. Add the GitHub webhook

In your repository, open Settings → Webhooks → Add webhook and configure:

SettingValue
Payload URLhttps://YOUR-DOMAIN/gh_event
Content typeapplication/json
SecretThe value of GITHUB_WEBHOOK_TOKEN
SSL verificationEnabled
Selected eventsWorkflow jobs and Workflow runs
ActiveEnabled

Trigger a workflow, then inspect the webhook's recent deliveries and the exporter logs. Confirm successful delivery of workflow events, not just the initial webhook ping. Avoid configuring overlapping repository and organization webhooks for the same events.

On the Docker host, inspect the emitted metrics:

curl -fsS http://127.0.0.1:9101/metrics

In Prometheus at http://localhost:9090, verify that the github-actions scrape target is up. Workflow-specific series may appear only after relevant events arrive.

Build a GitHub Actions Grafana dashboard

Open Grafana at http://localhost:3000 and sign in as admin with your chosen password. Add a Prometheus data source and set its server URL to http://prometheus:9090.

Use the Compose service name here. Inside Grafana's container, localhost refers to Grafana itself.

Create a dashboard, add a visualization for each query below, and select that data source. These queries target cpanato v0.11.0; exporters with similar names may expose different metrics.

Exporter scrape availability

Use a Stat panel:

up{job="github-actions"}

A value of 1 means the scrape succeeded. It does not prove that GitHub is delivering events. A reachable exporter can still have a broken webhook configuration.

Completed workflow events by outcome

Use a Time series panel:

sum by (org, repo, workflow_name, conclusion) (  increase(workflow_status_count{    job="github-actions",    status="completed"  }[1h]))

This shows the estimated increase in completed-event counters over a rolling hour, split by outcome. Filtering on status="completed" avoids counting requested and in-progress events as additional completions.

Treat the result as monitoring telemetry. Prometheus extrapolation can produce fractional values. The exporter does not deduplicate deliveries, so redelivery can inflate counters. An exact execution ledger needs event identity and deduplication.

Mean workflow duration

Use a Time series panel with seconds as the unit:

sum by (org, repo, workflow_name) (  rate(workflow_execution_time_seconds_sum{    job="github-actions"  }[1h]))/sum by (org, repo, workflow_name) (  rate(workflow_execution_time_seconds_count{    job="github-actions"  }[1h]))

The histogram's sum divided by its count gives the mean of observed durations in the window. In this version, the exporter calculates each workflow observation as updated_at minus run_started_at on a completed event. That is the timestamp interval represented by the chart, rather than a sum of job execution time.

Quiet repositories may show gaps or an undefined result when there are no observations. Increase the window for infrequent workflows.

Mean job execution duration by runner group

Use another Time series panel with a seconds unit:

sum by (org, runner_group) (  rate(workflow_job_duration_seconds_sum{    job="github-actions",    state="in_progress"  }[1h]))/sum by (org, runner_group) (  rate(workflow_job_duration_seconds_count{    job="github-actions",    state="in_progress"  }[1h]))

Despite the in_progress label, this duration is recorded on job completion using completed_at minus started_at. Use the panel to compare observed job durations across runner groups.

Check what queue-time metrics actually measure

Be careful with the separate state="queued" histogram. The README describes a queue-time query, but the v0.11.0 implementation uses the first step's started_at minus the job's started_at, and only for an in-progress payload containing one step. It does not establish the full time a job waited for a runner. If runner queue delay is your main question, choose a collector that records the required queue-entry and execution-start timestamps.

Save the dashboard. Before adding alerts, compare observations with the corresponding GitHub runs and confirm that your selected windows suit your workflow frequency.

What happens to history when collection stops?

There are two separate questions: which GitHub events the exporter collected, and how long Prometheus retains the samples it scraped.

For this webhook-based setup, old workflow runs are not automatically replayed when the exporter starts. If the webhook endpoint is unavailable, investigate failed deliveries and recovery options. Do not assume missed events will be recovered automatically.

The exporter keeps metric state in memory. A restart resets its counters and histogram observations. Prometheus can handle visible counter resets in functions such as rate(), but it cannot reconstruct events that occurred and disappeared between scrapes. A restart policy helps availability; it does not make collection lossless.

Backfill differs across exporters. For example, gravitational/gha-exporter documents --initial-window and --backfill for collecting completed runs from a configurable lookback window. Before choosing an exporter, check which runs it can recover after downtime and how far back it can look. Ordinary scraping also does not automatically recreate samples at their original event timestamps.

Longer retention helps you analyze collected data for longer. It cannot repair data that was never collected.

Is Prometheus monitoring really free?

The open-source software in this example is free to use without a monitoring subscription. GitHub Actions usage still follows your GitHub billing terms.

Your operating costs depend on hosting, storage, backups, and maintenance time. An existing observability platform may make the extra infrastructure cost small. A new dedicated deployment has a different cost profile.

You also own secret rotation, webhook delivery checks, upgrades, and alert configuration. Branch and workflow labels can increase time-series cardinality as usage grows. Monitor that growth before increasing retention or adding dimensions.

Prometheus and Grafana are useful when your team wants control over its monitoring stack. They require more ongoing work when the primary goal is to open a CI dashboard and investigate a slowdown.

Prometheus and Grafana vs. Pipetrics

Both approaches offer a free starting point. The main difference is who operates collection and prepares the analysis.

Compare the collection architectures

Pipetrics connects through the Pipetrics GitHub App. Each organization has a dedicated processing service, keeping its data processing separate from other organizations. Workflow history is collected separately from incoming webhook events.

You can optionally connect your existing Grafana to that service through a dedicated, read-only PostgreSQL connection. Your team still operates Grafana, but does not need a GitHub Actions Prometheus exporter for this integration. See the Pipetrics Grafana setup guide for connection details, credentials, and TLS configuration.

For either approach, historical import coverage and recovery of missed events depend on collection behavior. Retention alone does not establish either.

Compare features and operating responsibilities

CapabilitySelf-hosted exporter + Prometheus + Grafana OSSPipetrics FreePipetrics Pro
Software or subscription priceFree open-source software; infrastructure and maintenance are your responsibility$0$10 per repository per month
Data retentionYou configure and fund storage; this example uses 30 days14 days360 days
Collection infrastructureYou host the exporter and PrometheusManaged by PipetricsManaged by Pipetrics
CI dashboardsYou create panels or adapt compatible community dashboardsReady-made Pipetrics dashboardsReady-made Pipetrics dashboards
Insights and investigationYou build queries and interpret the available metricsBuilt-in CI analysisBuilt-in CI analysis with longer retained history
Cost analysisOptional exporter billing metrics need separate setup; execution duration alone is insufficientCost Profiler for the current monthCost Profiler with full retained history
Slack notificationsConfigure a supported Grafana or Alertmanager notification routeNot included in the Free planIncluded
Existing Grafana integrationPrometheus is a native Grafana data sourceNot included in the Free planDedicated PostgreSQL connection to the organization's analytics data
MaintenanceYour team manages uptime, upgrades, storage, and delivery checksPipetrics operates its collection servicePipetrics operates its collection service

The Pipetrics GitHub App uses read-only Actions and supporting metadata permissions. Its integration documentation explains the permission set; application source-code access is not required. Ready-made analysis helps investigate workflow performance and modeled costs without maintaining exporter queries. Interpret modeled cost estimates within their documented scope and compare them with GitHub billing where needed.

Which approach fits your team?

Choose the exporter route if you already run Prometheus and Grafana, want customizable metrics, and can maintain a reliable webhook or polling service. It provides free GitHub Actions monitoring software with infrastructure under your control.

Choose Pipetrics if you want managed collection and ready-made CI analysis. You can start with the Free plan, keep 14 days of data, and consider Pro when you need 360-day retention, Slack notifications, or Grafana integration.

Whichever approach you choose, verify collection before trusting a chart. A dashboard is useful only when you understand what its metrics include and where gaps can occur.