Skip to main content
Pulumi logo Pulumi logo
IDP Best Practices Series: Part 2

Golden Paths in IDPs: A Complete Guide to Reusable Infrastructure with Pulumi Components and Templates

Updated Sep 6, 2026 21 min read
Golden Paths in IDPs: A Complete Guide to Reusable Infrastructure with Pulumi Components and Templates

Welcome to the second post in our IDP Best Practices series. In this article, we explore how to build golden paths in practice: pre-architected, reusable infrastructure patterns that help standardize and speed up cloud development.

Golden paths are opinionated routes for infrastructure provisioning that platform teams build for common workflows. Because modern cloud platforms offer so many options — over 200 AWS services, sprawling Azure catalogs, countless DevOps tools — developers can struggle with decision fatigue or produce inconsistent implementations. Golden paths solve this by providing ready-to-use, production-grade infrastructure that encodes your organization’s best practices, security policies, and operational standards.

In this guide, you’ll learn how to build golden paths for your Internal Developer Platform using two core Pulumi constructs: Components, reusable infrastructure building blocks, and Templates, predefined, deployable patterns. You’ll see how to create infrastructure abstractions that are written once, shared across teams, and consumed in any language, turning weeks of setup into minutes of developer-ready infrastructure.

The complete code examples from this post are available on GitHub.

The Platform Engineering Layer Cake: A Model for IDPs

To understand where golden paths fit into an Internal Developer Platform (IDP), think in layers. This three-tier model structures your platform to deliver increasing levels of abstraction, reuse, and developer value, and it maps onto the four factors of a Pulumi IDP: templates, components, environments, and policies.

Layer 1: Infrastructure layer

This is the foundation: raw cloud resources include VMs, databases, networks, and storage, which are the fundamental building blocks from AWS, Azure, GCP, and other providers. Pulumi gives you programmatic access to these resources through native providers, but working at this level requires deep infrastructure knowledge.

Layer 2: Platform layer - components

This is where the magic happens. Pulumi Components take those raw resources and package them into higher-level abstractions. Instead of manually configuring 20+ AWS resources for a secure web application, you create a component that handles all that complexity and exposes just the configuration that matters:

const app = new SecureWebApplication("my-app", {
    instanceType: "t3.medium",
    minReplicas: 2,
    maxReplicas: 10,
    enableWAF: true,
    environment: "production"
});

Layer 3: Golden path layer - templates

Templates are the layer developers actually consume. A template wraps one or more components in opinionated project scaffolding — repo structure, CI/CD wiring, policy guardrails, and sensible defaults — so starting a new service is one command instead of a design exercise. The rest of this guide builds a component first, then shows how a golden path template turns it into that one-command experience.

Part 1: Building Reusable Infrastructure Components

Reusable infrastructure components act as the foundation of your platform. They encapsulate complexity while remaining composable and scalable. Let’s build a real-world example: a component that deploys containerized microservices to AWS Fargate.

The Power of Multi-Language Components

Here’s what makes Pulumi components revolutionary: write once, consume anywhere. Your platform team can author components in TypeScript, but application teams can consume them in Python, Go, .NET, Java, or even YAML. This separation of concerns is crucial for scaling platform engineering across diverse teams.

Building a Microservice Component Step-by-Step

Let’s create a component that encapsulates everything needed to deploy a production-ready microservice:

// microservice-component.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

export interface MicroserviceArgs {
    appPath: string;      // Path to application code
    cpu?: number;         // CPU units (256, 512, 1024, etc.)
    memory?: number;      // Memory in MB
    port?: number;        // Application port
    desiredCount?: number; // Number of tasks
}

export class MicroserviceComponent extends pulumi.ComponentResource {
    public readonly url: pulumi.Output<string>;
    public readonly serviceName: pulumi.Output<string>;
    public readonly clusterName: pulumi.Output<string>;

