Skip to main content
Private preview. fremforge is in private preview — invited customers only. Content is still subject to change. Request access →

DORA dashboard

The DORA dashboard surfaces the four metrics from the DevOps Research and Assessment (DORA) State of DevOps report, computed live per-tenant from a dora_deployments table. Visible at Org admin → DORA.

No external service. No data sent to a third-party SaaS dashboard. The numbers are computed on the same Postgres instance that holds your repos, audit log, and findings — and they share the same EU residency and tenant-isolation guarantees as everything else under /<slug>/_admin/*.

The four metrics

MetricWhat it measuresHow fremforge computes it
Deployment frequencyHow often you ship to productionCount of dora_deployments rows where environment = 'production' AND success = true, divided by the rolling window. Reported as deployments/day, then bucketed into the DORA categories (Elite: ≥1/day, High: 1/day–1/week, Medium: 1/week–1/month, Low: <1/month).
Lead time for changesTime from code-commit to deploydeployed_at − committed_at, p50 + p95 over the window. Only counts deployments that supplied a commit_sha + committed_at. Reported in hours/days.
Change-failure rateShare of deployments that fail / require a fixcount(success=false OR is_rework=true) / count(*) over the window. Expressed as a percentage.
Mean time to restore (MTTR)Time to recover from a failed deploymentFor each success=false event, the wall-clock from deployed_at to the next successful deployment to the same (repo, environment). Reported as median.

Window defaults to 30 days rolling. Configurable at Org admin → DORA → Settings between 7d / 30d / 90d.

How deployments get into the table

Two ingest paths run side-by-side. Use whichever matches your release shape; many tenants use both.

1. Forgejo release webhook (automatic)

When a repo creates a Forgejo release (annotated tag v* or any release object published through the UI / POST /api/v1/repos/{owner}/{repo}/releases), fremforge auto-ingests one dora_deployments row with:

  • environment = production (default)
  • deployed_at = release created_at
  • commit_sha = release target_commitish resolved to a SHA
  • committed_at = the commit’s author.date
  • success = true
  • external_ref = release:{release_id} (idempotent — replay the webhook safely)

No customer wiring required. The webhook target is configured at the platform level; per-org enablement is automatic.

2. POST /api/v1/dora/deployments (custom — recommended for richer telemetry)

For tenants that don’t tag releases (continuous deployment, GitOps push-on-merge, blue/green flips) the auto-ingest misses every deploy. POST one record per deployment from your CI workflow:

curl -X POST https://frem.sh/api/v1/dora/deployments \
  -H "Authorization: Bearer ffp_<your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "repo_full_name": "acme/web",
    "environment": "production",
    "deployed_at": "2026-05-14T10:00:00Z",
    "commit_sha": "abc1234...",
    "committed_at": "2026-05-14T09:30:00Z",
    "success": true,
    "external_ref": "deploy-12345"
  }'

Required: repo_full_name, environment, deployed_at.

Optional but recommended:

  • commit_sha + committed_at — without these, the row drops out of the lead-time calculation entirely. The other three metrics still count it.
  • success (defaults true) — set false on rollback / failed deploy. Drives change-failure rate + MTTR.
  • external_ref — dedupe key, tenant-scoped. The endpoint is idempotent on (tenant_id, repo_full_name, environment, external_ref): replay the call from a retried CI job, get a 200 with the existing row’s id rather than a duplicate insert. Omit if you don’t want dedupe.
  • is_rework (defaults false) — flag this as a hotfix / revert / follow-up. Counts toward change-failure rate even when success=true. Drives the 5th metric (Rework Rate) per the 2025 DORA report.

Required scope on the token: findings:write — same scope CI workflows use for dep-scan + SBOM ingest. Mint at Org admin → API tokens (or use token exchange from your customer IdP for a short-lived token).

Response shape (201 on first insert, 200 on dedupe-hit):

{
  "id": "0191e3b6-0000-7000-0000-000000000001",
  "tenant_id": "0191e3b6-0000-7000-0000-000000000000",
  "repo_full_name": "acme/web",
  "environment": "production",
  "deployed_at": "2026-05-14T10:00:00Z",
  "deduped": false
}

Example workflow integrations

GitHub-style Actions workflow on fremforge

# .forgejo/workflows/dora-ingest.yaml
name: Report deploy to DORA
on:
  workflow_run:
    workflows: [Deploy to production]
    types: [completed]

jobs:
  report:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: fremforge
    steps:
      - uses: actions/checkout@v4
      - name: POST dora deployment
        run: |
          curl -X POST https://frem.sh/api/v1/dora/deployments \
            -H "Authorization: Bearer ${{ secrets.FREMFORGE_API_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{
              "repo_full_name": "${{ github.repository }}",
              "environment": "production",
              "deployed_at": "'"$(date -u +%FT%TZ)"'",
              "commit_sha": "${{ github.sha }}",
              "committed_at": "'"$(git show -s --format=%cI HEAD)"'",
              "success": true,
              "external_ref": "actions-run:${{ github.run_id }}"
            }'

Argo CD post-sync hook

# argocd-postsync-dora.yaml
apiVersion: batch/v1
kind: Job
metadata:
  generateName: argocd-dora-
  annotations:
    argocd.argoproj.io/hook: PostSync
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: dora-report
          image: curlimages/curl:8.5.0
          envFrom:
            - secretRef: { name: fremforge-dora-token }
          args:
            - sh
            - -c
            - |
              curl -sS -X POST https://frem.sh/api/v1/dora/deployments \
                -H "Authorization: Bearer $FREMFORGE_API_TOKEN" \
                -H "Content-Type: application/json" \
                -d "{
                  \"repo_full_name\": \"$REPO\",
                  \"environment\": \"$ENV\",
                  \"deployed_at\": \"$(date -u +%FT%TZ)\",
                  \"commit_sha\": \"$ARGOCD_APP_REVISION\",
                  \"success\": true,
                  \"external_ref\": \"argocd:$ARGOCD_APP_NAME:$ARGOCD_APP_REVISION\"
                }"

Marking a deploy as a rollback

curl -X POST https://frem.sh/api/v1/dora/deployments \
  -H "Authorization: Bearer ffp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "repo_full_name": "acme/web",
    "environment": "production",
    "deployed_at": "2026-05-14T10:30:00Z",
    "success": false,
    "is_rework": true,
    "external_ref": "rollback-from-deploy-12345"
  }'

This row drops change-failure rate AND starts the MTTR clock. The next success=true deploy to (acme/web, production) closes it.

Reading the dashboard

The admin page shows the latest computed values for the current window plus a 30-day sparkline per metric. Hover any data point for the underlying deploy list. Click a metric to drill into the per-repo breakdown.

The DORA categories (Elite / High / Medium / Low) for each metric are computed live against the Accelerate thresholds — the same bands the Google Cloud DORA report uses.

What we deliberately don’t do

  • No team-level aggregation in v1. DORA’s “Team” axis requires per-deploy team-attribution, which isn’t reliably derivable from repo_full_name alone in monorepo + multi-tenant deploys. Available via the per-repo breakdown today; first-class team aggregation is on the roadmap.
  • No outside-the-tenant comparison. Industry benchmarks aren’t shown — comparison would require sending your numbers to a third-party aggregator, which conflicts with the EU-residency contract. Use the public DORA report for “where do we stand vs. the industry” framing.
  • No automatic “incident” linkage. MTTR is computed from your success=false → next success=true pair. We don’t try to infer incident windows from PagerDuty, Statuspage, or Forgejo issue activity — the customer’s deploy success/failure signal is the authoritative input.

Related

  • Audit log — every DORA ingest emits an audit event (api.dora.deployments.ingested) so an operator can verify the lineage of any displayed number.
  • Agent-native authentication — token-exchange a customer-IdP JWT for a short-lived findings:write token if you don’t want to maintain a long-lived PAT.
  • API tokens — mint long-lived findings:write PATs for CI workflows at User settings → API tokens; see Authentication policy for org-wide max-lifetime + rotation policy.