> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Track DORA metrics

Included by default

If you signed up for Port on or after May 1, 2026, DORA metrics are already tracked in your portal. Follow this guide to customize the DORA calculations to match your organization's deployment workflow and tooling.

The four DORA (DevOps Research and Assessment) metrics deployment frequency, lead time for changes, change failure rate, and mean time to recovery are the industry standard for measuring software delivery performance.
By tracking these metrics, you can identify areas for improvement and ensure your team is delivering high-quality software efficiently.

This guide walks you through building DORA metrics tracking in Port for deployment frequency, lead time for changes, change failure rate (CFR), and mean time to recovery (MTTR) at both the service and team level.

By the end of this guide, you will have a DORA metrics dashboard providing visibility into delivery performance across services and teams, with automatic tier classification (Elite, High, Medium, Low) and scorecard levels (Bronze, Silver, Gold) based on DORA benchmarks.

DORA Metrics dashboard with key widgets

Steps to build DORA metrics

  1. Validate your data model: ensure the foundational blueprints (service, Team) and relations are in place and aligned. DORA metrics build on top of this foundation.
  2. Track deployments: create the deployment blueprint and map merged PRs to deployment entities. This is the recommended default strategy. For organizations that require a more customized approach, alternative strategies such as workflow runs, CI/CD pipelines, or releases are also supported.
  3. Track incidents (optional): connect PagerDuty (or another tool) to enable CFR and MTTR.
  4. Configure metrics: add aggregation and tier calculation properties for deployment frequency, lead time, CFR, and MTTR at the service and team level.
  5. Build the dashboard: create widgets to visualize DORA metrics across your organization.
  6. Set up DORA scorecards: create service and team scorecards that grade DORA maturity with Bronze, Silver, and Gold levels.

Prerequisites

This guide assumes the following:

  • A Port account with the onboarding process completed.
  • Foundational data model in place: complete the Create foundational Engineering Intelligence data model guide, which provisions the core Organization, Team, and Service blueprints (with team ownership on services) for GitHub, GitLab, and Azure DevOps.
  • A connected Git repository (GitHub, GitLab, or Azure Repos) linked to Port. Note: Other Git providers are supported, though this guide focuses on the three mentioned above.
  • An active Git integration: For GitHub users ensure Port's GitHub Ocean integration is installed.
  • (Optional) Incident Management integration: To track change failure rate and MTTR metrics, a PagerDuty integration with the pagerdutyIncident blueprint is required.
Incident-dependent metrics

Change failure rate and MTTR require an incident management integration with incident entities linked to services. This guide uses PagerDuty as the example, but the same approach applies to other tools like OpsGenie, FireHydrant, or ServiceNow. You just need to adjust the blueprint identifier and property names to match your integration.
Without an incident integration, only deployment frequency and lead time metrics will be available.

Tracking deployments

Deployments refer to releasing new or updated code into various environments such as Production, Staging, or Testing.
Tracking deployments helps you understand how efficiently your team ships features and monitor release stability.

By default, this guide creates deployment entities from merged pull requests to the default branch, which is the simplest and most common approach. If your organization requires a more customized deployment tracking strategy (e.g., via CI/CD pipelines, workflow runs, or releases), see Alternative deployment tracking strategies below.

Deployments contribute to three key DORA metrics: deployment frequency, change failure rate, and lead time for changes.

Create the deployment blueprint

  1. Navigate to your Port Builder page.

  2. Click the + Blueprint button to create a new blueprint.

  3. Click on the {...} Edit JSON button in the top right corner.

  4. Paste the JSON for your Git provider and click Save:

    Deployment blueprint (Click to expand)
    {
    "identifier": "deployment",
    "title": "Deployment",
    "icon": "Deployment",
    "description": "A production deployment created from a merged PR to the default branch",
    "schema": {
    "properties": {
    "deploymentStatus": {
    "title": "Deployment Status",
    "type": "string",
    "enum": ["Success", "Failure"],
    "enumColors": {
    "Success": "green",
    "Failure": "red"
    }
    },
    "environment": {
    "title": "Environment",
    "type": "string",
    "enum": ["Production", "Staging", "Development"],
    "enumColors": {
    "Production": "green",
    "Staging": "yellow",
    "Development": "blue"
    }
    },
    "createdAt": {
    "title": "Deployment Time",
    "type": "string",
    "format": "date-time"
    }
    },
    "required": []
    },
    "mirrorProperties": {
    "github_lead_time_hours": {
    "title": "Lead Time for Changes (Hours)",
    "path": "github_pull_request.cycle_time_hours"
    },
    "github_repo_id": {
    "title": "GitHub Repository ID",
    "path": "github_pull_request.repository.$identifier"
    },
    "gitlab_repo_id": {
    "title": "GitLab Repository ID",
    "path": "gitlab_merge_request.repository.$identifier"
    },
    "ado_repo_id": {
    "title": "ADO Repository ID",
    "path": "ado_pull_request.repository.$identifier"
    }
    },
    "calculationProperties": {},
    "aggregationProperties": {},
    "relations": {
    "service": {
    "target": "service",
    "title": "Service",
    "many": false,
    "required": false
    },
    "github_pull_request": {
    "target": "githubPullRequest",
    "title": "Pull Request",
    "many": false,
    "required": false
    }
    }
    }

What you should see: After saving, the Deployment blueprint appears in your Builder with relations to both Service and your PR/MR blueprint, plus a lead time mirror property (github_lead_time_hours for GitHub, gitlab_lead_time_hours for GitLab, ado_lead_time_hours for Azure DevOps).

Missing lead time

If you do not have the lead time (cycle_time_hours) configured on your pull request / merge request blueprint, follow the relevant guide for your Git provider:

Alternatively, you can use the default leadTimeHours property that comes with some integrations and update the mirror property path to github_pull_request.leadTimeHours (GitHub), gitlab_merge_request.leadTimeHours (GitLab), or ado_pull_request.leadTimeHours (Azure DevOps).

Map deployments from merged PRs

The recommended approach is to create deployment entities automatically when pull requests are merged into the default branch. Each merged PR creates a deployment entity with the lead time calculated as the time from PR creation to merge. If your organization requires a more complex setup (e.g., workflow runs, CI/CD pipelines, releases, or custom API), see Alternative deployment tracking strategies below.

  1. Navigate to the data sources page in your Port portal.
  2. Select your Git integration.

Add the deployment mapping configuration for your provider:

Deployment mapping from merged PRs (Click to expand)
Hardcoded values

The deploymentStatus is hardcoded to Success and environment to Production in these examples. You can modify these values based on your requirements.

- kind: pull-request
selector:
query: .base.ref == "main" and .state == "closed" and .merged_at != null
states: ["closed"]
maxResults: 100
since: 90
port:
entity:
mappings:
identifier: .head.repo.name + "-deploy-" + (.number | tostring)
title: '"Merge: " + .title'
blueprint: '"deployment"'
properties:
environment: '"Production"'
deploymentStatus: '"Success"'
createdAt: .merged_at
relations:
service: .head.repo.name
github_pull_request: .head.repo.name + "-pr-" + (.number | tostring)
Match your pull request mapping

Set github_pull_request to the same expression as the identifier in your GitHub Ocean pull-request resource (often .head.repo.name + "-pr-" + (.number|tostring)). Tune maxResults and since to limit how many closed pull requests sync. See GitHub Ocean examples and Migrate from the GitHub app.

Set up GitHub deployment service relations

The service relation on deployment entities is set in two ways:

  1. At ingest: the mapping sets service directly using .head.repo.name, matching the service identifier by repository name.
  2. Via automation: the set_github_deployment_relations automation fires whenever a service's github_repository relation changes and bulk-updates all deployment entities where github_pull_request is not null and github_repo_id matches the new repository identifier.
Prerequisite: github_repository relation on service

