Port and Kratix: Internal Developer Platforms Beyond Backstage
Backstage is a developer portal. Port and Kratix go further - they’re platforms for building platforms. Port focuses on the catalog and self-service actions. Kratix focuses on composable infrastructure delivery.
This guide covers when to use each and how to implement them.
TL;DR
- Port: SaaS developer portal with actions and scorecards
- Kratix: Self-hosted platform framework with Promises
- Backstage for catalog + docs, Port for actions + metrics
- Kratix for GitOps-native infrastructure delivery
- All can work together
Port: Self-Service Developer Portal
Port is a SaaS platform for building developer portals with self-service capabilities.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Port │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Software │ │ Self-Svc │ │ Scorecards │ │
│ │ Catalog │ │ Actions │ │ (Production Readiness) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ GitHub │ │ Kubernetes │ │ Slack │
│ │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘
Define Blueprints
{
"identifier": "service",
"title": "Service",
"icon": "Microservice",
"schema": {
"properties": {
"language": {
"type": "string",
"enum": ["Go", "Python", "Node.js", "Java"]
},
"tier": {
"type": "string",
"enum": ["critical", "standard", "experimental"]
},
"owner": {
"type": "string"
},
"repository": {
"type": "string",
"format": "url"
},
"slackChannel": {
"type": "string"
},
"onCall": {
"type": "string"
},
"productionReadiness": {
"type": "number",
"minimum": 0,
"maximum": 100
}
},
"required": ["language", "tier", "owner"]
},
"relations": {
"environment": {
"target": "environment",
"many": true
},
"dependencies": {
"target": "service",
"many": true
}
}
}
Self-Service Actions
{
"identifier": "create_service",
"title": "Create New Service",
"icon": "Plus",
"trigger": {
"type": "self-service",
"userInputs": {
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$"
},
"language": {
"type": "string",
"enum": ["Go", "Python", "Node.js"]
},
"tier": {
"type": "string",
"enum": ["critical", "standard"]
},
"includeDatabase": {
"type": "boolean",
"default": false
}
},
"required": ["name", "language", "tier"]
}
},
"invocationMethod": {
"type": "GITHUB",
"org": "company",
"repo": "platform-actions",
"workflow": "create-service.yaml"
}
}
GitHub Action Backend
# .github/workflows/create-service.yaml
name: Create Service
on:
workflow_dispatch:
inputs:
name:
required: true
language:
required: true
tier:
required: true
includeDatabase:
required: false
default: 'false'
port_run_id:
required: true
jobs:
create:
runs-on: ubuntu-latest
steps:
- name: Notify Port - Running
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
runId: ${{ inputs.port_run_id }}
status: "RUNNING"
- name: Create Repository
uses: actions/github-script@v6
with:
script: |
await github.rest.repos.createUsingTemplate({
template_owner: 'company',
template_repo: '${{ inputs.language }}-service-template',
name: '${{ inputs.name }}',
owner: 'company',
private: true
})
- name: Create Database (if requested)
if: inputs.includeDatabase == 'true'
run: |
# Trigger Crossplane claim or Terraform
kubectl apply -f - <<EOF
apiVersion: database.platform.company.com/v1alpha1
kind: PostgreSQLInstance
metadata:
name: ${{ inputs.name }}-db
namespace: platform
spec:
size: small
EOF
- name: Register in Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: CREATE
blueprint: service
identifier: ${{ inputs.name }}
properties: |
{
"language": "${{ inputs.language }}",
"tier": "${{ inputs.tier }}",
"repository": "https://github.com/company/${{ inputs.name }}"
}
- name: Notify Port - Complete
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
runId: ${{ inputs.port_run_id }}
status: "SUCCESS"
summary: "Service ${{ inputs.name }} created successfully"
Scorecards
{
"identifier": "production_readiness",
"title": "Production Readiness",
"rules": [
{
"identifier": "has_readme",
"title": "Has README",
"level": "Bronze",
"query": {
"property": "hasReadme",
"operator": "=",
"value": true
}
},
{
"identifier": "has_monitoring",
"title": "Has Monitoring",
"level": "Silver",
"query": {
"property": "hasMonitoring",
"operator": "=",
"value": true
}
},
{
"identifier": "has_runbook",
"title": "Has Runbook",
"level": "Silver",
"query": {
"property": "runbookUrl",
"operator": "isNotEmpty"
}
},
{
"identifier": "slo_defined",
"title": "SLO Defined",
"level": "Gold",
"query": {
"property": "sloAvailability",
"operator": ">",
"value": 0
}
}
]
}
Kratix: Composable Platform Framework
Kratix lets you define “Promises” - self-service capabilities that developers can request. It’s GitOps-native and works with any Kubernetes resources.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Platform Cluster │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Kratix Controller ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ ││
│ │ │ Promise │ │ Promise │ │ Promise │ ││
│ │ │ (Database) │ │ (Logging) │ │ (Environment) │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│
▼ GitOps (Flux/Argo)
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Worker 1 │ │ Worker 2 │ │ Worker 3 │
│ (Dev) │ │ (Staging) │ │ (Prod) │
└────────────┘ └────────────┘ └────────────┘
Define a Promise
# promise-postgresql.yaml
apiVersion: platform.kratix.io/v1alpha1
kind: Promise
metadata:
name: postgresql
spec:
# What developers request
api:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresqls.database.platform.company.com
spec:
group: database.platform.company.com
names:
kind: PostgreSQL
plural: postgresqls
singular: postgresql
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: string
enum: ["small", "medium", "large"]
version:
type: string
default: "15"
required:
- size
# Pipeline to process requests
workflows:
resource:
configure:
- apiVersion: platform.kratix.io/v1alpha1
kind: Pipeline
metadata:
name: configure-postgresql
spec:
containers:
- name: generate-manifests
image: company/postgresql-pipeline:latest
command:
- /bin/sh
- -c
- |
# Read request
SIZE=$(yq '.spec.size' /kratix/input/object.yaml)
VERSION=$(yq '.spec.version' /kratix/input/object.yaml)
NAME=$(yq '.metadata.name' /kratix/input/object.yaml)
NAMESPACE=$(yq '.metadata.namespace' /kratix/input/object.yaml)
# Map size to resources
case $SIZE in
small) CPU=500m; MEM=1Gi; STORAGE=10Gi ;;
medium) CPU=1; MEM=2Gi; STORAGE=50Gi ;;
large) CPU=2; MEM=4Gi; STORAGE=100Gi ;;
esac
# Generate CloudNativePG cluster
cat > /kratix/output/cluster.yaml <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: ${NAME}
namespace: ${NAMESPACE}
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:${VERSION}
storage:
size: ${STORAGE}
resources:
requests:
cpu: ${CPU}
memory: ${MEM}
monitoring:
enablePodMonitor: true
EOF
Developer Usage
# database-request.yaml
apiVersion: database.platform.company.com/v1
kind: PostgreSQL
metadata:
name: my-app-db
namespace: my-team
spec:
size: medium
version: "15"
Multi-Cluster Delivery
# kratix-destination.yaml
apiVersion: platform.kratix.io/v1alpha1
kind: Destination
metadata:
name: production
spec:
stateStoreRef:
name: production-gitops
kind: GitStateStore
strictMatchLabels: true
---
apiVersion: platform.kratix.io/v1alpha1
kind: GitStateStore
metadata:
name: production-gitops
spec:
url: https://github.com/company/gitops-production
branch: main
secretRef:
name: github-credentials
Comparison
FEATURE PORT KRATIX BACKSTAGE
======= ==== ====== =========
Hosting SaaS Self-hosted Self-hosted
Catalog ✅ ❌ ✅
Self-Service Actions ✅ (built-in) ✅ (Promises) ✅ (plugins)
Scorecards ✅ ❌ ✅ (plugins)
GitOps Native ✅ (GitHub) ✅ ❌
Infrastructure Actions Native Plugins
Learning Curve Low Medium High
Customization Medium High Very High
When to Use Each
Port:
- Quick time-to-value
- Non-technical stakeholders need visibility
- Scorecards and compliance tracking
- Existing GitHub/GitLab workflows
Kratix:
- GitOps-native infrastructure delivery
- Multi-cluster deployments
- Complex infrastructure composition
- Want to own the platform
Backstage:
- Need extensive customization
- Strong frontend development capability
- Want to own the entire portal
References
- Port Docs: https://docs.getport.io
- Kratix Docs: https://kratix.io/docs
- Backstage: https://backstage.io