Two Approaches to the Same Problem#

Both Port and Backstage solve the same core problem: giving developers a single interface to discover services, provision infrastructure, and understand the operational state of their systems. They take fundamentally different approaches to getting there.

Backstage is an open-source framework (CNCF Incubating) originally built by Spotify. You deploy and operate it yourself. It provides a plugin architecture and core primitives — you build the portal your organization needs by assembling and configuring plugins.

Port is a commercial SaaS platform. You configure it through a web UI and API. It provides an opinionated data model, built-in scorecards, self-service actions, and integrations that you connect rather than code.

The fundamental tradeoff: Backstage gives you maximum flexibility at the cost of engineering effort. Port gives you faster time-to-value at the cost of some flexibility and a vendor dependency.

Architecture#

Backstage runs as a Node.js application with a React frontend and Express backend. You deploy it on your own infrastructure — typically Kubernetes. It needs PostgreSQL for catalog persistence and plugin data. The application is a monorepo you fork and maintain. Updates come as npm package upgrades that you merge into your fork.

Your Infrastructure
├── Backstage Frontend (React SPA)
├── Backstage Backend (Node.js/Express)
├── PostgreSQL
└── Plugin backends (each may have its own DB tables)

Port runs entirely as SaaS. Your infrastructure runs lightweight agents (exporters) that sync data into Port’s cloud. The UI, API, data storage, and computation all run on Port’s side. You interact through their web UI, REST API, or Terraform provider.

Your Infrastructure                   Port Cloud
├── Port K8s Exporter    ──────>    ├── Data Model
├── Port GitHub App      ──────>    ├── Catalog
├── Port Agent (webhooks)──────>    ├── Scorecards
└── CI/CD (GitHub Actions)──────>   └── Self-Service UI

Operational implication: Backstage is another service your platform team operates — upgrades, database migrations, scaling, monitoring. Port is a SaaS subscription — your operational burden is limited to the exporters and agents.

Catalog Model#

Backstage uses a fixed entity model: Components, APIs, Resources, Systems, Domains, Groups, Users, Locations. Entities are defined in catalog-info.yaml files in your repositories. Backstage discovers them by scanning configured GitHub organizations or explicit location registrations.

# catalog-info.yaml in your repo
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: orders-service
  annotations:
    github.com/project-slug: myorg/orders-service
    pagerduty.com/service-id: P1234XY
spec:
  type: service
  lifecycle: production
  owner: orders-team
  system: commerce
  dependsOn:
    - resource:orders-database
  providesApis:
    - orders-api

The entity types are fixed. You cannot create a “Database” kind or an “Environment” kind without writing a custom processor — which means TypeScript code, not configuration.

Port uses a fully flexible data model. You define Blueprints (entity types) with whatever properties and relations you need. A Blueprint for a microservice, a database, an environment, a deployment — whatever matches your mental model.

{
  "identifier": "microservice",
  "title": "Microservice",
  "schema": {
    "properties": {
      "language": {"type": "string", "enum": ["go", "python", "java", "typescript"]},
      "on_call": {"type": "string"},
      "tier": {"type": "string", "enum": ["critical", "standard", "experimental"]},
      "repository": {"type": "string", "format": "url"},
      "last_deploy": {"type": "string", "format": "date-time"}
    }
  },
  "relations": {
    "team": {"target": "team", "required": true},
    "environment": {"target": "environment", "many": true},
    "depends_on": {"target": "microservice", "many": true}
  }
}

Practical difference: Backstage works well when your model fits its entity kinds. Port works better when you need to model infrastructure, environments, deployments, cloud accounts, or other concepts that do not map cleanly to Backstage’s fixed types.

Scorecards and Standards#

Backstage has the Tech Insights plugin for scorecards. It requires configuring fact collectors (data sources) and checks (rules) in TypeScript. The setup is nontrivial — each data source needs a collector implementation, and checks are coded rather than configured.

Port has built-in scorecards that you configure through the UI or API. Rules are JQ expressions evaluated against entity properties. No code required.

{
  "title": "Production Readiness",
  "rules": [
    {
      "identifier": "has_on_call",
      "title": "Has on-call rotation",
      "level": "Gold",
      "query": {"combinator": "and", "conditions": [
        {"property": "on_call", "operator": "isNotEmpty"}
      ]}
    },
    {
      "identifier": "recent_deploy",
      "title": "Deployed in last 30 days",
      "level": "Silver",
      "query": {"combinator": "and", "conditions": [
        {"property": "last_deploy", "operator": "between",
         "value": {"preset": "lastMonth"}}
      ]}
    }
  ]
}