This automation requires the service blueprint to have a github_repository relation pointing to githubRepository. This relation is set up by the Create foundational Engineering Intelligence data model guide.

  1. Go to the automations page.
  2. Click + Automation.
  3. Click the {...} Edit JSON button.
  4. Paste the following JSON and click Save:
Set GitHub Deployment relations automation (Click to expand)
{
"identifier": "set_github_deployment_relations",
"title": "Set GitHub Deployment relations",
"trigger": {
"type": "automation",
"event": {"type": "ANY_ENTITY_CHANGE", "blueprintIdentifier": "service"},
"condition": {
"type": "JQ",
"expressions": [
".diff.before.relations.github_repository != .diff.after.relations.github_repository",
".diff.after.relations.github_repository != null"
],
"combinator": "and"
}
},
"invocationMethod": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/migrations",
"agent": false,
"synchronized": true,
"method": "POST",
"headers": {"Content-Type": "application/json", "RUN_ID": "{{ .run.id }}"},
"body": {
"sourceBlueprint": "deployment",
"mapping": {
"blueprint": "deployment",
"filter": ".relations.github_pull_request != null and .properties.github_repo_id == \"{{ .event.diff.after.relations.github_repository }}\"",
"entity": {
"identifier": ".identifier",
"relations": {"service": "\"{{ .event.context.entityIdentifier }}\""}
}
}
}
}
}
Default branch

The mappings above filter for PRs merged to the main branch. If your repositories use a different default branch (e.g., master), update the filter accordingly.

  1. Click Save & Resync.

After resync, navigate to the Deployments catalog page in your portal. You should see deployment entities appearing for each merged PR, linked to the corresponding service and pull request.

Alternative deployment tracking strategies

If PR/MR merges don't fit your workflow, Port supports several other deployment tracking methods.

Other deployment tracking strategies (Click to expand)

Workflow/Job runs

Track deployments by monitoring workflow runs in your pipeline. The deployment status is set dynamically based on whether the workflow concluded successfully or failed.

- kind: workflow-run
selector:
query: >
(.head_branch == "main") and
(.name | test("deploy|CD"; "i"))
port:
entity:
mappings:
identifier: .head_repository.name + "-deploy-" + (.run_number | tostring)
title: .head_repository.name + " Deployment via workflow"
blueprint: '"deployment"'
properties:
environment: '"Production"'
createdAt: .created_at
deploymentStatus: (.conclusion | ascii_upcase[0:1] + .[1:])
relations:
service: .head_repository.name

CI/CD pipelines (Jenkins, CircleCI, Azure Pipelines, etc.)

CI/CD pipelines can report deployments to Port using Port's API as part of the pipeline execution. See the relevant guide for your CI/CD tool:

These integrations use search relations to map the deployment to the correct service based on the service's $title. See mapping relations using search queries for more details.

Releases/Tags (GitHub only)

- kind: release
selector:
query: (.target_commitish == "main") and (.name | test("Production"; "i"))
port:
entity:
mappings:
identifier: .release.name + "-" + .release.tag_name
title: .release.name + " Deployment on release"
blueprint: '"deployment"'
properties:
environment: '"Production"'
createdAt: .release.created_at
deploymentStatus: '"Success"'
relations:
service: .repo.name

Find more details about setting up GitHub integrations for releases and tags in Repositories, repository releases and tags.

Custom API

If your tool or workflow is not natively supported, you can create deployment entities directly via Port's API:

curl -X POST https://api.port.io/v1/blueprints/deployment/entities?upsert=true&merge=true \
-H "Authorization: Bearer $YOUR_PORT_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identifier": "custom-deployment-1234",
"title": "Custom Deployment 1234",
"properties": {
"environment": "Production",
"createdAt": "2024-09-01T12:00:00Z",
"deploymentStatus": "Success"
},
"relations": {
"service": "your-service-identifier"
}
}'

Replace $YOUR_PORT_API_TOKEN with your actual API token. See mapping relations using search queries for details.

Tracking incidents

Incidents are essential for tracking change failure rate (CFR) and mean time to recovery (MTTR). Effective incident tracking reveals how frequently deployments fail and how quickly teams resolve issues.

The steps below use PagerDuty as the example incident integration. If you use a different tool (OpsGenie, FireHydrant, ServiceNow, etc.), adapt the blueprint identifier (e.g., replace pagerdutyIncident with your incident blueprint) and property names accordingly.

Set up data model

Ensure that your PagerDuty incident blueprint is properly configured to map incidents to the correct services. Use the PagerDuty incident blueprint in the integration examples, and the default mapping configuration on the main PagerDuty page, as references when aligning your data model.

Add incident resolution time and recovery time properties:

  1. Navigate to your Port Builder page.

  2. Select the PagerDuty Incident blueprint.

  3. Click on the {...} button in the top right corner, and choose Edit JSON.

  4. Add the following properties:

    Additional PagerDuty incident properties (Click to expand)
    "resolvedAt": {
    "title": "Incident Resolution Time",
    "type": "string",
    "format": "date-time",
    "description": "The timestamp when the incident was resolved"
    },
    "recoveryTime": {
    "title": "Time to Recovery",
    "type": "number",
    "description": "The time (in minutes) between the incident being triggered and resolved"
    }
  5. Click Save.

Add incident mapping config:

  1. Navigate to your Port Data Sources page.

  2. Select the PagerDuty data source.

  3. Add the following property mappings to the incident mapping section:

    Incident mapping for resolvedAt and recoveryTime (Click to expand)
    resolvedAt: .resolved_at
    recoveryTime: >-
    (.created_at as $createdAt | .resolved_at as $resolvedAt |
    if $resolvedAt == null then null else
    ( ($resolvedAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) -
    ($createdAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) ) / 60 end)
  4. Click Save & Resync.

Syncing incidents

To sync incidents from PagerDuty, follow the PagerDuty guide. For other tools:

Automatic relations

The relation between the PagerDuty incident blueprint and the service blueprint is automatically created when you install the PagerDuty integration.

After resync, navigate to the PagerDuty Incidents catalog page in your portal. You should see incident entities appearing with resolvedAt and recoveryTime properties populated for resolved incidents, and each incident linked to the corresponding service.

Set up metrics

Now we'll add aggregation and calculation properties to compute DORA metrics and classify services and teams into performance tiers.

For each metric below, you will add properties to both the Service and Team blueprints. To edit a blueprint's JSON:

Relation chain for team-level metrics

Team-level aggregation properties scope data to each team's services. For Azure DevOps, azureDevopsPullRequest and azureDevopsBuild entities have inherited team ownership through their service relation, so no explicit pathFilter is needed. For GitHub PR aggregations, use pathFilter: [{fromBlueprint: "githubPullRequest", path: ["service", "_team"]}]. For GitLab MR aggregations, use pathFilter: [{fromBlueprint: "gitlabMergeRequest", path: ["service", "_team"]}]. Deployment aggregations work without a pathFilter for all providers.

  1. Go to the Builder in your Port portal.

  2. Click on the blueprint you want to edit (Service or Team).

  3. Click on the {...} button in the top right corner, and choose Edit JSON.

  4. Add the properties shown below to the "aggregationProperties" and "calculationProperties" sections.

  5. Click Save.

Aggregation data availability

Aggregation properties are calculated based on data ingested after the property is created. Historical data that was ingested before you add these properties will not be included. To backfill, trigger a resync on the relevant integration after saving the aggregation properties. See aggregation properties for more details.

Deployment frequency

Deployment frequency measures how often your services deploy to production. It is calculated as the average number of successful production deployments per week. The tier calculation classifies services as Elite (≥7/week), High (≥1/week), Medium (≥0.25/week), or Low.

Service level

Add the following to the Service blueprint:

Aggregation properties (Click to expand)

Add to "aggregationProperties":

