TL;DR
Master Platform Engineering with this comprehensive guide covering internal developer platforms, self-service capabilities, and best practices for modern software delivery
Platform Engineering: Building Modern Developer Platforms
Platform Engineering focuses on building and maintaining scalable, self-service platforms that enhance developer productivity and operational efficiency. This guide explores the principles, patterns, and practices for creating effective internal developer platforms.
$1
`` graph TB
subgraph "Developer Experience"
A[Self-service Portal]
B[CLI Tools]
C[API Gateway]
end
subgraph "Platform Core"
D[Service Catalog]
E[Policy Engine]
F[Automation Engine]
end
subgraph "Infrastructure"
G[Cloud Resources]
H[Kubernetes Clusters]
I[Database Services]
end
A --> C
B --> C
C --> D
D --> E
E --> F
F --> G
F --> H
F --> I
classDef dev fill:#1a73e8,stroke:#fff,color:#fff
classDef core fill:#34a853,stroke:#fff,color:#fff
classDef infra fill:#fbbc04,stroke:#fff,color:#fff
class A,B,C dev
class D,E,F core
class G,H,I infra
mermaid
`
$1
$1
` apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: web-service
description: Web Service Template
tags:
- nodejs
- typescript
spec:
type: service
lifecycle: production
owner: team-a
system: web-platform
providesApis:
- web-api
consumesApis:
- auth-api
dependsOn:
- resource:database
- resource:cache
yaml
`service-catalog.yaml
$1
` // infrastructure.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as k8s from "@pulumi/kubernetes"; export class ServiceInfrastructure extends pulumi.ComponentResource {
constructor(name: string, args: any, opts?: pulumi.ComponentResourceOptions) {
super("custom:service:Infrastructure", name, args, opts); // Create VPC
const vpc = new aws.ec2.Vpc( cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
}); // Create EKS Cluster
const cluster = new aws.eks.Cluster( roleArn: args.roleArn,
vpcConfig: {
subnetIds: vpc.publicSubnets.map(s => s.id),
},
}); // Deploy Service
const deployment = new k8s.apps.v1.Deployment( metadata: { namespace: args.namespace },
spec: {
replicas: 3,
selector: { matchLabels: { app: name } },
template: {
metadata: { labels: { app: name } },
spec: {
containers: [{
name: "app",
image: args.image,
ports: [{ containerPort: 8080 }],
}],
},
},
},
}, { provider: new k8s.Provider("k8s", { kubeconfig: cluster.kubeconfig }) });
}
}
typescript
${name}-vpc, {
${name}-cluster, {
${name}-deployment, {
`
$1
$1
` package platform.policy default allow = false allow {
input.request.kind == "Deployment"
image := input.request.object.spec.template.spec.containers[_].image
startswith(image, "approved-registry.com/")
} deny[msg] {
input.request.kind == "Deployment"
container := input.request.object.spec.template.spec.containers[_]
not container.resources.limits.cpu
msg := "CPU limits are required"
}
rego
`policy.rego
Allow only approved base images
Enforce resource quotas
$1
` apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-role
namespace: apps
rules:
resources: ["deployments", "services"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] ---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: apps
subjects:
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer-role
apiGroup: rbac.authorization.k8s.io
yaml
`rbac.yaml
$1
$1
` apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: service-pipeline
spec:
workspaces:
- name: shared-workspace
params:
- name: git-url
- name: image-name
tasks:
- name: fetch-source
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-workspace
params:
- name: url
value: $(params.git-url)
- name: run-tests
taskRef:
name: npm-test
runAfter:
- fetch-source
workspaces:
- name: source
workspace: shared-workspace
- name: build-image
taskRef:
name: buildah
runAfter:
- run-tests
params:
- name: IMAGE
value: $(params.image-name)
workspaces:
- name: source
workspace: shared-workspace
yaml
`tekton-pipeline.yaml
$1
$1
` global:
scrape_interval: 15s
evaluation_interval: 15s scrape_configs:
- job_name: 'platform-services'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
yaml
`prometheus-config.yaml
$1
` {
"dashboard": {
"id": null,
"title": "Platform Overview",
"tags": ["platform", "overview"],
"timezone": "browser",
"panels": [
{
"title": "Service Health",
"type": "gauge",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(up{job=\"platform-services\"})",
"refId": "A"
}
]
},
{
"title": "Resource Usage",
"type": "graph",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(container_memory_usage_bytes) by (namespace)",
"refId": "B"
}
]
}
]
}
}
json
`
$1
$1
` // platform-cli.ts
import { Command } from 'commander';
import { createService, deployService, getServiceStatus } from './platform-api'; const program = new Command(); program
.command('create')
.description('Create a new service')
.option('-t, --template ', 'Service template to use')
.option('-n, --name .action(async (options) => {
try {
const service = await createService({
template: options.template,
name: options.name
});
console.log( } catch (error) {
console.error('Failed to create service:', error);
}
}); program
.command('deploy')
.description('Deploy a service')
.option('-n, --name .option('-e, --environment .action(async (options) => {
try {
await deployService({
name: options.name,
environment: options.environment
});
console.log( } catch (error) {
console.error('Failed to deploy service:', error);
}
}); program.parse(process.argv);
typescript
Service ${service.name} created successfully);
Service ${options.name} deployed to ${options.environment});
``
$1
$1
| Practice | Description | Benefit |
|---|---|---|
| Standardization | Common patterns | Consistency |
| Automation | Self-service flows | Efficiency |
| Documentation | Clear guidelines | Adoption |
$1
$1
| Control | Implementation | Purpose |
|---|---|---|
| Authentication | SSO/OIDC | Access control |
| Authorization | RBAC/OPA | Permission management |
| Secrets | Vault integration | Secure storage |
$1
$1
| Issue | Cause | Solution |
|---|---|---|
| Deploy Failed | Resource limits | Check quotas |
| Access Denied | RBAC config | Verify roles |
| Service Down | Health checks | Check logs |
$1
1. [Internal Developer Platform](https://internaldeveloperplatform.org/)
2. [Platform Engineering](https://platformengineering.org/)
3. [Backstage Documentation](https://backstage.io/docs/)
4. [OPA Documentation](https://www.openpolicyagent.org/docs/)
5. [Tekton Documentation](https://tekton.dev/docs/)
6. [Kubernetes Documentation](https://kubernetes.io/docs/)
$1
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.