    constructor(name: string, args: MicroserviceArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:infrastructure:Microservice", name, {}, opts);

        // Set defaults
        const cpu = args.cpu || 256;
        const memory = args.memory || 512;
        const port = args.port || 8080;
        const desiredCount = args.desiredCount || 2;

        // Create ECR repository for Docker images
        const repository = new awsx.ecr.Repository(`${name}-repo`, {
            forceDelete: true
        }, { parent: this });

        // Build and push Docker image
        const image = new awsx.ecr.Image(`${name}-image`, {
            repositoryUrl: repository.url,
            context: args.appPath,
            platform: "linux/amd64",
        }, { parent: this });

        // Create Application Load Balancer
        const loadBalancer = new awsx.lb.ApplicationLoadBalancer(`${name}-lb`, {
            defaultTargetGroup: {
                port: port,
                protocol: "HTTP",
                healthCheck: {
                    path: "/health",
                    interval: 30,
                    timeout: 5,
                    healthyThreshold: 2,
                    unhealthyThreshold: 3,
                }
            }
        }, { parent: this });

        // Create ECS cluster
        const cluster = new aws.ecs.Cluster(`${name}-cluster`, {}, { parent: this });

        // Create Fargate service
        const service = new awsx.ecs.FargateService(`${name}-service`, {
            cluster: cluster.arn,
            taskDefinitionArgs: {
                container: {
                    name: name,
                    image: image.imageUri,
                    cpu: cpu,
                    memory: memory,
                    essential: true,
                    portMappings: [{
                        containerPort: port,
                        targetGroup: loadBalancer.defaultTargetGroup,
                    }],
                },
            },
            desiredCount: desiredCount,
            assignPublicIp: true,
        }, { parent: this });

        // Export outputs
        this.url = pulumi.interpolate`http://${loadBalancer.loadBalancer.dnsName}`;
        this.serviceName = service.service.name;
        this.clusterName = cluster.name;

        // Register outputs
        this.registerOutputs({
            url: this.url,
            serviceName: this.serviceName,
            clusterName: this.clusterName,
        });
    }
}

Documenting for Discoverability and Adoption

To make your component easy to use, add comprehensive documentation and examples to a README.md file:

# MicroserviceComponent

Abstraction for resources needed when using AWS container services.

A component to abstract the details related to:
- Creating a docker image and pushing it to AWS ECR.
- Deploy to ECS Fargate using the docker image.

# Inputs

* appPath: Path to local folder containing the app and Dockerfile.
* port: Port to expose via an ALB.
* cpu (Optional): CPU capacity. Defaults to 256 (i.e. 0.25 vCPU).
* memory (Optional): Memory capacity. Defaults to 512 (i.e. 0.5GB).
* containerName (Optional): Name of the container. Defaults to "my-app".

# Outputs

* publicUrl: The DNS name for the loadbalancer fronting the app.

# Usage
## Specify Package in `Pulumi.yaml`

Add the following to your `Pulumi.yaml` file:
Note: If no version is specified, the latest version will be used.

... omit for brevity ...

This documentation will help developers understand how to use your component effectively, including required inputs, outputs, and example usage.

Publishing Your Component via Private Registry

Once your component is ready, publish it to your Pulumi Private Registry. This assumes your component’s source already lives in a Git repository — the example below publishes from github.com/myorg/microservice-component, so swap in your own repository’s path:

# Tag your component version
git tag v1.0.1
git push origin v1.0.1

# Publish to your organization's private registry
pulumi package publish github.com/myorg/microservice-component@1.0.1

The argument is the component’s source, not a registry URL: pulumi package publish takes a Git repository, a plugin reference, or a local schema file, and publishes it to the private registry of your default organization. If you belong to more than one org, pass --publisher ORG_NAME.

Now any team in your organization can discover and use your component, regardless of their language preference. All they need is to navigate to the Components section in the Pulumi IDP and search for microservice-component.

img_2.png

Part 2: From Building Blocks to Golden Paths

While components are powerful, they’re just the starting point. Golden path templates layer on opinionated scaffolding, workflows, and compliance best practices that guide developers to production.

Golden path maturity: from zero to product-grade platforms

Teams evolve from ad hoc deployments to mature, productized templates with versioning, metrics, and governance. These maturity levels reflect how deeply your platform enables safe, consistent delivery.

img_8.png

Organizations typically progress through three stages of golden path maturity:

Stage 1: No Golden Paths

  • Every team reinvents the wheel
  • Inconsistent practices across projects
  • High cognitive load on developers
  • Security and compliance gaps