"total_deployments": {
"title": "Total Deployments",
"type": "number",
"target": "deployment",
"description": "Total successful deployments to Production",
"query": {
"combinator": "and",
"rules": [
{ "property": "deploymentStatus", "operator": "=", "value": "Success" },
{ "property": "environment", "operator": "=", "value": "Production" }
]
},
"calculationSpec": { "func": "count", "calculationBy": "entities" },
"pathFilter": [{ "fromBlueprint": "deployment", "path": ["service"] }]
},
"deployment_frequency": {
"title": "Deployment Frequency (per week)",
"type": "number",
"target": "deployment",
"description": "Average successful Production deployments per week",
"query": {
"combinator": "and",
"rules": [
{ "property": "deploymentStatus", "operator": "=", "value": "Success" },
{ "property": "environment", "operator": "=", "value": "Production" }
]
},
"calculationSpec": {
"func": "average",
"averageOf": "week",
"calculationBy": "entities",
"measureTimeBy": "createdAt"
},
"pathFilter": [{ "fromBlueprint": "deployment", "path": ["service"] }]
}
Tier calculation property (Click to expand)

Add to "calculationProperties":

"deploy_freq_tier": {
"title": "Deployment Frequency",
"description": "DORA deployment frequency tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.total_deployments == null or .properties.total_deployments == 0) then \"Low\" else if (.properties.deployment_frequency // 0) >= 7 then \"Elite\" elif (.properties.deployment_frequency // 0) >= 1 then \"High\" elif (.properties.deployment_frequency // 0) >= 0.25 then \"Medium\" else \"Low\" end end"
}

Team level

Add the following to the Team blueprint. The team-level deployment frequency tier divides total deployment frequency by the number of services owned by the team, ensuring a fair cross-team comparison.

Select your Git provider. For deployment aggregations, all three providers use Port's native team ownership - no pathFilter is required:

Aggregation properties (Click to expand)

Add to "aggregationProperties":

"services_count": {
"title": "Services Count",
"type": "number",
"target": "service",
"calculationSpec": { "func": "count", "calculationBy": "entities" }
},
"total_deployments": {
"title": "Total Deployments",
"type": "number",
"target": "deployment",
"description": "Total successful deployments across team services",
"query": {
"combinator": "and",
"rules": [
{ "property": "deploymentStatus", "operator": "=", "value": "Success" },
{ "property": "environment", "operator": "=", "value": "Production" }
]
},
"calculationSpec": { "func": "count", "calculationBy": "entities" }
},
"deployment_frequency": {
"title": "Deployment Frequency (per week)",
"type": "number",
"target": "deployment",
"description": "Average weekly deployments across team services",
"query": {
"combinator": "and",
"rules": [
{ "property": "deploymentStatus", "operator": "=", "value": "Success" },
{ "property": "environment", "operator": "=", "value": "Production" }
]
},
"calculationSpec": {
"func": "average",
"averageOf": "week",
"calculationBy": "entities",
"measureTimeBy": "createdAt"
}
}
GitHub native team ownership

For the _team blueprint, no pathFilter is required. Port's native team ownership automatically scopes aggregations to entities owned by each team, traversing through the service.$team ownership chain to reach deployments.

Tier calculation properties (Click to expand)

Add to "calculationProperties":

"deployment_frequency_per_service": {
"title": "Deployment Frequency (per service)",
"description": "Deployment frequency normalized by the number of services",
"type": "number",
"calculation": "if (.properties.services_count != null and .properties.services_count != 0) then ((.properties.deployment_frequency // 0) / .properties.services_count) else 0 end"
},
"deploy_freq_tier": {
"title": "Deployment Frequency",
"description": "DORA deployment frequency tier (per service)",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.total_deployments == null or .properties.total_deployments == 0) then \"Low\" else (if (.properties.services_count != null and .properties.services_count != 0) then ((.properties.deployment_frequency // 0) / .properties.services_count) else 0 end) as $dpf | if $dpf >= 7 then \"Elite\" elif $dpf >= 1 then \"High\" elif $dpf >= 0.25 then \"Medium\" else \"Low\" end end"
}

What you should see: After saving both blueprints, open any service or team entity. You should see Total Deployments, Deployment Frequency (per week), and a Deployment Frequency tier badge (Elite/High/Medium/Low). Team entities also show Services Count and Deployment Frequency (per service).

Lead time for changes

Lead time for changes measures how quickly code moves from development into production. Port supports two approaches, you can choose the one that best fits your workflow:

Lead time is measured from when a pull request is created to when it is merged, using the cycle_time_hours property that Port calculates automatically as part of your PR/MR mapping. No additional setup is required - the aggregation below reads this property directly.

Scoping lead time to the default branch

Aggregation queries filter on blueprint properties in Port, not raw API field names. By default, the aggregation below includes merged PRs/MRs in the last 30 days. To scope by target branch, add a string property to your pull request blueprint (for example targetBranch) and map it from your Git provider (for example GitHub .base.ref, GitLab .target_branch, or Azure DevOps .targetRefName with refs/heads/ stripped). Then add a query rule such as "property": "targetBranch", "operator": "=", "value": "main".

The tier calculation classifies services as Elite (≤24h), High (≤168h / 1 week), Medium (≤720h / 30 days), or Low.

Using the first-commit method in the aggregation below

If you chose the first commit to merge method, replace "property": "cycle_time_hours" with "property": "first_commit_to_merge_hours" in each aggregation property below.

Service level

Add the following to the Service blueprint. Select your Git provider to get the correct target blueprint:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"github_lead_time_for_change": {
"title": "Lead Time for Changes (Hours)",
"type": "number",
"target": "githubPullRequest",
"description": "Average time from PR creation to merge in the last 30 days",
"query": {
"combinator": "and",
"rules": [
{ "property": "mergedAt", "operator": "between", "value": { "preset": "lastMonth" } }
]
},
"calculationSpec": {
"func": "average",
"averageOf": "total",
"calculationBy": "property",
"property": "cycle_time_hours",
"measureTimeBy": "$createdAt"
}
}
Tier calculation property (Click to expand)

Add to "calculationProperties":

"github_lead_time_tier": {
"title": "Lead Time for Changes",
"description": "DORA lead time for changes tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.github_lead_time_for_change == null) then \"Low\" elif .properties.github_lead_time_for_change <= 24 then \"Elite\" elif .properties.github_lead_time_for_change <= 168 then \"High\" elif .properties.github_lead_time_for_change <= 720 then \"Medium\" else \"Low\" end"
}

Team level

Add the following to the Team blueprint. Select your Git provider - both the target and fromBlueprint in pathFilter must match:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"github_lead_time_for_change": {
"title": "Lead Time for Changes (Hours)",
"type": "number",
"target": "githubPullRequest",
"description": "Average lead time across team services in the last 30 days",
"query": {
"combinator": "and",
"rules": [
{ "property": "mergedAt", "operator": "between", "value": { "preset": "lastMonth" } }
]
},
"calculationSpec": {
"func": "average",
"averageOf": "total",
"calculationBy": "property",
"property": "cycle_time_hours",
"measureTimeBy": "$createdAt"
},
"pathFilter": [{ "fromBlueprint": "githubPullRequest", "path": ["service", "_team"] }]
}
Tier calculation property (Click to expand)

Add to "calculationProperties":

"github_lead_time_tier": {
"title": "Lead Time for Changes",
"description": "DORA lead time for changes tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.github_lead_time_for_change == null) then \"Low\" elif .properties.github_lead_time_for_change <= 24 then \"Elite\" elif .properties.github_lead_time_for_change <= 168 then \"High\" elif .properties.github_lead_time_for_change <= 720 then \"Medium\" else \"Low\" end"
}

What you should see: After saving both blueprints, service and team entities should display Lead Time for Changes (Hours) and a Lead Time for Changes tier badge.

Change failure rate (CFR)

Requires incident integration

