Skip to main content

Check out Port for yourself ➜ 

Set up a service production readiness scorecard

Scorecards let you define maturity levels for your services, giving platform engineers and R&D managers a clear view of which services meet your organization's readiness standards and which need attention. This guide demonstrates how to set up a service production readiness scorecard using data from GitHub, GitLab, or Azure DevOps integrations, evaluating services against progressive maturity levels: Bronze, Silver, and Gold.

Common use cases

  • Define clear, enforceable standards for service ownership and documentation.
  • Identify unmaintained or undocumented services before they become incidents.
  • Gate deployments or trigger alerts when services drop below a minimum readiness level.
  • Give developers a self-service view of exactly what their service is missing.

Prerequisites

This guide assumes the following:

  • You have a Port account and have completed the onboarding process.
  • You have installed a GitHub, GitLab, or Azure DevOps integration.
  • You have verified that the service blueprint has values for the repository metadata used in this scorecard (url, readme, gitignore, language, pr_template, visibility, and codeowners) in your environment.
Optional extensions

All core rules in this scorecard use repository metadata that your git integration can supply. Depending on your provider and mapping, you might need to map some properties to service first. The extend with additional integrations section describes how to enrich the scorecard with data from other tools once you have them installed.

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.

LevelOrderDescription
Basic0 (default)Entity exists but meets no scorecard criteria
Bronze1Foundational hygiene: ownership and documentation present
Silver2Good practices: actively maintained, metadata complete
Gold3Excellence: strict governance and high activity

Create the scorecard

Blueprint: Service

All rules in the core scorecard use repository metadata on the service blueprint that should be populated from your GitHub, GitLab, or Azure DevOps integration mapping.

#RuleLevelPropertyConditionRequires
1Has repository URLBronzeurlis not emptyGit integration
2Has READMEBronzereadmeis not emptyGit integration
3Has team assignedBronzeteam (relation)is not emptyGit integration
4Has .gitignoreBronzegitignoreis not emptyGit integration
5Has language definedBronzelanguageis not emptyGit integration
6Has PR templateSilverpr_templateis not emptyGit integration
7Private repositorySilvervisibility= "private"Git integration
8Has criticality definedSilvercriticalityis not emptyGit integration
9Active repo (last 30 days)Silverdays_since_last_push< 30Git integration
10Has CODEOWNERSGoldcodeownersis not emptyGit integration
11Active repo (last 7 days)Golddays_since_last_push< 7Git integration

Steps

  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. If the production_readiness scorecard does not exist, click + New Scorecard. If it already exists, open it instead.

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

  6. Paste the following JSON configuration:

    Service Production Readiness scorecard (click to expand)
    {
    "identifier": "production_readiness",
    "title": "Service Production Readiness",
    "levels": [
    {
    "color": "paleBlue",
    "title": "Basic"
    },
    {
    "color": "bronze",
    "title": "Bronze"
    },
    {
    "color": "silver",
    "title": "Silver"
    },
    {
    "color": "gold",
    "title": "Gold"
    }
    ],
    "rules": [
    {
    "identifier": "has_url",
    "title": "Has repository URL",
    "description": "Service has a repository URL set, confirming it is linked to source control.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "url"
    }
    ]
    }
    },
    {
    "identifier": "has_readme",
    "title": "Has README",
    "description": "Service has a README file in the repository, providing basic documentation.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "readme"
    }
    ]
    }
    },
    {
    "identifier": "has_team",
    "title": "Has team assigned",
    "description": "Service has a team relation set, ensuring clear ownership.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "relation": "team"
    }
    ]
    }
    },
    {
    "identifier": "has_gitignore",
    "title": "Has .gitignore",
    "description": "Repository has a .gitignore file, preventing accidental commits of build artifacts or secrets.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "gitignore"
    }
    ]
    }
    },
    {
    "identifier": "has_language",
    "title": "Has language defined",
    "description": "Repository has a detected primary language, useful for routing and tooling decisions.",
    "level": "Bronze",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "language"
    }
    ]
    }
    },
    {
    "identifier": "has_pr_template",
    "title": "Has PR template",
    "description": "Repository includes a pull request template to guide contributors.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "pr_template"
    }
    ]
    }
    },
    {
    "identifier": "private_visibility",
    "title": "Private repository",
    "description": "Repository visibility is private, reducing exposure of internal code.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "=",
    "property": "visibility",
    "value": "private"
    }
    ]
    }
    },
    {
    "identifier": "has_criticality",
    "title": "Has criticality defined",
    "description": "Service has a criticality level set, enabling incident routing and risk prioritization.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "criticality"
    }
    ]
    }
    },
    {
    "identifier": "active_repo_30d",
    "title": "Active repo (last 30 days)",
    "description": "Repository has been pushed to within the last 30 days, indicating it is actively maintained.",
    "level": "Silver",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<",
    "property": "days_since_last_push",
    "value": 30
    }
    ]
    }
    },
    {
    "identifier": "has_codeowners",
    "title": "Has CODEOWNERS",
    "description": "Repository has a CODEOWNERS file, enforcing mandatory code review by designated owners.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "isNotEmpty",
    "property": "codeowners"
    }
    ]
    }
    },
    {
    "identifier": "active_repo_7d",
    "title": "Active repo (last 7 days)",
    "description": "Repository has been pushed to within the last 7 days — elite activity level.",
    "level": "Gold",
    "query": {
    "combinator": "and",
    "conditions": [
    {
    "operator": "<",
    "property": "days_since_last_push",
    "value": 7
    }
    ]
    }
    }
    ]
    }
  7. Click Save to create the scorecard.

