{"slug":"platform-engineering-backstage-crossplane","title":"Platform Engineering: Building Internal Developer Platforms with Backstage and Crossplane","description":"How to build a self-service developer platform that reduces environment provisioning from weeks to minutes using Backstage, Crossplane, and ArgoCD","date":"2025-02-15","author":"Elaph Hilful","thumbnail":"/blog-images/platform-engineering.svg","tags":["Platform Engineering","Backstage","Crossplane","ArgoCD","Kubernetes"],"category":"DevOps","featured":true,"readTime":6,"htmlContent":"<p>Platform Engineering has emerged as the answer to a critical question: how do you enable 200+ engineers to provision production-ready environments without creating a bottleneck on your infrastructure team? The answer is an Internal Developer Platform (IDP) that provides self-service infrastructure with built-in guardrails.</p>\n<p>In this guide, I'll share how we built a platform that reduced environment provisioning from 2-3 weeks to under 10 minutes while maintaining SOC 2 compliance.</p>\n<p><img src=\"/blog-images/platform-engineering.svg\" alt=\"Internal Developer Platform Architecture\" loading=\"lazy\" decoding=\"async\"></p>\n<h2>Why Platform Engineering?</h2>\n<p>The DevOps promise was \"you build it, you run it.\" The reality? Most developers don't want to be infrastructure experts — they want to ship features. Platform Engineering bridges this gap by providing:</p>\n<ul>\n<li><strong>Golden paths</strong>: Opinionated, pre-configured templates that encode best practices</li>\n<li><strong>Self-service</strong>: Developers provision what they need without tickets</li>\n<li><strong>Guardrails</strong>: Automated policy enforcement prevents misconfigurations</li>\n<li><strong>Abstraction</strong>: Cloud complexity hidden behind simple, consistent APIs</li>\n</ul>\n<h2>The Architecture: Three Layers</h2>\n<h3>Layer 1: Developer Experience (Backstage)</h3>\n<p>Backstage is the frontend of your platform. It provides a unified portal where developers can:</p>\n<ul>\n<li>Discover existing services and their owners</li>\n<li>Provision new environments from templates</li>\n<li>View documentation, APIs, and dependencies</li>\n<li>Monitor service health and SLOs</li>\n</ul>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># backstage/templates/microservice/template.yaml</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">scaffolder.backstage.io/v1beta3</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Template</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">production-microservice</span>\n  <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Production</span> <span class=\"hljs-string\">Microservice</span>\n  <span class=\"hljs-attr\">description:</span> <span class=\"hljs-string\">Create</span> <span class=\"hljs-string\">a</span> <span class=\"hljs-string\">new</span> <span class=\"hljs-string\">microservice</span> <span class=\"hljs-string\">with</span> <span class=\"hljs-string\">CI/CD,</span> <span class=\"hljs-string\">observability,</span> <span class=\"hljs-string\">and</span> <span class=\"hljs-string\">security</span>\n<span class=\"hljs-attr\">spec:</span>\n  <span class=\"hljs-attr\">owner:</span> <span class=\"hljs-string\">platform-team</span>\n  <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">service</span>\n\n  <span class=\"hljs-attr\">parameters:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Service</span> <span class=\"hljs-string\">Details</span>\n      <span class=\"hljs-attr\">required:</span> [<span class=\"hljs-string\">name</span>, <span class=\"hljs-string\">team</span>, <span class=\"hljs-string\">language</span>]\n      <span class=\"hljs-attr\">properties:</span>\n        <span class=\"hljs-attr\">name:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Service</span> <span class=\"hljs-string\">Name</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">pattern:</span> <span class=\"hljs-string\">\"^[a-z][a-z0-9-]*$\"</span>\n        <span class=\"hljs-attr\">team:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Owning</span> <span class=\"hljs-string\">Team</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">payments</span>, <span class=\"hljs-string\">identity</span>, <span class=\"hljs-string\">commerce</span>, <span class=\"hljs-string\">data</span>]\n        <span class=\"hljs-attr\">language:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Language</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">go</span>, <span class=\"hljs-string\">typescript</span>, <span class=\"hljs-string\">python</span>, <span class=\"hljs-string\">java</span>]\n        <span class=\"hljs-attr\">tier:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Service</span> <span class=\"hljs-string\">Tier</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">critical</span>, <span class=\"hljs-string\">standard</span>, <span class=\"hljs-string\">experimental</span>]\n          <span class=\"hljs-attr\">default:</span> <span class=\"hljs-string\">standard</span>\n\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Infrastructure</span>\n      <span class=\"hljs-attr\">properties:</span>\n        <span class=\"hljs-attr\">cloud:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Cloud</span> <span class=\"hljs-string\">Provider</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">aws</span>, <span class=\"hljs-string\">azure</span>, <span class=\"hljs-string\">gcp</span>]\n          <span class=\"hljs-attr\">default:</span> <span class=\"hljs-string\">aws</span>\n        <span class=\"hljs-attr\">database:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Database</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">postgres</span>, <span class=\"hljs-string\">mysql</span>, <span class=\"hljs-string\">dynamodb</span>, <span class=\"hljs-string\">none</span>]\n        <span class=\"hljs-attr\">cache:</span>\n          <span class=\"hljs-attr\">title:</span> <span class=\"hljs-string\">Cache</span> <span class=\"hljs-string\">Layer</span>\n          <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">string</span>\n          <span class=\"hljs-attr\">enum:</span> [<span class=\"hljs-string\">redis</span>, <span class=\"hljs-string\">memcached</span>, <span class=\"hljs-string\">none</span>]\n\n  <span class=\"hljs-attr\">steps:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">scaffold</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Scaffold</span> <span class=\"hljs-string\">Repository</span>\n      <span class=\"hljs-attr\">action:</span> <span class=\"hljs-string\">fetch:template</span>\n      <span class=\"hljs-attr\">input:</span>\n        <span class=\"hljs-attr\">url:</span> <span class=\"hljs-string\">./skeleton</span>\n        <span class=\"hljs-attr\">values:</span>\n          <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.name</span> <span class=\"hljs-string\">}}</span>\n          <span class=\"hljs-attr\">team:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.team</span> <span class=\"hljs-string\">}}</span>\n\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">publish</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Create</span> <span class=\"hljs-string\">Repository</span>\n      <span class=\"hljs-attr\">action:</span> <span class=\"hljs-string\">publish:github</span>\n      <span class=\"hljs-attr\">input:</span>\n        <span class=\"hljs-attr\">repoUrl:</span> <span class=\"hljs-string\">github.com?owner=myorg&amp;repo=${{</span> <span class=\"hljs-string\">parameters.name</span> <span class=\"hljs-string\">}}</span>\n\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">provision-infra</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Provision</span> <span class=\"hljs-string\">Infrastructure</span>\n      <span class=\"hljs-attr\">action:</span> <span class=\"hljs-string\">crossplane:claim</span>\n      <span class=\"hljs-attr\">input:</span>\n        <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">platform.myorg.io/v1alpha1</span>\n        <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Environment</span>\n        <span class=\"hljs-attr\">metadata:</span>\n          <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.name</span> <span class=\"hljs-string\">}}</span>\n        <span class=\"hljs-attr\">spec:</span>\n          <span class=\"hljs-attr\">cloud:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.cloud</span> <span class=\"hljs-string\">}}</span>\n          <span class=\"hljs-attr\">tier:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.tier</span> <span class=\"hljs-string\">}}</span>\n          <span class=\"hljs-attr\">database:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.database</span> <span class=\"hljs-string\">}}</span>\n          <span class=\"hljs-attr\">cache:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">parameters.cache</span> <span class=\"hljs-string\">}}</span>\n\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">id:</span> <span class=\"hljs-string\">register</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">Register</span> <span class=\"hljs-string\">in</span> <span class=\"hljs-string\">Catalog</span>\n      <span class=\"hljs-attr\">action:</span> <span class=\"hljs-string\">catalog:register</span>\n      <span class=\"hljs-attr\">input:</span>\n        <span class=\"hljs-attr\">repoContentsUrl:</span> <span class=\"hljs-string\">${{</span> <span class=\"hljs-string\">steps.publish.output.repoContentsUrl</span> <span class=\"hljs-string\">}}</span>\n        <span class=\"hljs-attr\">catalogInfoPath:</span> <span class=\"hljs-string\">/catalog-info.yaml</span>\n</code></pre>\n<h3>Layer 2: GitOps Orchestration (ArgoCD)</h3>\n<p>ArgoCD ensures that the desired state in Git matches the actual state in the cluster. Every infrastructure change flows through Git:</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># argocd/application-set.yaml</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">argoproj.io/v1alpha1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ApplicationSet</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">team-environments</span>\n  <span class=\"hljs-attr\">namespace:</span> <span class=\"hljs-string\">argocd</span>\n<span class=\"hljs-attr\">spec:</span>\n  <span class=\"hljs-attr\">generators:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">git:</span>\n        <span class=\"hljs-attr\">repoURL:</span> <span class=\"hljs-string\">https://github.com/myorg/environments.git</span>\n        <span class=\"hljs-attr\">revision:</span> <span class=\"hljs-string\">HEAD</span>\n        <span class=\"hljs-attr\">directories:</span>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">teams/*/environments/*</span>\n\n  <span class=\"hljs-attr\">template:</span>\n    <span class=\"hljs-attr\">metadata:</span>\n      <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path.basename}}</span>\"</span>\n      <span class=\"hljs-attr\">labels:</span>\n        <span class=\"hljs-attr\">team:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path[1]}}</span>\"</span>\n        <span class=\"hljs-attr\">environment:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path[3]}}</span>\"</span>\n    <span class=\"hljs-attr\">spec:</span>\n      <span class=\"hljs-attr\">project:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path[1]}}</span>\"</span>\n      <span class=\"hljs-attr\">source:</span>\n        <span class=\"hljs-attr\">repoURL:</span> <span class=\"hljs-string\">https://github.com/myorg/environments.git</span>\n        <span class=\"hljs-attr\">targetRevision:</span> <span class=\"hljs-string\">HEAD</span>\n        <span class=\"hljs-attr\">path:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path}}</span>\"</span>\n      <span class=\"hljs-attr\">destination:</span>\n        <span class=\"hljs-attr\">server:</span> <span class=\"hljs-string\">https://kubernetes.default.svc</span>\n        <span class=\"hljs-attr\">namespace:</span> <span class=\"hljs-string\">\"<span class=\"hljs-template-variable\">{{path[1]}}</span>-<span class=\"hljs-template-variable\">{{path[3]}}</span>\"</span>\n      <span class=\"hljs-attr\">syncPolicy:</span>\n        <span class=\"hljs-attr\">automated:</span>\n          <span class=\"hljs-attr\">prune:</span> <span class=\"hljs-literal\">true</span>\n          <span class=\"hljs-attr\">selfHeal:</span> <span class=\"hljs-literal\">true</span>\n        <span class=\"hljs-attr\">syncOptions:</span>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">CreateNamespace=true</span>\n          <span class=\"hljs-bullet\">-</span> <span class=\"hljs-string\">ServerSideApply=true</span>\n</code></pre>\n<h3>Layer 3: Infrastructure Abstraction (Crossplane)</h3>\n<p>Crossplane is the secret weapon. It exposes cloud infrastructure as Kubernetes-native APIs, letting developers provision databases, caches, and queues with simple YAML:</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># crossplane/compositions/environment.yaml</span>\n<span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">apiextensions.crossplane.io/v1</span>\n<span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Composition</span>\n<span class=\"hljs-attr\">metadata:</span>\n  <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">production-environment</span>\n<span class=\"hljs-attr\">spec:</span>\n  <span class=\"hljs-attr\">compositeTypeRef:</span>\n    <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">platform.myorg.io/v1alpha1</span>\n    <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Environment</span>\n  <span class=\"hljs-attr\">resources:</span>\n    <span class=\"hljs-comment\"># Kubernetes Namespace</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">namespace</span>\n      <span class=\"hljs-attr\">base:</span>\n        <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">kubernetes.crossplane.io/v1alpha1</span>\n        <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Object</span>\n        <span class=\"hljs-attr\">spec:</span>\n          <span class=\"hljs-attr\">forProvider:</span>\n            <span class=\"hljs-attr\">manifest:</span>\n              <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">v1</span>\n              <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Namespace</span>\n      <span class=\"hljs-attr\">patches:</span>\n        <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">fromFieldPath:</span> <span class=\"hljs-string\">metadata.name</span>\n          <span class=\"hljs-attr\">toFieldPath:</span> <span class=\"hljs-string\">spec.forProvider.manifest.metadata.name</span>\n\n    <span class=\"hljs-comment\"># PostgreSQL Database (if requested)</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">database</span>\n      <span class=\"hljs-attr\">base:</span>\n        <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">rds.aws.upbound.io/v1beta1</span>\n        <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">Instance</span>\n        <span class=\"hljs-attr\">spec:</span>\n          <span class=\"hljs-attr\">forProvider:</span>\n            <span class=\"hljs-attr\">engine:</span> <span class=\"hljs-string\">postgres</span>\n            <span class=\"hljs-attr\">engineVersion:</span> <span class=\"hljs-string\">\"15\"</span>\n            <span class=\"hljs-attr\">instanceClass:</span> <span class=\"hljs-string\">db.t3.medium</span>\n            <span class=\"hljs-attr\">allocatedStorage:</span> <span class=\"hljs-number\">20</span>\n            <span class=\"hljs-attr\">publiclyAccessible:</span> <span class=\"hljs-literal\">false</span>\n            <span class=\"hljs-attr\">storageEncrypted:</span> <span class=\"hljs-literal\">true</span>\n      <span class=\"hljs-attr\">patches:</span>\n        <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">fromFieldPath:</span> <span class=\"hljs-string\">spec.tier</span>\n          <span class=\"hljs-attr\">toFieldPath:</span> <span class=\"hljs-string\">spec.forProvider.instanceClass</span>\n          <span class=\"hljs-attr\">transforms:</span>\n            <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">map</span>\n              <span class=\"hljs-attr\">map:</span>\n                <span class=\"hljs-attr\">critical:</span> <span class=\"hljs-string\">db.r6g.xlarge</span>\n                <span class=\"hljs-attr\">standard:</span> <span class=\"hljs-string\">db.t3.medium</span>\n                <span class=\"hljs-attr\">experimental:</span> <span class=\"hljs-string\">db.t3.micro</span>\n\n    <span class=\"hljs-comment\"># Redis Cache (if requested)</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">name:</span> <span class=\"hljs-string\">cache</span>\n      <span class=\"hljs-attr\">base:</span>\n        <span class=\"hljs-attr\">apiVersion:</span> <span class=\"hljs-string\">elasticache.aws.upbound.io/v1beta1</span>\n        <span class=\"hljs-attr\">kind:</span> <span class=\"hljs-string\">ReplicationGroup</span>\n        <span class=\"hljs-attr\">spec:</span>\n          <span class=\"hljs-attr\">forProvider:</span>\n            <span class=\"hljs-attr\">engine:</span> <span class=\"hljs-string\">redis</span>\n            <span class=\"hljs-attr\">engineVersion:</span> <span class=\"hljs-string\">\"7.0\"</span>\n            <span class=\"hljs-attr\">nodeType:</span> <span class=\"hljs-string\">cache.t3.medium</span>\n            <span class=\"hljs-attr\">numCacheClusters:</span> <span class=\"hljs-number\">2</span>\n            <span class=\"hljs-attr\">atRestEncryptionEnabled:</span> <span class=\"hljs-literal\">true</span>\n            <span class=\"hljs-attr\">transitEncryptionEnabled:</span> <span class=\"hljs-literal\">true</span>\n</code></pre>\n<h2>Developer Workflow: From Request to Running Service</h2>\n<p>Here's what the developer experience looks like:</p>\n<ol>\n<li><strong>Developer</strong> opens Backstage, selects \"Create New Service\"</li>\n<li><strong>Backstage</strong> scaffolds repository from golden path template</li>\n<li><strong>Git commit</strong> triggers ArgoCD sync</li>\n<li><strong>ArgoCD</strong> deploys Crossplane Claims to the cluster</li>\n<li><strong>Crossplane</strong> provisions cloud resources (RDS, ElastiCache, etc.)</li>\n<li><strong>Service</strong> is running with CI/CD, monitoring, and security — in under 10 minutes</li>\n</ol>\n<pre role=\"region\" aria-label=\"bash code block\"><code class=\"hljs language-bash\" tabindex=\"0\"><span class=\"hljs-comment\"># What developers see:</span>\n$ kubectl get environments -n payments-team\nNAME              CLOUD   TIER       STATUS   AGE\ncheckout-service  aws     critical   Ready    8m\nfraud-detector    aws     standard   Ready    6m\n</code></pre>\n<h2>Policy Guardrails: Security Without Friction</h2>\n<p>The platform enforces organizational policies automatically:</p>\n<pre role=\"region\" aria-label=\"yaml code block\"><code class=\"hljs language-yaml\" tabindex=\"0\"><span class=\"hljs-comment\"># crossplane/compositions/policy-hooks.yaml</span>\n<span class=\"hljs-comment\"># Cost guardrails per tier</span>\n<span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">fromFieldPath:</span> <span class=\"hljs-string\">spec.tier</span>\n  <span class=\"hljs-attr\">toFieldPath:</span> <span class=\"hljs-string\">metadata.annotations[cost-limit]</span>\n  <span class=\"hljs-attr\">transforms:</span>\n    <span class=\"hljs-bullet\">-</span> <span class=\"hljs-attr\">type:</span> <span class=\"hljs-string\">map</span>\n      <span class=\"hljs-attr\">map:</span>\n        <span class=\"hljs-attr\">critical:</span> <span class=\"hljs-string\">\"5000\"</span> <span class=\"hljs-comment\"># $5000/month max</span>\n        <span class=\"hljs-attr\">standard:</span> <span class=\"hljs-string\">\"1000\"</span> <span class=\"hljs-comment\"># $1000/month max</span>\n        <span class=\"hljs-attr\">experimental:</span> <span class=\"hljs-string\">\"100\"</span> <span class=\"hljs-comment\"># $100/month max</span>\n</code></pre>\n<p>Combined with OPA Gatekeeper:</p>\n<pre role=\"region\" aria-label=\"rego code block\"><code class=\"hljs language-rego\" tabindex=\"0\"># Prevent experimental workloads in production clusters\ndeny[msg] {\n    input.review.object.metadata.labels.tier == \"experimental\"\n    input.review.object.metadata.namespace == \"production\"\n    msg := \"Experimental tier workloads cannot be deployed to production\"\n}\n</code></pre>\n<h2>Results: Measurable Impact</h2>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Before IDP</th>\n<th>After IDP</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Environment provisioning</td>\n<td>2-3 weeks</td>\n<td>&lt; 10 minutes</td>\n</tr>\n<tr>\n<td>Infrastructure tickets/month</td>\n<td>200+</td>\n<td>&lt; 30</td>\n</tr>\n<tr>\n<td>Teams fully self-serving</td>\n<td>0</td>\n<td>40+</td>\n</tr>\n<tr>\n<td>Compliance violations</td>\n<td>Frequent</td>\n<td>Zero (automated)</td>\n</tr>\n<tr>\n<td>Platform uptime</td>\n<td>99.9%</td>\n<td>99.99%</td>\n</tr>\n<tr>\n<td>Annual cost savings</td>\n<td>-</td>\n<td>$420K (right-sizing)</td>\n</tr>\n</tbody>\n</table>\n<h2>Lessons from Production</h2>\n<ol>\n<li>\n<p><strong>Start with golden paths, not golden cages</strong>: Give teams escape hatches for genuinely unique needs, but make the golden path so good that 90% of teams choose it voluntarily.</p>\n</li>\n<li>\n<p><strong>Treat your platform as a product</strong>: Your engineers are your customers. Measure developer satisfaction, gather feedback, and iterate. If the platform is harder than doing things manually, teams will route around it.</p>\n</li>\n<li>\n<p><strong>Composability over completeness</strong>: Don't try to build everything at once. Start with the most common provisioning patterns and expand based on demand.</p>\n</li>\n<li>\n<p><strong>GitOps makes everything auditable</strong>: Every infrastructure change has a PR, a review, and a commit hash. This makes compliance evidence automatic.</p>\n</li>\n<li>\n<p><strong>Invest in documentation</strong>: A platform without documentation is a black box. Backstage's TechDocs integration makes documentation a first-class citizen.</p>\n</li>\n</ol>\n<h2>Getting Started</h2>\n<p>The quickest path to an IDP:</p>\n<ol>\n<li><strong>Month 1</strong>: Deploy Backstage with a service catalog (just visibility)</li>\n<li><strong>Month 2</strong>: Add 2-3 golden path templates for common patterns</li>\n<li><strong>Month 3</strong>: Integrate Crossplane for infrastructure provisioning</li>\n<li><strong>Month 4</strong>: Add ArgoCD for GitOps-driven deployments</li>\n<li><strong>Month 5+</strong>: Iterate based on developer feedback</li>\n</ol>\n<p>Remember: the best platform is one that engineers actually want to use. Start simple, prove value, and expand.</p>"}