Change failure rate requires an incident management integration with incident entities linked to services. The examples below use PagerDuty (pagerdutyIncident), you can adjust the blueprint identifier and property names if you use a different tool. See the Tracking incidents section above.

Change failure rate measures the percentage of deployments that are associated with incidents. It is calculated as incidents / (deployments + incidents) × 100. The tier calculation classifies services as Elite (≤5%), High (≤20%), Medium (≤30%), or Low.

CFR calculation approach

The standard DORA definition of CFR is failed deployments / total deployments. Since most incident tools don't directly link incidents to specific deployments, this guide uses incident count as a proxy for failed deployments. This is a common and practical adaptation and if your workflow allows you to mark deployments as failed directly (e.g., via a deploymentStatus of Failed), you can adjust the formula accordingly.

Service level

Add the following to the Service blueprint:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"total_incidents": {
"title": "Total Incidents",
"type": "number",
"target": "pagerdutyIncident",
"description": "Total incidents linked to this service",
"calculationSpec": {
"func": "count",
"calculationBy": "entities"
},
"pathFilter": [
{
"fromBlueprint": "pagerdutyIncident",
"path": ["service"]
}
]
}
Tier calculation property (Click to expand)

Add to "calculationProperties":

"change_failure_rate": {
"title": "Change Failure Rate (%)",
"description": "Percentage of deployments that caused incidents",
"type": "number",
"calculation": "if (.properties.total_deployments == null or .properties.total_deployments == 0) then null else ((.properties.total_incidents // 0) / (.properties.total_deployments + (.properties.total_incidents // 0)) * 100 | floor) end"
},
"cfr_tier": {
"title": "CFR",
"description": "DORA change failure rate tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.change_failure_rate == null) then null else (if .properties.change_failure_rate <= 5 then \"Elite\" elif .properties.change_failure_rate <= 20 then \"High\" elif .properties.change_failure_rate <= 30 then \"Medium\" else \"Low\" end) end"
}

Team level

Add the following to the Team blueprint:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"total_incidents": {
"title": "Total Incidents",
"type": "number",
"target": "pagerdutyIncident",
"description": "Total incidents across team services",
"calculationSpec": {
"func": "count",
"calculationBy": "entities"
}
}
GitLab users

For GitLab, add a pathFilter to scope the aggregation to the explicit team relation: "pathFilter": [{"fromBlueprint": "pagerdutyIncident", "path": ["service", "team"]}]. GitHub and Azure DevOps use Port's native team ownership, so no pathFilter is needed.

Tier calculation properties (Click to expand)

Add to "calculationProperties":

"change_failure_rate": {
"title": "Change Failure Rate (%)",
"description": "Percentage of deployments that caused incidents",
"type": "number",
"calculation": "if (.properties.total_deployments == null or .properties.total_deployments == 0) then null else ((.properties.total_incidents // 0) / (.properties.total_deployments + (.properties.total_incidents // 0)) * 100 | floor) end"
},
"cfr_tier": {
"title": "CFR",
"description": "DORA change failure rate tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.change_failure_rate == null) then null else (if .properties.change_failure_rate <= 5 then \"Elite\" elif .properties.change_failure_rate <= 20 then \"High\" elif .properties.change_failure_rate <= 30 then \"Medium\" else \"Low\" end) end"
}

What you should see: After saving both blueprints, service entities show Total Incidents and a CFR tier badge. Team entities additionally show a Change Failure Rate (%) value.

Mean time to recovery (MTTR)

MTTR measures the average time from incident trigger to resolution, reflecting how quickly teams recover from failures. The tier calculation classifies services as Elite (≤60min), High (≤1,440min / 1 day), Medium (≤43,200min / 30 days), or Low.

Requires incident integration

MTTR requires an incident management integration with incident entities that include a recovery time property. The examples below use PagerDuty (pagerdutyIncident with recoveryTime), you can adjust the blueprint identifier and property names if you use a different tool. See the Tracking incidents section above.

Service level

Add the following to the Service blueprint:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"mean_time_to_recovery": {
"title": "MTTR (Minutes)",
"type": "number",
"target": "pagerdutyIncident",
"description": "Average time in minutes from incident trigger to resolution",
"calculationSpec": {
"func": "average",
"averageOf": "total",
"calculationBy": "property",
"property": "recoveryTime",
"measureTimeBy": "$createdAt"
},
"pathFilter": [
{
"fromBlueprint": "pagerdutyIncident",
"path": ["service"]
}
]
}
Tier calculation property (Click to expand)

Add to "calculationProperties":

"mttr_tier": {
"title": "MTTR",
"description": "DORA MTTR tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.total_incidents == null or .properties.total_incidents == 0) then null elif (.properties.mean_time_to_recovery == null) then \"Elite\" elif .properties.mean_time_to_recovery <= 60 then \"Elite\" elif .properties.mean_time_to_recovery <= 1440 then \"High\" elif .properties.mean_time_to_recovery <= 43200 then \"Medium\" else \"Low\" end"
}

Team level

Add the following to the Team blueprint:

Aggregation property (Click to expand)

Add to "aggregationProperties":

"mean_time_to_recovery": {
"title": "MTTR (Minutes)",
"type": "number",
"target": "pagerdutyIncident",
"description": "Average recovery time across team services",
"calculationSpec": {
"func": "average",
"averageOf": "total",
"calculationBy": "property",
"property": "recoveryTime",
"measureTimeBy": "$createdAt"
}
}
GitLab users

For GitLab, add a pathFilter to scope the aggregation to the explicit team relation: "pathFilter": [{"fromBlueprint": "pagerdutyIncident", "path": ["service", "team"]}]. GitHub and Azure DevOps use Port's native team ownership, so no pathFilter is needed.

Tier calculation property (Click to expand)

Add to "calculationProperties":

"mttr_tier": {
"title": "MTTR",
"description": "DORA MTTR tier",
"type": "string",
"colorized": true,
"colors": {
"Low": "red",
"Medium": "orange",
"High": "blue",
"Elite": "lime"
},
"calculation": "if (.properties.total_incidents == null or .properties.total_incidents == 0) then null elif (.properties.mean_time_to_recovery == null) then \"Elite\" elif .properties.mean_time_to_recovery <= 60 then \"Elite\" elif .properties.mean_time_to_recovery <= 1440 then \"High\" elif .properties.mean_time_to_recovery <= 43200 then \"Medium\" else \"Low\" end"
}

What you should see: After saving both blueprints, service and team entities show MTTR (Minutes) and an MTTR tier badge.

Visualize metrics

We will create a dedicated dashboard to monitor DORA metrics using Port's customizable widgets. The dashboard covers deployment frequency, lead time, and optionally change failure rate and MTTR.

DORA metrics dashboard with tier classification