Property availability

days_since_last_push is a calculated property on service and requires last_push to be populated. Properties like codeowners, pr_template, gitignore, and visibility come from your git integration mapping. criticality is typically set by your team as service metadata. Newly ingested services might show Basic until the integration completes its first sync and your mappings populate these fields.

Extend with additional integrations

The core scorecard covers everything a git integration provides. Once you install other tools, you can append additional rules to the rules array to capture signals from those systems. Some examples:

  • PagerDuty: check that each service has an active on-call assignment (pagerduty_oncall is not empty). Useful as a Gold-level requirement for production services.
  • SonarQube: enforce minimum code coverage, cap the number of open code smells, or require a passing security rating.
  • Snyk: gate Silver or Gold on having zero critical vulnerabilities or a clean dependency audit.
  • Datadog / New Relic: require that services have APM monitoring configured as a Gold-level requirement.
  • CI coverage by provider: optionally add a CI rule using your provider-specific property (for example, workflow_count for GitHub or pipeline_count for GitLab) after you verify the property exists on service.

To add a rule, go to the Service blueprint in the Builder, open the Scorecards tab, click Service Production Readiness → Edit JSON, and append the new rule object to the rules array.

Visualize scorecard results

Once the scorecard is in place, create a dashboard to monitor production readiness across all services.

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.
  5. Inside the Engineering Intelligence folder, click + again.
  6. Select New dashboard.
  7. Name the dashboard Service Production Readiness 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 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

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

Dashboard JSON payload (click to expand)
{
"identifier": "production_readiness_scorecards",
"title": "Service Production Readiness Scorecards",
"icon": "Shield",
"type": "dashboard",
"parent": "engineering_intelligence",
"widgets": [
{
"id": "prScorecardsDashboardWidget",
"type": "dashboard-widget",
"layout": [
{
"height": 400,
"columns": [
{"id": "serviceLevelDistribution", "size": 6},
{"id": "servicesBelowBronze", "size": 6}
]
},
{
"height": 400,
"columns": [
{"id": "serviceReadinessTable", "size": 12}
]
}
],
"widgets": [
{
"id": "serviceLevelDistribution",
"type": "entities-pie-chart",
"title": "Service Production Readiness Level Distribution",
"icon": "Pie",
"description": "Distribution of production readiness scorecard levels across services",
"blueprint": "service",
"property": "scorecard#production_readiness",
"dataset": {
"combinator": "and",
"rules": []
}
},
{
"id": "servicesBelowBronze",
"type": "entities-number-chart",
"title": "Services Below Bronze",
"icon": "Alert",
"description": "Number of services that have not reached Bronze readiness level",
"blueprint": "service",
"chartType": "countEntities",
"calculationBy": "entities",
"func": "count",
"unit": "none",
"dataset": {
"combinator": "and",
"rules": [
{
"operator": "=",
"property": "scorecard#production_readiness",
"value": "Basic"
}
]
}
},
{
"id": "serviceReadinessTable",
"type": "table-entities-explorer",
"displayMode": "widget",
"title": "Service Production Readiness Overview",
"icon": "Table",
"description": "Services with their production readiness scorecard level and key hygiene indicators",
"blueprint": "service",
"dataset": {"combinator": "and", "rules": []},
"excludedFields": [],
"blueprintConfig": {
"service": {
"groupSettings": {"groupBy": ["team"]},
"propertiesSettings": {
"order": ["$title", "team", "scorecard-property#production_readiness", "readme", "codeowners", "criticality", "visibility", "days_since_last_push", "language"],
"shown": ["$title", "team", "scorecard-property#production_readiness", "readme", "codeowners", "criticality", "visibility", "days_since_last_push", "language"]
},
"filterSettings": {"filterBy": {"combinator": "and", "rules": []}},
"sortSettings": {"sortBy": []}
}
}
}
]
}
]
}

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 @pr_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.

Next steps

  • Add automations: Use Port automations to notify teams via Slack when their service drops below Bronze, or create Jira tickets for services missing ownership.
  • Customize thresholds: The active_repo_30d and active_repo_7d rules use days_since_last_push. Adjust these values to match your team's release cadence.
  • Combine with delivery metrics: Combine this scorecard with the Measure PR delivery metrics guide to get both hygiene and velocity signals in one dashboard.