Port scorecards provide aggregate visibility — you can see “73% of production services have an on-call rotation” at a glance. Backstage Tech Insights can achieve this but requires significantly more setup.

Self-Service Actions#

Backstage provides the Scaffolder for creating new projects from templates. Templates are YAML files with input forms and action steps (fetch template, publish to GitHub, register in catalog, create CI pipeline). The Scaffolder is powerful for project creation but limited for day-2 operations (scaling, promoting, adding resources).

# Backstage Scaffolder template
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: new-service
spec:
  parameters:
    - title: Service Details
      properties:
        name:
          type: string
        owner:
          type: string
          ui:field: OwnerPicker
  steps:
    - id: fetch
      action: fetch:template
      input:
        url: ./skeleton
    - id: publish
      action: publish:github
      input:
        repoUrl: github.com?owner=myorg&repo=${{ parameters.name }}
    - id: register
      action: catalog:register

Port provides Self-Service Actions that can trigger any automation: GitHub Actions, webhooks, Terraform runs, Kubernetes jobs. Actions are available from the entity page — “Scale this service,” “Promote to production,” “Add a database.” The input form, validation, and audit trail are built in.

{
  "identifier": "scale_service",
  "title": "Scale Service",
  "trigger": "DAY-2",
  "invocationMethod": {
    "type": "GITHUB",
    "org": "myorg",
    "repo": "platform-actions",
    "workflow": "scale.yaml"
  },
  "userInputs": {
    "properties": {
      "replicas": {"type": "number", "minimum": 1, "maximum": 20}
    }
  }
}

Practical difference: Port’s actions are more versatile for day-2 operations. Backstage’s Scaffolder excels at day-0 project creation. For ongoing operational actions in Backstage, you need custom plugins or external workflow triggers.

Plugin Ecosystem#

Backstage has 200+ community plugins covering Kubernetes, ArgoCD, PagerDuty, GitHub Actions, Terraform, Datadog, Snyk, SonarQube, and more. Installing a plugin means adding npm packages and wiring them into your Backstage app code. Some plugins are well-maintained; others are abandoned. Plugin compatibility across Backstage versions is a recurring pain point.

Port has pre-built integrations (exporters) for major tools, configured through the UI. The integration surface is narrower than Backstage’s plugin catalog but each integration is maintained by Port and guaranteed to work. For tools without a pre-built integration, you use the REST API or webhook-based ingestion.

Maintenance Burden#

This is where the decision often hinges.

Backstage maintenance reality:

  • Upgrading Backstage means merging upstream changes into your fork. Major version bumps can break plugins.
  • Each plugin has its own release cycle. Plugin A might require Backstage 1.25+ while Plugin B has not been updated past 1.22.
  • You need at least one TypeScript-proficient engineer dedicated to Backstage. Two is safer.
  • Database migrations, Node.js version management, dependency security patches — standard application maintenance.
  • Typical steady-state effort: 0.5-1 FTE for a small installation, 1-2 FTE for a large one.

Port maintenance reality:

  • SaaS — no infrastructure to manage.
  • Exporters and agents need occasional updates but are lightweight.
  • Configuration changes through UI or Terraform provider.
  • Vendor lock-in: your data model, scorecards, and actions are Port-specific. Migration cost is real.
  • Typical steady-state effort: 0.25-0.5 FTE for configuration and integration work.

Decision Framework#

Factor Choose Backstage Choose Port
Team has TypeScript expertise Yes Not required
Need custom entity types Plugin development Built-in
Budget for SaaS Constrained Available
Time to first value Months Weeks
Deep plugin customization Critical requirement Nice to have
Vendor lock-in tolerance Low Acceptable
Platform team size 3+ engineers 1-2 engineers
Organization size 200+ developers Any size

Under 50 developers, no dedicated platform team: Port. You will not sustain a Backstage instance.

50-200 developers, 2-3 platform engineers: Either works. If you have TypeScript skills and want full control, Backstage. If you want faster time-to-value and less maintenance, Port.

200+ developers, dedicated platform team: Backstage gives you the flexibility you will eventually need. Invest in plugin development. Consider Port if your team lacks TypeScript depth or you want to focus engineering effort on golden paths rather than portal maintenance.

Hybrid option: Some organizations run Port as the portal and use Backstage’s Scaffolder independently for project templating — taking the best of both without the full Backstage operational burden.