Create the dashboard

  1. Navigate to your software catalog.
  2. Click on the + button in the left sidebar.
  3. Select New folder (if you don't already have one).
  4. Name the folder Engineering Intelligence and click Create. The folder identifier will be automatically set to engineering_intelligence - this is required for the API script method to work.
  5. Inside the Engineering Intelligence folder, click + New again.
  6. Select New dashboard.
  7. Name the dashboard DORA Metrics and click Create.

Add widgets

You can populate the dashboard using either an API script or by manually creating each widget through the UI.

The fastest way to set up the dashboard is by using Port's API to create all widgets at once.

Get your Port API token

  1. In your Port portal, click on your profile picture in the top right corner.

  2. Select Credentials.

  3. Click Generate API token.

  4. Copy the generated token and store it as an environment variable:

    export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
EU region

If your portal is hosted in the EU region, replace api.port.io with api.port-eu.io in the dashboard creation command below.

Create the dashboard with widgets

Provider-specific property names

The dashboard JSON below includes columns for both GitHub and GitLab in the service and team tables. The KPI and trend widgets use GitHub-specific property names - if you are using GitLab, replace github_lead_time_hours with gitlab_lead_time_for_change in those widgets. If you are using Azure DevOps, replace github_lead_time_hours with ado_lead_time_hours, github_lead_time_tier with ado_lead_time_tier, and github_lead_time_for_change with ado_lead_time_for_change in the KPI/trend widgets and table columns.

Save the following JSON to a file named dora_dashboard.json:

Dashboard JSON payload (Click to expand)
{
"identifier": "dora_metrics",
"title": "DORA Metrics",
"icon": "Health",
"type": "dashboard",
"parent": "engineering_intelligence",
"widgets": [
{
"id": "doraDashboardWidget",
"type": "dashboard-widget",
"layout": [
{
"height": 400,
"columns": [
{"id": "deployFreqKpi", "size": 6},
{"id": "deployFreqChart", "size": 6}
]
},
{
"height": 520,
"columns": [
{"id": "teamDoraTable", "size": 12}
]
},
{
"height": 520,
"columns": [
{"id": "groupDoraTable", "size": 12}
]
},
{
"height": 520,
"columns": [
{"id": "serviceDoraTable", "size": 12}
]
},
{
"height": 400,
"columns": [
{"id": "doraGithubLeadTimeKpi", "size": 6},
{"id": "doraGithubLeadTimeChart", "size": 6}
]
},
{
"height": 400,
"columns": [
{"id": "cfrKpi", "size": 6},
{"id": "cfrChart", "size": 6}
]
},
{
"height": 400,
"columns": [
{"id": "mttrKpi", "size": 6},
{"id": "mttrChart", "size": 6}
]
}
],
"widgets": [
{
"id": "deployFreqKpi",
"type": "entities-number-chart",
"title": "Avg Deployment Frequency",
"icon": "Metric",
"description": "Average weekly deployments per service across all teams across organization",
"blueprint": "_team",
"chartType": "aggregateByProperty",
"calculationBy": "property",
"func": "average",
"property": "deployment_frequency_per_service",
"averageOf": "total",
"displayFormatting": "custom",
"decimalPlaces": ".00",
"unit": "custom",
"unitCustom": "per week",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "=", "property": "type", "value": "team"}
]
}
},
{
"id": "deployFreqChart",
"type": "line-chart",
"title": "Number of Deployments per Day",
"icon": "LineChart",
"description": "Number of deployments per day across the organization",
"blueprint": "deployment",
"chartType": "countEntities",
"func": "count",
"measureTimeBy": "createdAt",
"timeInterval": "day",
"timeRange": {"preset": "lastMonth"},
"xAxisTitle": "Day",
"yAxisTitle": "Deployments",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "=", "property": "deploymentStatus", "value": "Success"},
{"operator": "=", "property": "environment", "value": "Production"}
]
}
},
{
"id": "teamDoraTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Team - DORA Metrics",
"icon": "Table",
"description": "Scored against DORA benchmarks: Elite / High / Medium / Low",
"blueprint": "_team",
"dataset": {"combinator": "and", "rules": [{"operator": "=", "property": "type", "value": "team"}]},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"groupSettings": {"groupBy": []},
"propertiesSettings": {
"order": ["$title", "deploy_freq_tier", "deployment_frequency_per_service", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"],
"shown": ["$title", "deploy_freq_tier", "deployment_frequency_per_service", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": [{"operator": "=", "property": "type", "value": "team"}]}},
"sortSettings": {"sortBy": [{"property": "deployment_frequency_per_service", "order": "desc"}]}
}
}
},
{
"id": "groupDoraTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Group - DORA Metrics",
"icon": "Table",
"description": "Scored against DORA benchmarks: Elite / High / Medium / Low",
"blueprint": "_team",
"dataset": {"combinator": "and", "rules": [{"operator": "=", "property": "type", "value": "group"}]},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"groupSettings": {"groupBy": []},
"propertiesSettings": {
"order": ["$title", "deploy_freq_tier", "deployment_frequency_per_service", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"],
"shown": ["$title", "deploy_freq_tier", "deployment_frequency_per_service", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": [{"operator": "=", "property": "type", "value": "group"}]}},
"sortSettings": {"sortBy": [{"property": "deployment_frequency_per_service", "order": "desc"}]}
}
}
},
{
"id": "serviceDoraTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Service - DORA Metrics",
"icon": "Table",
"description": "Scored against DORA benchmarks: Elite / High / Medium / Low",
"blueprint": "service",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"service": {
"groupSettings": {"groupBy": ["team"]},
"propertiesSettings": {
"order": ["$title", "team", "deploy_freq_tier", "deployment_frequency", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"],
"shown": ["$title", "team", "deploy_freq_tier", "deployment_frequency", "github_lead_time_tier", "gitlab_lead_time_tier", "ado_lead_time_tier", "github_lead_time_for_change", "gitlab_lead_time_for_change", "ado_lead_time_for_change", "cfr_tier", "change_failure_rate", "mttr_tier", "mean_time_to_recovery", "total_incidents"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": [{"property": "deployment_frequency", "order": "desc"}]}
}
}
},
{
"id": "doraGithubLeadTimeKpi",
"type": "entities-number-chart",
"title": "Avg Lead Time for Changes (GitHub)",
"icon": "Metric",
"description": "Average lead time from linked GitHub PRs for production deployments in the last 30 days",
"blueprint": "deployment",
"chartType": "aggregateByProperty",
"calculationBy": "property",
"func": "average",
"property": "github_lead_time_hours",
"averageOf": "total",
"measureTimeBy": "createdAt",
"displayFormatting": "custom",
"decimalPlaces": ".00",
"unit": "custom",
"unitCustom": "Hours",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "=", "property": "deploymentStatus", "value": "Success"},
{"operator": "=", "property": "environment", "value": "Production"},
{"operator": "between", "property": "createdAt", "value": {"preset": "lastMonth"}}
]
}
},
{
"id": "doraGithubLeadTimeChart",
"type": "line-chart",
"title": "Daily Mean Lead Time for Changes (GitHub)",
"icon": "LineChart",
"description": "Average lead time per day from linked GitHub pull requests for production deployments",
"blueprint": "deployment",
"chartType": "aggregatePropertiesValues",
"func": "average",
"properties": ["properties.github_lead_time_hours"],
"measureTimeBy": "createdAt",
"timeInterval": "day",
"timeRange": {"preset": "lastMonth"},
"xAxisTitle": "Day",
"yAxisTitle": "Hours",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "=", "property": "deploymentStatus", "value": "Success"},
{"operator": "=", "property": "environment", "value": "Production"}
]
}
},
{
"id": "cfrKpi",
"type": "entities-number-chart",
"title": "Avg Change Failure Rate (CFR)",
"icon": "Metric",
"description": "Average change failure rate across all teams in the organization",
"blueprint": "_team",
"chartType": "aggregateByProperty",
"calculationBy": "property",
"func": "average",
"property": "change_failure_rate",
"averageOf": "total",
"displayFormatting": "round",
"unit": "custom",
"unitCustom": "%",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "=", "property": "type", "value": "team"}
]
}
},
{
"id": "cfrChart",
"type": "line-chart",
"title": "Daily Incidents Count",
"icon": "LineChart",
"description": "Number of incidents per day across the organization",
"blueprint": "pagerdutyIncident",
"chartType": "countEntities",
"func": "count",
"measureTimeBy": "created_at",
"timeInterval": "day",
"timeRange": {"preset": "lastMonth"},
"xAxisTitle": "Day",
"yAxisTitle": "Incidents",
"dataset": {"combinator": "and", "rules": []}
},
{
"id": "mttrKpi",
"type": "entities-number-chart",
"title": "Avg Daily Mean Time to Recovery (MTTR)",
"icon": "Metric",
"description": "Average mean time to recovery across the organization in the last 30 days",
"blueprint": "pagerdutyIncident",
"chartType": "aggregateByProperty",
"calculationBy": "property",
"func": "average",
"property": "recoveryTime",
"averageOf": "total",
"displayFormatting": "round",
"unit": "custom",
"unitCustom": "Minutes",
"dataset": {
"combinator": "and",
"rules": [
{"operator": "between", "property": "resolvedAt", "value": {"preset": "lastMonth"}}
]
}
},
{
"id": "mttrChart",
"type": "line-chart",
"title": "Daily Mean Time to Recovery (MTTR)",
"icon": "LineChart",
"description": "Average incident resolution time per day across the organization",
"blueprint": "pagerdutyIncident",
"chartType": "aggregatePropertiesValues",
"func": "average",
"properties": ["properties.recoveryTime"],
"measureTimeBy": "resolvedAt",
"timeInterval": "day",
"timeRange": {"preset": "lastMonth"},
"xAxisTitle": "Day",
"yAxisTitle": "Minutes",
"dataset": {"combinator": "and", "rules": []}
}
]
}
]
}