Stage 2: Dawn of Templates

  • Basic cookie-cutter templates emerge
  • Some standardization begins
  • Manual processes still dominate
  • Limited support and evolution

Stage 3: Templates as Products

  • Golden paths have dedicated owners
  • Regular release cycles and versioning
  • Migration guides for updates
  • Metrics track adoption and success
  • Continuous improvement based on feedback

The characteristics of a golden path

Drawing from Spotify’s pioneering work, golden paths share these characteristics:

  1. Pre-architected and Supported: The platform team owns and supports the path
  2. Optional but Recommended: Developers can deviate, but staying on the path ensures support
  3. Transparent Abstractions: The implementation is visible, not a black box
  4. Extensible: Teams can add project-specific resources without breaking the pattern
  5. Evolutionary: Templates improve based on feedback and new requirements

Building a Go microservice golden path

Let’s create a complete golden path for Go microservices that includes:

  • Application scaffolding with best practices
  • Infrastructure deployment using our component
  • CI/CD pipeline configuration
  • Observability setup
  • Security controls

Step 1: Template Structure

go-microservice-boilerplate/
├── Pulumi.yaml           # Infrastructure definition
├── src/                  # Application code
│   ├── main.go           # Go microservice
│   ├── Dockerfile        # Container definition
│   └── go.mod            # Dependencies
└── README.md            # Documentation

Step 2: Application Scaffolding

The src/main.go file contains a minimal Go microservice with all the best practices and compliance guardrails baked in. This helps developers to quickly get started and extend the application without worrying too much about selecting the right libraries or frameworks.

// source/main.go
package main

import (
	"context"
	"flag"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)

func initTracer() (*sdktrace.TracerProvider, error) {
	exporter, err := otlptracehttp.New(context.Background())
	if err != nil {
		return nil, err
	}

	resource, err := resource.Merge(
		resource.Default(),
		resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("${PROJECT}"),
			semconv.ServiceVersionKey.String("1.0.0"),
		),
	)
	if err != nil {
		return nil, err
	}

	tp := sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exporter),
		sdktrace.WithResource(resource),
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
	)

	otel.SetTracerProvider(tp)
	return tp, nil
}

func echoHandler(c echo.Context) error {
	message := c.QueryParam("message")
	if message == "" {
		message = "Hello from Go microservice!"
	}

	return c.JSON(http.StatusOK, map[string]string{
		"echo":      message,
		"service":   "${PROJECT}",
		"timestamp": time.Now().UTC().Format(time.RFC3339),
	})
}

func healthHandler(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]string{
		"status":  "healthy",
		"service": "${PROJECT}",
	})
}

