Platform Engineering: Developer Platforms That Scale
Design internal developer platforms that abstract infrastructure, offer self-service, cut cognitive load on application teams and speed up delivery.

DevOps promised "you build it, you run it." In practice, this means every application team needs expertise in Kubernetes, Terraform, CI/CD pipelines, observability, and security scanning. Platform engineering provides a different answer: build a curated, self-service platform that handles infrastructure complexity so application teams can focus on delivering business value.
The key distinction is that platform teams build products for developers, not tickets for developers.
The Platform as a Product
Treating the platform as a product means understanding your users (developers), their jobs to be done, and measuring whether the platform actually improves their workflow.
interface PlatformCapability {
name: string;
selfService: boolean;
currentState: "manual" | "semi-automated" | "fully-automated";
developerFriction: "high" | "medium" | "low";
usage: number; // requests per week
}
const platformCapabilities: PlatformCapability[] = [
{
name: "Create new service",
selfService: false,
currentState: "manual",
developerFriction: "high",
usage: 5,
},
{
name: "Deploy to production",
selfService: true,
currentState: "fully-automated",
developerFriction: "low",
usage: 200,
},
{
name: "Provision database",
selfService: false,
currentState: "manual",
developerFriction: "high",
usage: 8,
},
{
name: "Add monitoring dashboard",
selfService: false,
currentState: "semi-automated",
developerFriction: "medium",
usage: 15,
},
{
name: "Rotate secrets",
selfService: true,
currentState: "fully-automated",
developerFriction: "low",
usage: 30,
},
];
// Prioritize by friction × frequency
function prioritizeCapabilities(
capabilities: PlatformCapability[]
): PlatformCapability[] {
const frictionScore = { high: 3, medium: 2, low: 1 };
return [...capabilities].sort((a, b) => {
const scoreA = frictionScore[a.developerFriction] * a.usage;
const scoreB = frictionScore[b.developerFriction] * b.usage;
return scoreB - scoreA;
});
}Service Templates and Golden Paths
The most impactful platform feature is service scaffolding. Instead of copying and pasting from existing services and fixing configuration for days, developers get a production-ready service in minutes.
// ❌ The copy-paste-and-pray approach
// 1. Clone existing service repo
// 2. Rename everything (miss some references)
// 3. Update CI/CD config (copy wrong secrets)
// 4. Modify Kubernetes manifests (break resource limits)
// 5. Change database config (point to wrong cluster)
// 6. Spend 3 days debugging why deployment fails// ✅ Platform-provided service templates
interface ServiceTemplate {
name: string;
language: string;
includes: string[];
parameters: TemplateParameter[];
}
interface TemplateParameter {
name: string;
description: string;
type: "string" | "select" | "boolean";
options?: string[];
defaultValue?: string;
required: boolean;
}
const templates: ServiceTemplate[] = [
{
name: "Node.js API Service",
language: "typescript",
includes: [
"Express server with health checks",
"OpenTelemetry instrumentation",
"Structured logging (pino)",
"Dockerfile with multi-stage build",
"Kubernetes manifests (deployment, service, HPA)",
"CI/CD pipeline (build, test, deploy)",
"Grafana dashboard template",
"Alert rules for SLOs",
"README with runbook links",
],
parameters: [
{
name: "serviceName",
description: "Name of the service (lowercase, hyphens)",
type: "string",
required: true,
},
{
name: "team",
description: "Owning team",
type: "select",
options: ["payments", "catalog", "identity", "notifications"],
required: true,
},
{
name: "database",
description: "Database type",
type: "select",
options: ["postgresql", "none"],
defaultValue: "postgresql",
required: false,
},
{
name: "messageQueue",
description: "Include message queue consumer",
type: "boolean",
defaultValue: "false",
required: false,
},
],
},
];The golden path isn't mandatory—it's the easiest option. Teams can diverge if they have good reasons, but the default path is so well-paved that most teams prefer it.
The Platform API Layer
A platform API provides programmatic access to infrastructure operations. This enables both the self-service portal and automated workflows.
interface PlatformAPI {
services: {
create(config: ServiceConfig): Promise<ServiceInstance>;
deploy(serviceId: string, version: string): Promise<Deployment>;
scale(serviceId: string, replicas: number): Promise<void>;
getStatus(serviceId: string): Promise<ServiceStatus>;
};
databases: {
provision(config: DatabaseConfig): Promise<DatabaseInstance>;
createBackup(dbId: string): Promise<BackupInfo>;
restoreBackup(dbId: string, backupId: string): Promise<void>;
};
secrets: {
set(key: string, value: string, scope: string): Promise<void>;
rotate(key: string): Promise<void>;
listScopes(): Promise<string[]>;
};
observability: {
createDashboard(config: DashboardConfig): Promise<string>;
createAlert(config: AlertConfig): Promise<string>;
};
}
interface ServiceConfig {
name: string;
team: string;
template: string;
environment: "dev" | "staging" | "production";
resources: {
cpu: string;
memory: string;
replicas: number;
};
}
interface ServiceStatus {
name: string;
environment: string;
currentVersion: string;
replicas: { desired: number; ready: number };
health: "healthy" | "degraded" | "unhealthy";
lastDeployment: {
version: string;
timestamp: Date;
status: "success" | "failed" | "rolling-back";
};
}Developer Experience Metrics
Platform success is measured by developer productivity, not infrastructure metrics. Track how long common workflows take and where developers get stuck.
interface DeveloperExperienceMetrics {
timeToFirstDeploy: number; // minutes: new service → production
deploymentFrequency: number; // deploys per team per week
leadTimeForChanges: number; // minutes: commit → production
ticketsToInfraTeam: number; // per team per month
platformAdoptionRate: number; // % of services using golden paths
developerSatisfaction: number; // quarterly survey, 1-5
}
function assessPlatformMaturity(
metrics: DeveloperExperienceMetrics
): { level: string; nextSteps: string[] } {
const nextSteps: string[] = [];
if (metrics.timeToFirstDeploy > 480) {
nextSteps.push(
`Time to first deploy is ${metrics.timeToFirstDeploy} min. ` +
"Target: under 60 min via service templates."
);
}
if (metrics.ticketsToInfraTeam > 10) {
nextSteps.push(
`${metrics.ticketsToInfraTeam} infra tickets/month per team. ` +
"Identify top ticket categories and build self-service for them."
);
}
if (metrics.platformAdoptionRate < 0.7) {
nextSteps.push(
`Only ${(metrics.platformAdoptionRate * 100).toFixed(0)}% adoption. ` +
"Interview non-adopters to understand gaps."
);
}
if (metrics.developerSatisfaction < 3.5) {
nextSteps.push(
`Satisfaction at ${metrics.developerSatisfaction}/5. ` +
"Run developer experience interviews for qualitative feedback."
);
}
const level =
nextSteps.length === 0
? "mature"
: nextSteps.length <= 2
? "growing"
: "foundational";
return { level, nextSteps };
}Avoiding Platform Team Anti-Patterns
Platform teams fail when they build an internal cloud provider instead of an opinionated workflow.
interface PlatformAntiPattern {
pattern: string;
symptom: string;
fix: string;
}
const antiPatterns: PlatformAntiPattern[] = [
{
pattern: "Building AWS-on-AWS",
symptom: "Platform exposes raw Terraform/Kubernetes primitives to developers",
fix: "Abstract to workflow-level operations: 'deploy my service' not 'create an ingress'",
},
{
pattern: "Ticket-driven platform",
symptom: "Developers file tickets and wait for platform team to act",
fix: "Build self-service APIs and UIs; platform team builds tools, not fulfills requests",
},
{
pattern: "Mandatory adoption without value",
symptom: "Teams forced to use platform tools that make their workflow worse",
fix: "Make the platform the easiest path, not the required path",
},
{
pattern: "Building without user research",
symptom: "Platform features nobody asked for, missing features everyone needs",
fix: "Embed with application teams; watch them work; fix their actual pain points",
},
{
pattern: "No documentation or onboarding",
symptom: "Only platform team members can use the platform effectively",
fix: "Write getting-started guides, record walkthroughs, staff office hours",
},
];Key Takeaways
Platform engineering succeeds when you treat the platform as a product with developer teams as your users. Start by measuring where developers spend time on toil—provisioning services, debugging deployment pipelines, wiring observability—and build self-service automation for the highest-friction, highest-frequency workflows first. Provide golden path templates that give teams a production-ready starting point with observability, CI/CD, and security built in. Expose a platform API that powers both self-service UIs and automated workflows. Measure success through developer experience metrics: time to first deploy, infra ticket volume, and developer satisfaction. The platform team that ships the fewest features but eliminates the most developer toil is the one that succeeds.