Then run the following command to create the dashboard with all widgets:

curl -s -X POST "https://api.port.io/v1/pages" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @dora_dashboard.json | python3 -m json.tool
Engineering Intelligence folder

The script assumes an engineering_intelligence folder already exists in your catalog. If you haven't created it yet, follow steps 1-4 in the create the dashboard section first.

Set up DORA scorecards

Included by default

If you signed up for Port on or after May 1, 2026, DORA scorecards are already set up in your portal. Follow this guide to customize the DORA tier thresholds based on your organization's performance targets.

Scorecards let you define maturity levels for your services and teams, giving engineering leaders a clear view of which areas meet delivery standards and which need attention.

This part builds on the DORA metrics setup above, which creates the required aggregation and calculation properties. We will create scorecards that automatically grade each service and team against DORA State of DevOps performance tiers for deployment frequency, lead time for changes, change failure rate (CFR), and mean time to recovery (MTTR).

Each metric gets its own scorecard, evaluated at both the service and team level, using progressive maturity levels: Bronze (Medium), Silver (High), and Gold (Elite).

DORA scorecard overview with team metrics

Review scorecard use cases

  • Set clear, measurable delivery and reliability expectations across engineering teams.
  • Identify services that are falling behind on deployment velocity, lead time, or incident response.
  • Compare team delivery maturity at a glance and drive improvement initiatives.
  • Gate deployments or trigger alerts when services drop below a minimum scorecard level.

Review the scorecard setup flow

  1. Review prerequisites: ensure the DORA metrics data model is in place with the required aggregation and calculation properties on your service and team blueprints.

  2. Understand the thresholds: review the DORA performance tiers and how they map to Port scorecard levels.

  3. Create service-level scorecards: add a scorecard for each DORA metric on the service blueprint, grading individual services.

  4. Create team-level scorecards: add the same scorecards on the team blueprint, with deployment frequency normalized per service for fair comparison.

  5. Visualize scorecard results: build a dashboard with pie charts and tables to monitor DORA maturity across the organization.

Service and team level

This part creates scorecards at two levels:

  • Service level: grades each individual service against DORA benchmarks, giving service owners direct visibility into their delivery performance.
  • Team level: grades teams using the same thresholds, with deployment frequency normalized by the number of services the team owns (so teams of different sizes are compared fairly).

Review scorecard prerequisites

This part assumes the following:

  • You have a Port account and have completed the onboarding process.
  • You have completed the Create foundational Engineering Intelligence data model guide, which provisions the core Organization, Team, and Service blueprints (with team ownership on services) for GitHub, GitLab, and Azure DevOps.
  • You completed the metrics setup in Part 1 of this guide, which configures the required aggregation and calculation properties on the service and _team blueprints.
  • For CFR and MTTR scorecards, you have an incident integration (e.g., PagerDuty) connected as described in the DORA metrics guide.

Understand maturity levels

Port scorecards use four fixed maturity levels. An entity must pass all rules at a given level (and all levels below it) to achieve that level.

LevelDORA TierDescription
BasicLowEntity exists but meets no scorecard criteria
BronzeMediumFoundational: deploys monthly, lead time under a month
SilverHighGood practices: deploys weekly, lead time under a week
GoldEliteExcellence: deploys daily, lead time under a day
Scorecard levels vs. tier labels

Port scorecards use fixed level names (Basic, Bronze, Silver, Gold). The DORA tier calculation properties on your entities use the standard DORA labels (Low, Medium, High, Elite). Both represent the same thresholds as shown in the table above.

Review DORA thresholds

Each scorecard uses thresholds aligned with the DORA State of DevOps benchmarks:

MetricBronze (Medium)Silver (High)Gold (Elite)
Deployment Frequency>= 0.25/week (monthly)>= 1/week>= 7/week (daily)
Lead Time for Changes<= 720 hours (1 month)<= 168 hours (1 week)<= 24 hours (1 day)
Change Failure Rate<= 30%<= 20%<= 5%
MTTR<= 43,200 min (1 month)<= 1,440 min (1 day)<= 60 min (1 hour)
MTTR Bronze maps to DORA Low tier

For deployment frequency, lead time, and CFR, the Bronze threshold aligns with the DORA "Medium" tier. For MTTR however, the Bronze threshold of 1 month is more lenient than the DORA "Medium" boundary (1 day to 1 week), so it maps to the "Low" tier instead. This ensures services with infrequent incidents still receive a baseline score rather than being marked as Basic.

Create scorecards

Each DORA metric is a separate scorecard so you can see individual metric performance as distinct badges on each entity. Select the level you want to configure:

Deployment frequency

Blueprint: Service

#RuleLevelPropertyCondition
1Deploys at least monthlyBronzedeployment_frequency>= 0.25
2Deploys at least weeklySilverdeployment_frequency>= 1
3Deploys at least dailyGolddeployment_frequency>= 7
Provider-agnostic deployment blueprint

The deployment blueprint that backs the deployment_frequency property is provider-agnostic. Deployments from GitHub, GitLab, and Azure DevOps all land in the same blueprint, so all deploy frequency scorecard rules use github_svc_df_* identifiers regardless of which SCM provider you use.

