DORA Metrics Command
Measure, benchmark, and instrument DORA metrics for production engineering teams.
Mode: instrument
Add DORA event emission to a GitHub Actions workflow.
Steps:
-
Identify which events to capture:
- Deploy event: triggered on successful deployment to a target environment
- Incident open event: triggered by PagerDuty/OpsGenie webhook when an incident is created
- Incident close event: triggered when the incident is resolved
-
Detect: does a Prometheus Pushgateway exist in the stack? If not, it must be deployed before instrumentation can work:
kubectl get svc -A | grep pushgatewayIf absent, deploy via Helm before proceeding:
helm upgrade --install prometheus-pushgateway prometheus-community/prometheus-pushgateway \--namespace monitoring \--create-namespace -
Generate GitHub Actions steps for each event type:
- Deploy event: push
dora_deployment_timestampanddora_lead_time_secondsto Pushgateway - Incident triggered (PagerDuty/OpsGenie webhook): push
dora_incident_start_timestamp - Incident resolved: push
dora_incident_duration_secondsanddora_incident_caused_by_deploy
- Deploy event: push
-
Output: exact YAML to append to the existing workflow, using the Pushgateway job name convention
job/dora/instance/<repo-owner_repo-name>. Sanitizeowner/repo→owner_repoto avoid breaking Pushgateway path segments.Example deploy event step:
- name: Push DORA deploy metricsif: success()env:PUSHGATEWAY_URL: ${{ secrets.PUSHGATEWAY_URL }}REPO: ${{ github.repository }}run: |DEPLOY_TS=$(date +%s)# Lead time from first commit in this batch — requires fetch-depth: 0 in checkout.FIRST_COMMIT_TS=$(git log --reverse --format="%ct" origin/main..HEAD | head -1)FIRST_COMMIT_TS=${FIRST_COMMIT_TS:-$DEPLOY_TS}LEAD_TIME=$((DEPLOY_TS - FIRST_COMMIT_TS))INSTANCE="${REPO//\//_}"cat <<EOF | curl --data-binary @- "${PUSHGATEWAY_URL}/metrics/job/dora/instance/${INSTANCE}"# TYPE dora_deployment_timestamp gaugedora_deployment_timestamp{repo="${REPO}",env="production"} ${DEPLOY_TS}# TYPE dora_lead_time_seconds gaugedora_lead_time_seconds{repo="${REPO}",env="production"} ${LEAD_TIME}EOF -
Warn: Change Failure Rate requires incident source integration — a rate of 0% without incident data is a configuration gap, not a real metric. Never report 0% CFR without confirmed incident source connectivity.
Validation:
# Confirm Pushgateway received the metric
curl -s http://pushgateway:9091/metrics | grep dora_deployment_timestamp
# Confirm Prometheus scraped it (allow up to 1 scrape interval, default 15s)
curl -s 'http://prometheus:9090/api/v1/query?query=dora_deployment_timestamp' \
| jq '.data.result[0].value[1] // "not yet scraped — wait 15s and retry"'
Reference: references/dora.md → Open-source instrumentation pattern
Mode: dashboard
Generate a Grafana dashboard for all four DORA metrics.
Steps:
-
Confirm the four recording rules are deployed before building the dashboard:
dora:deployment_frequency:rate30ddora:lead_time_seconds:p50dora:change_failure_rate:ratio30ddora:mttr_seconds:p50
Verify with:
curl -s 'http://prometheus:9090/api/v1/query?query=dora:deployment_frequency:rate30d' | jq '.data.result[0].value[1] // "no data"'If any query returns no data, check the recording rule deployment — see
references/dora.mdfor the full rule set. -
Dashboard structure: four panels arranged in a 2×2 grid, one per DORA metric:
- Panel 1 — Deployment Frequency: stat + time-series, unit: deploys/day
- Panel 2 — Lead Time for Changes: stat + time-series, unit: hours (p50)
- Panel 3 — Change Failure Rate: stat + time-series, unit: percentage
- Panel 4 — MTTR: stat + time-series, unit: hours (p50)
Each panel includes DORA performance band overlays as threshold regions (Elite/High/Medium/Low). A time range selector provides 30/60/90 day views.
-
Import the complete dashboard JSON from
examples/dora/grafana-dashboard.json:# Import via Grafana APIcurl -s -X POST http://grafana:3000/api/dashboards/import \-H "Content-Type: application/json" \-d @examples/dora/grafana-dashboard.json -
Threshold values for each performance tier are embedded directly in the panel JSON as threshold bands. Update them in the panel
thresholdsfield if your organisation uses different band definitions.
Reference: references/dora.md → DORA performance bands
Mode: benchmark
Classify current metric values against DORA performance bands.
Steps:
-
Accept current metric values — either provided directly or queried from Prometheus:
# Query current valuescurl -s 'http://prometheus:9090/api/v1/query?query=dora:deployment_frequency:rate30d' | jq '.data.result[0].value[1] // "no data"'curl -s 'http://prometheus:9090/api/v1/query?query=dora:lead_time_seconds:p50' | jq '.data.result[0].value[1] // "no data"'curl -s 'http://prometheus:9090/api/v1/query?query=dora:change_failure_rate:ratio30d' | jq '.data.result[0].value[1] // "no data"'curl -s 'http://prometheus:9090/api/v1/query?query=dora:mttr_seconds:p50' | jq '.data.result[0].value[1] // "no data"' -
Map each metric to Elite / High / Medium / Low using the 2023 DORA performance bands:
Metric Elite High Medium Low Deployment Frequency Multiple/day Weekly–monthly Monthly–6 months Less than 6 months Lead Time for Changes < 1 hour 1 day – 1 week 1 week – 1 month > 1 month Change Failure Rate < 5% 5–10% 10–15% > 15% MTTR < 1 hour < 1 day 1 day – 1 week > 1 week -
Identify the weakest metric — the one furthest from Elite — as the highest-leverage improvement target.
-
Suggest the most impactful improvement for that specific metric:
- Low Deployment Frequency → reduce batch size, increase CI automation coverage
- High Lead Time → eliminate manual approval gates, parallelize test stages
- High Change Failure Rate → add pre-deploy smoke tests, feature flag risky changes
- High MTTR → improve alerting signal-to-noise, add runbook links to alerts
-
Output: tier table + one-sentence recommendation per metric.
Reference: references/dora.md → DORA performance bands
Mode: debug
Diagnose gaps in DORA metric data.
Steps:
-
Missing deployment events
- Check: does the workflow step run on the correct trigger (
on: pushto main/release branch)?grep -A5 '^on:' .github/workflows/*.yaml - Check: can the runner reach the Pushgateway?
curl http://pushgateway:9091/-/healthy
- Check: is the Pushgateway scrape target present in Prometheus?
curl -s http://prometheus:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job == "pushgateway")'
- Check: does the workflow step run on the correct trigger (
-
Missing MTTR (or stuck at 0)
- Check: is the PagerDuty/OpsGenie webhook configured to send to the incident listener?
- Check: does the webhook payload include both incident start and end timestamps?
- Check: is the GitHub Actions workflow triggered by
repository_dispatchevents for incident lifecycle?grep -r 'repository_dispatch' .github/workflows/
-
Change failure rate is exactly 0%
- Flag as anti-pattern: a CFR of 0% without confirmed incident source integration is a configuration gap, not a real measurement.
- Check: is any incident source (PagerDuty, OpsGenie, Jira) connected and sending events?
- Do not accept 0% CFR without evidence that the incident source is emitting resolved incident events.
-
Metrics stop at a specific date
- Check: Pushgateway retention — metrics expire after the configured push interval if not refreshed:
kubectl describe pod -n monitoring -l app=prometheus-pushgateway | grep -i retention
- Check: Prometheus scrape config still includes the Pushgateway target after any config reload.
- Check: was the Pushgateway restarted without persistence? Metrics in memory are lost on restart — enable persistence with
--persistence.file.
- Check: Pushgateway retention — metrics expire after the configured push interval if not refreshed:
Reference: references/dora.md → Anti-pattern detection
After completing this task, log errors and learnings via /platform-skills:self-improve log.