func main() {
	healthCheck := flag.Bool("health-check", false, "Run health check and exit")
	flag.Parse()

	if *healthCheck {
		resp, err := http.Get("http://localhost:8080/health")
		if err != nil || resp.StatusCode != http.StatusOK {
			os.Exit(1)
		}
		os.Exit(0)
	}

	tp, err := initTracer()
	if err != nil {
		panic(err)
	}
	defer func() {
		if err := tp.Shutdown(context.Background()); err != nil {
			panic(err)
		}
	}()

	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())
	e.Use(middleware.CORS())
	e.Use(otelecho.Middleware("${PROJECT}"))

	e.GET("/echo", echoHandler)
	e.GET("/health", healthHandler)

	go func() {
		if err := e.Start(":8080"); err != nil && err != http.ErrServerClosed {
			e.Logger.Fatal("shutting down the server")
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := e.Shutdown(ctx); err != nil {
		e.Logger.Fatal(err)
	}
}

Step 3: Infrastructure Template with YAML

Here’s how you define the infrastructure using Pulumi YAML. You can instantly spot one of the features of Pulumi Components. We consume the micorservice-component in YAML without knowing that it was authored in a different language. This is the power of Pulumi Components: write once, consume anywhere.

And we see another feature of Pulumi Components: As it is a first-class citizen, we can embed the component and add additional resources like auto-scaling policies, alarms, and more directly in the YAML file. This allows us to create a complete golden path for deploying Go microservices on AWS ECS with auto-scaling capabilities.

# Pulumi.yaml
name: ${PROJECT}
description: ${DESCRIPTION}
runtime: yaml
packages:
  component-microservice: https://github.com/smithrobs/component-microservice@v1.0.1
template:
  description: A template for deploying a Go microservice on AWS ECS with auto-scaling capabilities.
resources:
  microserviceComponent:
    type: component-microservice:MicroserviceComponent
    properties:
      appPath: ./src
      port: 8080
      containerName: ${PROJECT}
  ecsTarget:
    type: aws:appautoscaling:Target
    name: ecs_target
    properties:
      maxCapacity: 4
      minCapacity: 1
      resourceId: service/${microserviceComponent.clusterName}/${microserviceComponent.serviceName}
      scalableDimension: ecs:service:DesiredCount
      serviceNamespace: ecs
  ecsPolicyUP:
    type: aws:appautoscaling:Policy
    name: ecs_policy_up
    properties:
      name: ecs_policy_up
      policyType: StepScaling
      resourceId: ${ecsTarget.resourceId}
      scalableDimension: ${ecsTarget.scalableDimension}
      serviceNamespace: ${ecsTarget.serviceNamespace}
      stepScalingPolicyConfiguration:
        adjustmentType: ChangeInCapacity
        cooldown: 60
        metricAggregationType: Maximum
        stepAdjustments:
        - metricIntervalUpperBound: 0
          scalingAdjustment: 1
  ecsPolicyDown:
    type: aws:appautoscaling:Policy
    name: ecs_policy_down
    properties:
      name: ecs_policy_down
      policyType: StepScaling
      resourceId: ${ecsTarget.resourceId}
      scalableDimension: ${ecsTarget.scalableDimension}
      serviceNamespace: ${ecsTarget.serviceNamespace}
      stepScalingPolicyConfiguration:
        adjustmentType: ChangeInCapacity
        cooldown: 60
        metricAggregationType: Maximum
        stepAdjustments:
        - metricIntervalLowerBound: 0
          scalingAdjustment: -1
  serviceCPUHighUtilization:
    type: aws:cloudwatch:MetricAlarm
    name: service_cpu_high_utilization
    properties:
      name: service_cpu_high_utilization
      comparisonOperator: GreaterThanOrEqualToThreshold
      evaluationPeriods: 2
      metricName: CPUUtilization
      namespace: AWS/ECS
      period: 60
      statistic: Average
      threshold: 80
      alarmActions:
      - ${ecsPolicyUP.arn}
      dimensions:
        ClusterName: ${microserviceComponent.clusterName}
        ServiceName: ${microserviceComponent.serviceName}
  serviceCPULowUtilization:
    type: aws:cloudwatch:MetricAlarm
    name: service_cpu_low_utilization
    properties:
      name: service_cpu_low_utilization
      comparisonOperator: LessThanOrEqualToThreshold
      evaluationPeriods: 2
      metricName: CPUUtilization
      namespace: AWS/ECS
      period: 60
      statistic: Average
      threshold: 10
      alarmActions:
      - ${ecsPolicyDown.arn}
      dimensions:
        ClusterName: ${microserviceComponent.clusterName}
        ServiceName: ${microserviceComponent.serviceName}

outputs:
  publicUrl: ${microserviceComponent.publicUrl}

Step 4: Documentation and Examples

Add a README.md file to your template directory to provide clear instructions on how to use the template, including prerequisites, configuration options, and example commands:

# Go Microservice Golden Path

A **golden path** template that gets your Go microservice from code to production on AWS in minutes, not weeks.

## What You Get

This golden path provides everything your development team needs to deploy production-ready Go microservices:

✅ **Production-ready Go microservice** with Echo framework and OpenTelemetry tracing
✅ **AWS infrastructure that scales** - ECS with auto-scaling from 1-4 instances
✅ **Security by default** - Hardened containers, IAM roles, security groups
✅ **Monitoring built-in** - Health checks, load balancer monitoring, CloudWatch alarms
✅ **One-command deployment** - `pulumi up` handles everything
✅ **No AWS expertise required** - Complex ECS setup abstracted away

## For Development Teams: What to Expect

### Your Experience

**Day 1: Getting Started**

- Clone this repo, run `pulumi up`
- Your service is live on AWS in 5-10 minutes
- Public URL provided automatically - no manual setup needed

**Day 2-N: Development Workflow**

- Write your Go code in `microservice/main.go`
- Test locally with `go run main.go`
- Deploy changes with `pulumi up`
- AWS automatically rebuilds and redeploys your container

**Production Operations**

- Service automatically scales with CPU load (80% up, 10% down)
- Health checks ensure unhealthy containers are replaced
- Load balancer distributes traffic across healthy instances
- Distributed tracing helps debug issues across services

### What's Handled For You

You **don't** need to learn or configure:

- ECS clusters, services, and task definitions
- Application Load Balancers and target groups
- Auto-scaling policies and CloudWatch alarms
- Security groups and IAM roles
- Container registries and image building
- Health check configuration

You **do** focus on:

- Writing your Go application logic
- Adding your business endpoints
- Testing your service locally
- Deploying with confidence

Making Templates Available in Pulumi IDP

Publishing your template to Pulumi IDP enables true self-service. Head to Settings → Integrations → Organization Template Sources and add your template repository.

img_4.png

Once published, developers can discover and deploy it directly from the IDP interface.

img_5.png

Now developers can deploy through multiple interfaces:

CLI Deployment:

pulumi new https://github.com/myorg/go-microservice-boilerplate

You reach the New Project Wizard two ways: navigate to Stacks → Create Project → select a template from the card view → Use this Template, or navigate to Platform → Templates → select a template → Deploy with Pulumi. Both entry points open the same wizard and let you either fork the template into a new project or add a no-code stack that references it directly, without forking.

From there, the wizard offers a deployment method for the new stack:

  • Pulumi Deployments (VCS-backed): writes the stack’s configuration to a Git repository (GitHub, Azure DevOps, GitLab, or Bitbucket). Your organization needs an integration for that provider, and you need to authorize your account with it.
  • Pulumi Deployments (no-code): stores the stack’s configuration in a Pulumi ESC environment instead of a repository. See no-code stacks for requirements; any configured VCS provider works.
  • Local deployment: deploy the stack from your machine with the Pulumi CLI, no VCS integration required.

Build golden paths with Pulumi

Package your infrastructure patterns into reusable components and templates, publish them to a private registry, and let teams deploy production-ready stacks in minutes.
Get started

Best Practices for Reusable Infrastructure Components and Templates

Well-designed components and templates are the foundation of scalable, self-service infrastructure. These best practices ensure your abstractions are maintainable, discoverable, and production-ready.

1. Design for day 2 operations from day 1

Think beyond deployment. A golden path must also support ongoing operations:

  • How will teams safely update their infrastructure?
  • What happens during scaling events?
  • How do you handle disaster recovery?
  • What metrics and logs are needed for troubleshooting?

2. Expose complexity progressively

Provide sensible defaults that work, while enabling customization for advanced use cases:

interface ComponentArgs {
    // Required - what users must provide
    appName: string;

    // Common customizations with good defaults
    instanceType?: string;  // default: "t3.micro"
    replicas?: number;       // default: 2

    // Advanced options for power users
    networkConfig?: NetworkConfig;
    securityPolicies?: SecurityPolicy[];
    customMetrics?: MetricDefinition[];
}

3. Use semantic versioning everywhere

Clear versioning for both components and templates indicates stability and reliability:

  • Major versions (1.0.0 → 2.0.0): Breaking changes
  • Minor versions (1.0.0 → 1.1.0): New features, backward compatible
  • Patch versions (1.0.0 → 1.0.1): Bug fixes

This lets teams adopt updates with confidence and control.

4. Test your abstractions

Don’t ship black boxes. Create automated tests to validate key functionality and resource creation. Focus on:

  • Smoke tests that validate resource existence
  • Output validation to ensure correctness
  • Integration tests to confirm end-to-end behavior
import { expect } from "chai";
import * as pulumi from "@pulumi/pulumi";

describe("MicroserviceComponent", () => {
    it("should create required resources", async () => {
        const component = new MicroserviceComponent("test", {
            appPath: "./test-app",
            port: 3000
        });

        const resources = await pulumi.runtime.allResources();
        expect(resources).to.include("aws:ecs/cluster:Cluster");
        expect(resources).to.include("aws:ecs/service:Service");
        expect(resources).to.include("aws:lb/loadBalancer:LoadBalancer");
    });
});

5. Enforce guardrails with policy as code

Self-service only works if the platform team can trust what gets deployed without reviewing every change by hand. Pulumi Policies close that gap by attaching automated guardrails directly to the golden paths you publish, so a stack that violates a security or cost standard fails at pulumi preview instead of in production.

A policy pack is ordinary code, written in the same language as the golden path it governs:

import { PolicyPack, validateResourceOfType } from "@pulumi/policy";
import * as aws from "@pulumi/aws";

new PolicyPack("golden-path-guardrails", {
    policies: [
        {
            name: "s3-bucket-must-not-be-public",
            description: "S3 buckets deployed through a golden path may not allow public read access.",
            enforcementLevel: "mandatory",
            validateResource: validateResourceOfType(aws.s3.BucketAcl, (bucketAcl, args, reportViolation) => {
                if (bucketAcl.acl === "public-read" || bucketAcl.acl === "public-read-write") {
                    reportViolation("S3 buckets must not be publicly readable.");
                }
            }),
        },
    ],
});

Run it locally against a stack with pulumi preview --policy-pack ./golden-path-guardrails, or publish it with pulumi policy publish and enable it organization-wide so every team consuming the golden path inherits the guardrail automatically, with no extra step on their part. That is the model covered in depth in the next post in this series, How to Implement Robust Security Guardrails Using Policy as Code: start there for policy groups, remediation policies, and enforcement levels across an entire organization.

How to Measure Golden Path Success

Golden paths aren’t complete until they deliver measurable value. Use these KPIs to assess performance and drive iteration:

Adoption Metrics

  • Template usage rate: Percentage of new projects using golden paths
  • Component reuse: Number of stacks consuming shared components
  • Time to first deployment: Deployment time and frequency from code to production

Quality Metrics

  • Security compliance rate: Percentage of deployments passing security policies
  • Stability: Deployment incident frequency
  • Mean time to recovery (MTTR): Time to recover from production issues

Developer Experience Metrics

  • Developer satisfaction: Survey teams about their platform experience
  • Support ticket volume: Ticket volume related to infrastructure
  • Contribution: Number of PRs or issues submitted to platform templates

Real-World Results: Success Stories

Organizations using golden paths report significant improvements in speed and consistency:

  • Snowflake reduced deployment time from 1.5 weeks to less than a day
  • Starburst Data cut deployment time from 2 weeks to 3 hours, a 112x improvement
  • Mercedes-Benz R&D North America moved hundreds of microservices to the cloud on one toolset shared by its application and infrastructure teams

Golden paths give developers a faster route to production and give platform teams a consistent one to maintain. To learn more, download the whitepaper: The Golden Path to Cloud Success: Your IDP Roadmap.

Common Pitfalls and How to Avoid Them

Pitfall 1: Over-Abstraction

Problem: Creating components so abstract they’re unusable Solution: Start with concrete use cases, then generalize based on actual patterns

Pitfall 2: Insufficient Escape Hatches

Problem: Golden paths become golden cages Solution: Always provide ways to extend or override default behavior

Pitfall 3: Poor Versioning Strategy

Problem: Breaking changes without migration paths Solution: Maintain backward compatibility and provide clear upgrade guides

Pitfall 4: Lack of Ownership

Problem: Templates become orphaned and outdated Solution: Assign clear ownership and establish maintenance schedules

The Future of Golden Paths: AI and Beyond

The future of golden paths is intelligent, cross-cloud, and fully integrated with modern workflows.

AI-Enhanced Templates

Imagine templates that adapt based on your application’s actual behavior, automatically tuning resources and configurations for optimal performance and cost.

Cross-Cloud Portability

Components that abstract not just resources but entire cloud providers, enabling true multi-cloud golden paths.

GitOps-Native Workflows

Templates that include not just infrastructure but complete GitOps pipelines, from code commit to production deployment.

Your First Steps to Golden Paths

Ready to get started? Here’s your action plan:

  1. Audit Current Patterns: Document the infrastructure patterns your teams use most frequently
  2. Build one reusable component: Pick your most common pattern and build a reusable component
  3. Create Your First Template: Build a complete golden path for one project type
  4. Gather Feedback: Deploy with a pilot team and iterate based on their experience
  5. Scale Gradually: Expand your component library and template catalog based on demand
  6. Measure and Iterate: Track adoption and continuously improve based on metrics

Frequently asked questions

What is a golden path in platform engineering?

A golden path is the opinionated, well-supported route a platform team builds for a common infrastructure workflow, such as spinning up a new service. It packages best practices into reusable components and templates so following the path is faster than building from scratch, while the underlying implementation stays visible and extensible.

What is the difference between a golden path and a paved road?

The terms describe the same idea from two engineering cultures: Spotify popularized “golden path” and Netflix popularized “paved road.” Both describe a supported, opinionated default that a platform team maintains, with the freedom for teams to leave the path for a genuine edge case.

What makes a golden path different from a service catalog?

A service catalog gives developers a menu; a golden path gives them a paved road. The distinction matters once you start comparing platform-engineering tools, because popular catalog and portal tools often solve discovery problems without ever provisioning anything themselves.

Backstage, for example, is a software catalog and a scaffolder: its Software Templates generate a new repository and open a pull request, then hand off to whatever CI and IaC tooling that repository is wired to. Port and Cortex follow a similar shape, orchestrating external webhooks, GitHub Actions, or Terraform runs to carry out the actions a developer requests through the portal. In each case, the golden path is defined in one system and executed by another, so keeping the two in sync is an integration problem the platform team owns.

Pulumi collapses that hand-off. A golden path built with Pulumi components and templates is defined in the same general-purpose language and the same IaC engine that provisions the infrastructure, published to your private registry and secured with Pulumi Policies. No second system has to stay in sync, because the catalog entry and the thing that runs are the same artifact. For a deeper comparison of the two approaches, see Backstage vs. Pulumi IDP: Why Infrastructure-First Platform Engineering Matters.

Do golden paths stop teams from going off-path?

No, and a golden path that forbids deviation stops being a path and becomes a golden cage, one of the pitfalls covered above. The platform team’s job is to make the paved road so much easier than the alternative that most teams choose it without being told to, not to remove the choice. Teams that opt out own the operational and security work the platform team would otherwise have handled for them.

How does policy as code keep self-service safe?

Self-service only works if the platform team can trust what gets deployed without reviewing every change by hand. Pulumi Policies close that gap by attaching automated guardrails directly to the golden paths you publish, written as ordinary code in the same language as the golden path itself, so a stack that violates a security or cost standard fails at pulumi preview instead of in production. See Enforce guardrails with policy as code above for a working policy pack example, and How to Implement Robust Security Guardrails Using Policy as Code for the full model across an organization.

How do you measure golden path adoption?

Track adoption, quality, and developer-experience metrics together: the percentage of new services created from a template, security-compliance rate, mean time to recovery, developer-satisfaction survey results, and support-ticket volume tied to infrastructure. See How to Measure Golden Path Success above for the full breakdown.

How long does it take to build a first golden path?

Most teams can ship a first golden path, one component plus one template covering a single common workflow, in a few days to a couple of weeks, since the goal is a narrow, real use case rather than a fully general platform. The golden path maturity stages above describe how it grows from there.

Conclusion: From Fragmentation to Flow

Golden paths remove friction from the development process, encode expertise into reusable patterns, and empower developers to move at the speed of business, without restricting creativity or forcing rigid standards.

By building a library of components and templates, you transform your Internal Developer Platform from a collection of tools into a force multiplier for your entire engineering organization. You give developers the gift of not having to solve solved problems, while maintaining the flexibility to innovate where it matters.

Start small. Solve one problem well. Expand from there. With the right foundation, your developer platform will evolve into a system of self-service, speed, and stability.

Ready to build your golden paths

Next in our series: How to Implement Robust Security Guardrails Using Policy as Code. You will learn how to add automated guardrails that make sure every deployment meets your security and compliance standards without slowing down development.

Archived feature image Original feature image for this post

Related posts

The infrastructure as code platform for any cloud.