Build the mental model
DORA's four metrics come from years of research into what distinguishes high-performing delivery teams, and their value is that they measure outcomes rather than activity. Deployment frequency: how often you successfully release to production. Lead time for changes: how long from commit to running in production. Change failure rate: what share of deployments cause a degradation requiring remediation. Failed deployment recovery time: how long it takes to restore service when one does.
The first two describe throughput; the last two describe stability. The finding that surprises people is that these do not trade off against each other — teams that deploy more often generally fail less, because small changes are easier to review, verify and undo. Slowness is not caution.
Which is why any one of them, read alone, is gameable and often harmful. Optimise deployment frequency by itself and you get careless releases and a rising failure rate. Optimise change failure rate by itself and the safest strategy is to deploy almost never — a perfect score for a team that has stopped delivering. Lead time alone rewards merging quickly and ignores whether the change ever reached a user. Always read a speed metric next to a stability metric; a genuine improvement moves one without degrading the other.
Two practical warnings. Define your terms and keep them fixed — a deployment must mean the same event in January and in June, or your trend is measuring your definition rather than your team. And keep these numbers as team-level improvement signals, never as individual performance targets, because a metric used for evaluation will be met by whatever is cheapest to inflate.
Treat pipeline duration and flake rate as first-class alongside the four. They are the leading indicators: both degrade weeks before lead time and change failure rate visibly follow.
THE FOUR METRICS: SPEED VERSUS STABILITY
----------------------------------------
STABILITY
^
| change failure rate (lower is better)
| failed deployment recovery time (lower is better)
|
| +------------------------------+
| | high performers sit up here: |
| | frequent small deploys AND |
| | fast, reliable recovery |
| +------------------------------+
|
+--------------------------------------------> SPEED
deployment frequency (higher is better)
lead time for changes (lower is better)
READ THEM IN PAIRS
frequency alone -> ship anything, break production
failure rate alone -> ship nothing, stay perfectly green
LEADING INDICATORS
pipeline duration and flake rate degrade firstConnect it to a real scenario
You do not need a third-party tool to measure DORA; you need consistently recorded events. This workflow emits one after every deployment attempt.
The most important detail is `if: always()` on the record job. Failed deployments must be recorded too, otherwise you are counting only successes and your change failure rate is permanently zero. This is the single most common measurement mistake, and it produces a dashboard that looks excellent while telling you nothing.
The run passes `needs.deploy.result` through as the event status, so each record carries whether that attempt succeeded or failed. Those two fields alone yield both deployment frequency and change failure rate. `fetch-depth: 0` gives the full commit history, which is what lets the script read the author dates of the commits included in this deploy and compute lead time; with a shallow clone you would see one commit and your lead time would be quietly wrong.
One metric this workflow cannot produce on its own is failed deployment recovery time. That requires knowing when an incident began, and that timestamp lives in your alerting system or incident tracker, not in Actions. To get all four, join these deploy events to incident records on a shared identifier — usually the release version or commit SHA.
Try the working example
name: Deploy and Record DORA Events
on:
push:
branches:
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v4
- name: Deploy the current commit to production
run: ./scripts/deploy.sh production
- name: Verify the deployment is serving traffic
run: ./scripts/smoke.sh https://example.com
record:
needs: deploy
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Record the deployment event including failures
run: ./scripts/record-deploy-event.sh --sha ${{ github.sha }} --status ${{ needs.deploy.result }}
env:
METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }}
- name: Compute lead time from commit dates in this deploy
run: ./scripts/compute-lead-time.sh --sha ${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: dora-events
path: metrics/
retention-days: 90
On every push to main the deploy job runs under the production environment and confirms the release with a smoke test. The record job then runs whether or not the deploy succeeded, because of `if: always()`, and passes the outcome from `needs.deploy.result` into the event. As a result the metrics store accumulates every deployment attempt, failures included, which is what makes deployment frequency and change failure rate computable at all. `fetch-depth: 0` gives the lead-time script the full history, so it can see all commits included in this deploy rather than only the head. Note that because the deploy job declares the production environment, any approval wait configured there counts inside your measured lead time — which is correct, since users waited too. Failed deployment recovery time is not produced here; it requires joining these events to incident records.5-minute try-it
Pull the last ninety days of your deploy workflow runs from the API and compute deployment frequency and change failure rate — remembering to include the failed runs, which is where most people get it wrong. Then add incident start and resolution times for the same period, by hand if necessary, and compute time to restore. Finally, write down in under a page what counts as a deployment and what counts as a failure for your team, and get agreement on it. Without that written definition, your next quarter's trend will mostly reflect a change in interpretation.
One important caution
Recording only successful deploys, so change failure rate is permanently zero and the dashboard looks great while measuring nothing
Turning DORA metrics into individual performance targets — people split pull requests purely to inflate deployment counts and the numbers stop meaning anything
DORA: The four keys of software delivery performance — CI/CD with GitHub Actions