Create the deployment frequency scorecard

  1. Go to the Builder page of your portal.

  2. Search for the Service blueprint and select it.

  3. Click on the Scorecards tab.

  4. Click + New Scorecard.

  5. Click on the {...} Edit JSON button in the top right corner.

  6. Paste the following JSON configuration:

    Service Deployment Frequency scorecard (Click to expand)
    {
    "identifier": "dora_deploy_freq",
    "title": "Deployment Frequency",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "github_svc_df_medium",
    "title": "Deploys at least monthly (>= 0.25/week)",
    "description": "DORA Medium tier - service deploys to production at least once per month.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "deployment_frequency",
    "value": 0.25
    }
    ]
    }
    },
    {
    "identifier": "github_svc_df_high",
    "title": "Deploys at least weekly (>= 1/week)",
    "description": "DORA High tier - service deploys to production at least once per week.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "deployment_frequency",
    "value": 1
    }
    ]
    }
    },
    {
    "identifier": "github_svc_df_elite",
    "title": "Deploys at least daily (>= 7/week)",
    "description": "DORA Elite tier - service deploys to production at least once per day.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": ">=",
    "property": "deployment_frequency",
    "value": 7
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.

Lead time for changes

Blueprint: Service

Multi-provider organizations

If your organization uses services across multiple SCM providers, work through all relevant tabs below. The GitHub tab creates the scorecard, the GitLab and Azure DevOps tabs append rules to the same scorecard. This ensures every service is evaluated for lead time regardless of which provider hosts its repository.

#RuleLevelPropertyCondition
1Lead time under 1 monthBronzegithub_lead_time_for_change<= 720 hours
2Lead time under 1 weekSilvergithub_lead_time_for_change<= 168 hours
3Lead time under 1 dayGoldgithub_lead_time_for_change<= 24 hours

Create the lead time for changes scorecard

  1. Go to the Builder page of your portal.

  2. Search for the Service blueprint and select it.

  3. Click on the Scorecards tab.

  4. Click + New Scorecard.

  5. Click on the {...} Edit JSON button in the top right corner.

  6. Paste the following JSON configuration:

    Service Lead Time for Changes scorecard (Click to expand)
    {
    "identifier": "dora_lead_time",
    "title": "Lead Time for Changes",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "github_svc_lt_medium",
    "title": "Lead time under 1 month (< 720h)",
    "description": "DORA Medium tier - average lead time from PR creation to deployment is under one month.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "github_lead_time_for_change",
    "value": 720
    }
    ]
    }
    },
    {
    "identifier": "github_svc_lt_high",
    "title": "Lead time under 1 week (< 168h)",
    "description": "DORA High tier - average lead time is under one week.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "github_lead_time_for_change",
    "value": 168
    }
    ]
    }
    },
    {
    "identifier": "github_svc_lt_elite",
    "title": "Lead time under 1 day (< 24h)",
    "description": "DORA Elite tier - average lead time is under one day.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "github_lead_time_for_change",
    "value": 24
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.

Change failure rate (CFR)

Requires incident integration

This scorecard requires an incident integration (e.g., PagerDuty) to be configured. Use the incident tracking setup from Tracking incidents before creating this scorecard.

Blueprint: Service

#RuleLevelConditions
1CFR <= 30% and has deploymentsBronzechange_failure_rate <= 30 AND total_deployments > 0
2CFR <= 20% and has deploymentsSilverchange_failure_rate <= 20 AND total_deployments > 0
3CFR <= 5% and has deploymentsGoldchange_failure_rate <= 5 AND total_deployments > 0
Why require total_deployments > 0?

Without the total_deployments > 0 condition, services with no deployments would automatically achieve Gold (since 0 incidents / 0 deployments = no failures). The extra condition ensures only services with real deployment activity are evaluated.

Create the change failure rate scorecard

  1. Go to the Builder page of your portal.

  2. Search for the Service blueprint and select it.

  3. Click on the Scorecards tab.

  4. Click + New Scorecard.

  5. Click on the {...} Edit JSON button in the top right corner.

  6. Paste the following JSON configuration:

    Service Change Failure Rate scorecard (Click to expand)
    {
    "identifier": "dora_cfr",
    "title": "Change Failure Rate",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "svc_cfr_medium",
    "title": "CFR <= 30% (requires deployments)",
    "description": "DORA Medium tier - fewer than 30% of deployments cause incidents.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "change_failure_rate",
    "value": 30
    },
    {
    "operator": ">",
    "property": "total_deployments",
    "value": 0
    }
    ]
    }
    },
    {
    "identifier": "svc_cfr_high",
    "title": "CFR <= 20% (requires deployments)",
    "description": "DORA High tier - fewer than 20% of deployments cause incidents.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "change_failure_rate",
    "value": 20
    },
    {
    "operator": ">",
    "property": "total_deployments",
    "value": 0
    }
    ]
    }
    },
    {
    "identifier": "svc_cfr_elite",
    "title": "CFR <= 5% (requires deployments)",
    "description": "DORA Elite tier - fewer than 5% of deployments cause incidents.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "change_failure_rate",
    "value": 5
    },
    {
    "operator": ">",
    "property": "total_deployments",
    "value": 0
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.

Mean time to recovery (MTTR)

Requires incident integration

This scorecard requires an incident integration (e.g., PagerDuty) to be configured. Use the incident tracking setup from Tracking incidents before creating this scorecard.

Blueprint: Service

#RuleLevelConditions
1MTTR under 1 month and has incidentsBronzemean_time_to_recovery <= 43200 AND total_incidents > 0
2MTTR under 1 day and has incidentsSilvermean_time_to_recovery <= 1440 AND total_incidents > 0
3MTTR under 1 hour and has incidentsGoldmean_time_to_recovery <= 60 AND total_incidents > 0
Why require total_incidents > 0?

Without the total_incidents > 0 condition, services with no incidents would automatically achieve Gold (since there's nothing to recover from). The extra condition ensures only services with real incident activity are evaluated.

Create the MTTR scorecard

  1. Go to the Builder page of your portal.

  2. Search for the Service blueprint and select it.

  3. Click on the Scorecards tab.

  4. Click + New Scorecard.

  5. Click on the {...} Edit JSON button in the top right corner.

  6. Paste the following JSON configuration:

    Service MTTR scorecard (Click to expand)
    {
    "identifier": "dora_mttr",
    "title": "MTTR",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "svc_mttr_low",
    "title": "MTTR under 1 month (< 43200 min, requires incidents)",
    "description": "DORA Low tier - incidents are resolved within one month on average.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "mean_time_to_recovery",
    "value": 43200
    },
    {
    "operator": ">",
    "property": "total_incidents",
    "value": 0
    }
    ]
    }
    },
    {
    "identifier": "svc_mttr_high",
    "title": "MTTR under 1 day (< 1440 min, requires incidents)",
    "description": "DORA High tier - incidents are resolved within one day on average.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "mean_time_to_recovery",
    "value": 1440
    },
    {
    "operator": ">",
    "property": "total_incidents",
    "value": 0
    }
    ]
    }
    },
    {
    "identifier": "svc_mttr_elite",
    "title": "MTTR under 1 hour (< 60 min, requires incidents)",
    "description": "DORA Elite tier - incidents are resolved within one hour on average.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<=",
    "property": "mean_time_to_recovery",
    "value": 60
    },
    {
    "operator": ">",
    "property": "total_incidents",
    "value": 0
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.

What you should see: After creating all four service-level scorecards, each service entity will display four scorecard badges, one for each DORA metric. Services with sufficient data will show Bronze, Silver, or Gold based on their performance. Services without deployment or incident data will show Basic.

DORA scorecard badges on a service entity

Visualize scorecard results

Once the scorecards are in place, create a dashboard to monitor DORA maturity across services and teams.

Create the dashboard

  1. Navigate to your software catalog.

  2. Click on the + button in the left sidebar.

  3. Select New folder.

  4. Name the folder Engineering Intelligence and click Create. If the folder already exists, skip this step.

  5. Inside the Engineering Intelligence folder, click the + button again.

  6. Select New dashboard.

  7. Name the dashboard DORA Scorecards and click Create.

Add widgets

You can populate the dashboard using either an API script or by manually creating each widget through the UI.

The fastest way to set up the dashboard is by using Port's API to create all widgets at once.

Get your Port API token

  1. In your Port application, click on your profile picture .

  2. Select Credentials.

  3. Click Generate API token.

  4. Copy the generated token and store it as an environment variable:

    export PORT_ACCESS_TOKEN="YOUR_GENERATED_TOKEN"
EU region

If your portal is hosted in the EU region, replace api.port.io with api.port-eu.io in the dashboard creation command below.

Create the dashboard with widgets

Save the following JSON to a file named dora_scorecards_dashboard.json:

Dashboard JSON payload (Click to expand)
{
"identifier": "dora_scorecards",
"title": "DORA Scorecards",
"icon": "Star",
"type": "dashboard",
"parent": "engineering_intelligence",
"widgets": [
{
"id": "doraScorecardsDashboardWidget",
"type": "dashboard-widget",
"layout": [
{
"height": 400,
"columns": [
{"id": "svcDeployFreqDistribution", "size": 3},
{"id": "svcLeadTimeDistribution", "size": 3},
{"id": "svcCfrDistribution", "size": 3},
{"id": "svcMttrDistribution", "size": 3}
]
},
{
"height": 400,
"columns": [
{"id": "teamDeployFreqDistribution", "size": 3},
{"id": "teamLeadTimeDistribution", "size": 3},
{"id": "teamCfrDistribution", "size": 3},
{"id": "teamMttrDistribution", "size": 3}
]
},
{
"height": 400,
"columns": [
{"id": "serviceScorecardsTable", "size": 12}
]
},
{
"height": 400,
"columns": [
{"id": "teamScorecardsTable", "size": 12}
]
}
],
"widgets": [
{
"id": "svcDeployFreqDistribution",
"type": "entities-pie-chart",
"title": "Service - Deployment Frequency",
"icon": "Pie",
"description": "Distribution of deployment frequency scorecard levels across services",
"blueprint": "service",
"property": "scorecard#dora_deploy_freq",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "svcLeadTimeDistribution",
"type": "entities-pie-chart",
"title": "Service - Lead Time",
"icon": "Pie",
"description": "Distribution of lead time scorecard levels across services",
"blueprint": "service",
"property": "scorecard#dora_lead_time",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "svcCfrDistribution",
"type": "entities-pie-chart",
"title": "Service - CFR",
"icon": "Pie",
"description": "Distribution of change failure rate scorecard levels across services",
"blueprint": "service",
"property": "scorecard#dora_cfr",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "svcMttrDistribution",
"type": "entities-pie-chart",
"title": "Service - MTTR",
"icon": "Pie",
"description": "Distribution of MTTR scorecard levels across services",
"blueprint": "service",
"property": "scorecard#dora_mttr",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "teamDeployFreqDistribution",
"type": "entities-pie-chart",
"title": "Team - Deployment Frequency",
"icon": "Pie",
"description": "Distribution of deployment frequency scorecard levels across teams",
"blueprint": "_team",
"property": "scorecard#team_dora_deploy_freq",
"dataset": {
"combinator": "and",
"rules": [
{"property": "type", "operator": "=", "value": "team"}
]
}
},
{
"id": "teamLeadTimeDistribution",
"type": "entities-pie-chart",
"title": "Team - Lead Time",
"icon": "Pie",
"description": "Distribution of lead time scorecard levels across teams",
"blueprint": "_team",
"property": "scorecard#team_dora_lead_time",
"dataset": {
"combinator": "and",
"rules": [
{"property": "type", "operator": "=", "value": "team"}
]
}
},
{
"id": "teamCfrDistribution",
"type": "entities-pie-chart",
"title": "Team - CFR",
"icon": "Pie",
"description": "Distribution of change failure rate scorecard levels across teams",
"blueprint": "_team",
"property": "scorecard#team_dora_cfr",
"dataset": {
"combinator": "and",
"rules": [
{"property": "type", "operator": "=", "value": "team"}
]
}
},
{
"id": "teamMttrDistribution",
"type": "entities-pie-chart",
"title": "Team - MTTR",
"icon": "Pie",
"description": "Distribution of MTTR scorecard levels across teams",
"blueprint": "_team",
"property": "scorecard#team_dora_mttr",
"dataset": {
"combinator": "and",
"rules": [
{"property": "type", "operator": "=", "value": "team"}
]
}
},
{
"id": "serviceScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Service - DORA Scorecard Levels",
"icon": "Table",
"description": "DORA scorecard levels per service",
"blueprint": "service",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"service": {
"groupSettings": {"groupBy": ["$team"]},
"propertiesSettings": {
"order": ["$title", "$team", "scorecard#dora_deploy_freq", "deploy_freq_tier", "scorecard#dora_lead_time", "github_lead_time_tier", "gitlab_lead_time_tier", "scorecard#dora_cfr", "cfr_tier", "scorecard#dora_mttr", "mttr_tier"],
"shown": ["$title", "$team", "scorecard#dora_deploy_freq", "deploy_freq_tier", "scorecard#dora_lead_time", "github_lead_time_tier", "gitlab_lead_time_tier", "scorecard#dora_cfr", "cfr_tier", "scorecard#dora_mttr", "mttr_tier"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": [{"property": "scorecard#dora_deploy_freq", "order": "desc"}]}
}
}
},
{
"id": "teamScorecardsTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Team - DORA Scorecard Levels",
"icon": "Table",
"description": "DORA scorecard levels per team",
"blueprint": "_team",
"dataset": {
"combinator": "and",
"rules": [
{"property": "type", "operator": "=", "value": "team"}
]
},
"excludedFields": [],
"blueprintConfig": {
"_team": {
"groupSettings": {"groupBy": []},
"propertiesSettings": {
"order": ["$title", "scorecard#team_dora_deploy_freq", "deploy_freq_tier", "scorecard#team_dora_lead_time", "github_lead_time_tier", "gitlab_lead_time_tier", "scorecard#team_dora_cfr", "cfr_tier", "scorecard#team_dora_mttr", "mttr_tier"],
"shown": ["$title", "scorecard#team_dora_deploy_freq", "deploy_freq_tier", "scorecard#team_dora_lead_time", "github_lead_time_tier", "gitlab_lead_time_tier", "scorecard#team_dora_cfr", "cfr_tier", "scorecard#team_dora_mttr", "mttr_tier"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": [{"property": "scorecard#team_dora_deploy_freq", "order": "desc"}]}
}
}
}
]
}
]
}

Then run the following command to create the dashboard with all widgets:

curl -s -X POST "https://api.port.io/v1/pages" \
-H "Authorization: Bearer $PORT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @dora_scorecards_dashboard.json | python3 -m json.tool
Engineering Intelligence folder

The script assumes an engineering_intelligence folder already exists in your catalog. If you haven't created it yet, follow steps 1-4 in the create the dashboard section first.

What you should see: The completed DORA Scorecards dashboard displays pie charts showing the distribution of scorecard levels across services and teams, followed by table widgets listing each entity with its DORA scorecard grades and tier labels side by side.

DORA scorecards dashboard with service and team maturity widgets

Review design decisions

Compare per-metric scorecards with combined scorecards

Unlike the Delivery Performance scorecard which combines multiple PR metrics into a single scorecard, DORA scorecards are split into four separate scorecards (one per metric). This is intentional:

  • Independent evaluation: A service can be Elite at deployment frequency but Low at lead time. A combined scorecard would mask this by averaging across metrics.
  • Targeted improvement: Teams can focus on the specific metric that needs attention without being penalized by other metrics.
  • Conditional metrics: CFR and MTTR require an incident integration. Separate scorecards allow you to start with deployment frequency and lead time, then add CFR and MTTR when incidents are configured.

Normalize team-level deployment frequency

Team deployment frequency uses a per-service normalized metric (deployment_frequency_per_service). This divides the team's total deployment frequency by the number of services it owns, so a team with 10 services deploying 70 times per week is evaluated the same as a team with 1 service deploying 7 times per week both achieve Gold.

Lead time, CFR, and MTTR thresholds are unchanged at team level since they already represent averages that scale naturally.

Guard CFR and MTTR against missing data

Service CFR rules require total_deployments > 0 and MTTR rules require total_incidents > 0. Team CFR rules use isNotEmpty on change_failure_rate - since change_failure_rate is itself null when there are no deployments, this serves the same purpose. Without these guard conditions, entities with no data would trivially pass all rules and achieve Gold which would be misleading.

Review threshold sources

Thresholds are aligned with the DORA State of DevOps performance tiers as published by Google Cloud's DevOps Research and Assessment program.

Next steps

Once your DORA metrics and scorecard dashboards are in place, consider these additional improvements:

  • Add incident integration (PagerDuty) to unlock change failure rate and MTTR metrics for the full four-metric DORA picture.
  • Create automations to send Slack notifications when a service drops below Bronze on any DORA metric, or create Jira tickets for teams with consistently low deployment frequency.
  • Customize thresholds to match your organization's delivery standards. For example, if daily deployments are not realistic for your team, lower the Gold threshold from 7 deployments per week to 5.
  • Combine DORA scorecards with delivery performance scorecards for a broader view of engineering maturity.
  • Add an AI agent to provide natural language insights into your DORA data directly on the dashboard.