Platform Engineering

How to Build Golden Paths in Platform Engineering

golden paths in platform engineering

Golden Paths have become one of the core building blocks of platform engineering. They allow a set of common workflows to be self-served through automation and templates, without the need for human intervention.

They are also well suited to serve as the foundation for scaling AI and agents on top of existing platforms. Agents are good at following well-structured paths with predefined guardrails, checks, and scope. However, the exact paths must be adjusted to fit how agents work.

The 2025 DORA State of AI-assisted Software Development report found that 90% of organizations already run at least one internal platform, and when platform quality is high, the effect of AI adoption on organizational performance becomes strong and positive.

In this blog, we walk through a mental model and how to design and run Golden Paths, especially for the agentic era. We finish with a worked example on Spacelift: a service that needs a Postgres database on AWS.

TL;DR

A Golden Path is an opinionated, documented, supported, self-service route for a common engineering task, like provisioning a database or standing up a new service. Platform teams own them. Product teams and, increasingly, AI agents consume them.

  • Five building blocks: IaC modules, pipeline definitions, policy guardrails, documentation, and a portal entry
  • Four phases to roll one out: Understand the current state, build templates and guardrails, pilot and iterate, and plan for day 2.
  • Serve agents the same path as humans, through an API or MCP server, with typed inputs, scoped identities, and approvals on production.
  • Measure adoption first. Delivery metrics only mean something once people use the path.

What is a Golden Path?

The term Golden Path was introduced by Spotify as the “opinionated and supported” path to “build something”, such as a backend service or a data pipeline. Here is a working definition: A Golden Path is an opinionated, documented, supported, self-service route for a common task.

  • Opinionated means the platform team has already made sane, secure, default choices for users.
  • Documented means a developer (or, increasingly, an agent), can find the appropriate path and clear instructions on how to use it.
  • Supported means the platform team owns and maintains it.
  • Self-service means consumers can use Golden Paths at will, without other dependencies.

Where do Golden Paths fit in platform engineering?

Golden Paths sit on top of developer platforms and portals, providing the delivery mechanism for workflows. Platform teams build and own Golden Paths. Product and application teams consume them, without needing to understand the infrastructure underneath.

Realistically, a Golden Path can’t cover every case, nor does it have to. Most platform teams aim for the common, repeatable 80%: new services, commonly used databases, queues, storage buckets, and preview environments.

The remaining 20% (e.g., teams that need a specialized database or latency-sensitive workloads with custom networking) go through an exception process. And that’s fine; accept that you won’t be able to cover all use cases through Golden Paths.

What is the difference between Golden Paths and guardrails?

Guardrails are important components of functional Golden Paths. They provide hard limits, sane defaults, secure configurations, baked into the automated workflows. In practice, the two work together to form a nice user experience, without compromising enterprise security and governance standards.

What are the building blocks of a Golden Path

A Golden Path is a set of components, owned by the platform team, provided to the rest of the organization:

  • Standardized templates and modules: In the case of infrastructure, that’s Terraform or OpenTofu modules (or Pulumi components, or CDK constructs) that include your default configurations. A good module exposes some necessary inputs and hides the rest.
  • CI/CD pipeline definitions: Every path needs a standard way to test and apply changes. For infrastructure paths, which involve the IaC pipelines; for application paths, it includes building, testing, and deploying. Reusable pipeline definitions keep these consistent across teams.
  • Policy and compliance guardrails: Policy as code validates changes before they reach production. Open Policy Agent, Checkov, and AWS Service Control Policies are common choices, often in combination.
  • Documentation: Each path needs clear documentation on supported and unsupported use cases, usage, inputs, outputs, and ownership. Docs now have to serve both people and agents, so they should be adjusted accordingly. Markdown versions of docs, an llms.txt file at the root of your docs site to give agents an index, and an AGENTS.md file in each template repository are all necessary for agent consumption.
  • A platform portal is where users discover and use these components. Most teams expose Golden Paths through a service catalog: a list of things you can request, each backed by a template. A good platform portal also provides governance and traceability features on who created what and from which version of the path.

How to design and roll out a Golden Path

There are four phases for designing and rolling out a Golden Path:

Phase 1: Understand the current state

Document existing processes, and try to understand the users’ pain points and bottlenecks. Inventory the infrastructure and tooling you already run, so your paths can fit nicely with existing flows. Investigate ticket data to figure out how much time common tasks take end to end. Pick a few of the most common, slow tasks first, then attempt to optimize and standardize them.

Phase 2: Build the templates and guardrails

For infrastructure workflows, standardizing means building reusable IaC modules and templates. Expose the necessary inputs (try to keep inputs as minimal as possible, more inputs mean more user decisions) and publish it in your service catalog. Think, write, test, and apply relevant policies on the path.  

As you won’t be able to cover all use cases, think about how to support exception processes up front. 

Phase 3: Roll out and iterate

Pilot with one team or one project that has the problem you are trying to solve and gather feedback. Adjust, fix the issues, and then expand to more use cases or teams. Every new path that you introduce, has to be treated as a long-term product that has to be maintained and improved, as now users and agents might depend on it.

Phase 4: Plan for day 2 operations

Golden Paths must also provide maintenance and operation options. Creating infrastructure and resources is part of the job, but version upgrades, patching, and other common tasks are also necessary. 

The paths themselves should also be treated like any other versioned artifact. Use semantic versioning for modules and templates and publish a changelog.  Roll out new versions gradually, starting with non-production environments. Where you can, automate the upgrade itself.

  • Drift detection: Run scheduled plans against stacks created from a path, and alert when live state differs from code. Regressions and drift could happen for various reasons.
  • Deprecation: Decide how long you support old versions, and announce end-of-life dates and schedules.
  • Decommissioning: Every path that creates resources needs a way to delete them, including backups and other manifests. Short-lived environments, useful for experimentation and testing, should expire on their own.

Golden Paths for AI agents and AI workloads

The design work above holds for agents too. What changes is who is reading the path: an agent needs the same workflow exposed as a callable, typed operation, with its own identity and its own limits.

Agents as platform users

AI agents are a new class of a platform user. Coding agents such as Claude Code, Cursor, Kiro, and OpenAI Codex write Terraform and open pull requests. SRE agents read logs, investigate live systems, and increasingly propose fixes and take actions.

These agents should be able to use golden paths as humans. If an agent needs an infrastructure component to complete a task, instead of freestyling, guessing, and creating components with their generic knowledge, they should be able to provision it through pre-approved templates with policy checks included.

Platform components that need adjustment

Most Golden Paths were initially built to be accessed by humans through a portal, so serving agents means reworking a few components.

Interfaces: Every path needs an API and an MCP server next to the UI. The Model Context Protocol has become the common way to expose platform actions to agents. Many portals and IaC platforms now ship an MCP server of their own. For example, check out the Spacelift MCP.

  • Typed inputs and structured output: Inputs need schemas with allowed values, so agents can read them and structure their work. Output (status, errors, policy denials) needs to be structured so the agent can act on it.
  • Identity: Agents need their own identities with short-lived, scoped credentials. Each action maps to a specific agent, and you can revoke an agent’s access if needed.
  • Context: Docs written for retrieval, plus repository files (such as AGENTS.md or skills) that tell an agent which paths exist and when to use each.

Guardrails for agents

We established that guardrails are an important security layer for platforms. For AI and agent workflows, they become even more important, considering the scaling effects these tools have on systems and platforms:

  • Scopes per agent identity: Limit which environments and actions each agent can touch.
  • Approval gates: Require human approval for critical state-changing or deploy operations in production.
  • Rate and budget limits: Apply rate and budget limits per identity to avoid cases where agents go rogue or loop out of control.
  • Audit trails: Record all agent actions.

Start agents with read-only access. Move to scoped writes in non-production environments, then to production changes behind approvals. Autonomy is earned per workflow, based on how the agent performs. We covered this adoption path in more depth in AI in Platform Engineering: When Agents Join Your Platform, and the full control set in Guardrails for AI-Generated Infrastructure.

One golden path, served to developers and agents

How to measure Golden Path success

The most important thing to measure is adoption. If no one is using your platform, none of the other metrics matter. Start tracking new resources created through Golden Paths, active users, how many times paths were used, and percentages or resources created through paths vs created via different pathways.

Once you validate meaningful adoption,track delivery metrics: deployment frequency, lead time for changes, change failure rate, and incident rate. Compare these with other pathways and see how your golden paths compare.

Talk to your users. It’s equally important to get user feedback and qualitative metrics on user experience, as data and metrics don’t always tell the whole story. Combine data with user inputs, and you will get a more holistic view of your current state and where you should focus improvement efforts.

A common trend is also introducing agent-specific metrics. A few ideas:

  • Share of agent-initiated changes: How much of your infrastructure change volume comes from agents, per path.
  • Error and rollback rate: How often agent-initiated changes fail or get reverted, compared to human changes on the same path.
  • Cost per agent run: Tokens and compute spent per change.

On the business side, generic things that you can measure include developer onboarding time (days until a new engineer ships to production) and time to market for new features.

Build Golden Paths with Spacelift

Spacelift is an infrastructure orchestration platform for OpenTofu, Terraform, Pulumi, CloudFormation, Ansible, and Kubernetes. Its building blocks map closely to the Golden-Path components described above.

Map the building blocks

Each building block from the section above maps to a Spacelift concept you can version, attach, and audit:

  • IaC modules: The module registry hosts versioned Terraform and OpenTofu modules, with tests that run on each new version.
  • Stacks: A stack is Spacelift’s unit of deployment: source code, state, configuration, and the credentials it runs with. Every resource created through a golden path ends up in a stack.
  • Spaces: Spaces are the access boundary. They group stacks, policies, contexts, and cloud integrations. Access rules on a space decide who can read or change what’s inside it. Child spaces can inherit from parents, so a platform team defines shared policies.
  • Credentials: Cloud integrations for the major cloud providers generate short-lived credentials for each run, so no static keys sit in stacks. Contexts hold shared configuration (environment variables, files, hooks) and attach to stacks.
  • Policies and guardrails: Policies are written in Rego and run on Open Policy Agent. Plan policies inspect proposed changes; approval policies require reviews before a run applies; push and trigger policies control when runs start; login policies control access. Attached through spaces or labels, they apply to every stack.
  • Blueprints: Blueprints are YAML-defined forms that create a configured stack from user inputs. The resulting stack lives independently and can be edited.
  • Templates: Templates, add lifecycle management. Each published version is pinned to a commit, and stacks created from a template are immutable: they change only when the deployment moves to a new template version or its inputs change. This is the linked model from the versioning section, and it makes an upgrade across multiple deployments practical.
  • Drift detection: Scheduled drift detection runs proposed plans on a cron schedule and can reconcile automatically.

Open the path to AI agents

Agents reach Spacelift through three interfaces: two from outside the UI, one inside it.

  • Spacelift MCP server: Spacelift hosts an MCP server for each account. It exposes tools to discover the GraphQL API schema, run read-only queries, inspect provider schemas, run mutations, and manage resources.
  • Intent: Spacelift Intent provisions infrastructure from natural language, through an MCP client such as Claude Code, Cursor, ChatGPT, or VS Code. Intent calls Terraform provider operations directly under Spacelift governance. Intent policies check every write before it runs. Intent fits exploration and sandbox work better than long-lived production resources.
  • Infra Assistant: Infra Assistant is a chat panel in the Spacelift UI. In Ask mode, it is read-only: it explains stacks and failed runs, or searches the docs for you. In Build mode, it can create and change resources through conversation, governed by the same Intent policies.

Together, these tools give Golden Paths agent-facing interfaces with the same policies behind them.

Demo Golden Path: A service needs a Postgres database on AWS

A developer team is building a new service and needs a Postgres database. With a Golden Path, they have a self-service path to fill in inputs (or ask their coding agent to) and get an encrypted, private database with same-day backups, its credentials stored in a secret store, and its configuration as IaC with OpenTofu.

The Postgres golden path on Spacelift

To follow along, create a Spacelift free trial, connect the Spacelift MCP to your favorite agentic tool, and ask it to set up the example golden path from this blog and onboard you to Spacelift! Here’s also a demo code repository to check all the files in detail. Let’s get to it:

Step 1: Publish the module

The platform team publishes an rds-postgres module to the Spacelift module registry. Its interface is small: four inputs a developer chooses, and two the platform supplies.

variables.tf

variable "service_name" {
  type        = string
  description = "Owning service, lowercase letters, digits, and hyphens"
  validation {
    condition     = can(regex("^[a-z][a-z0-9-]{1,40}$", var.service_name))
    error_message = "service_name must start with a letter and use only lowercase letters, digits, and hyphens (max 41 chars)."
  }
}
 
variable "environment" {
  type = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be dev, staging, or prod."
  }
}
 
variable "size" {
  type    = string
  default = "small"
  validation {
    condition     = contains(["small", "medium", "large"], var.size)
    error_message = "size must be small, medium, or large."
  }
}
 
variable "owner_team" {
  type = string
}
 
# Supplied by the platform through a Spacelift context, never by the developer
variable "vpc_id" {
  type = string
}
 
variable "subnet_ids" {
  type = list(string)
}

Inside the module, the platform team’s choices are fixed:

main.tf

locals {
  is_prod = var.environment == "prod"
  name    = "${var.service_name}-${var.environment}"
  instance_class = {
    small  = "db.t4g.medium"
    medium = "db.r7g.large"
    large  = "db.r7g.2xlarge"
  }[var.size]
}
 
resource "aws_db_instance" "this" {
  identifier     = local.name
  engine         = "postgres"
  engine_version = "17"
  instance_class = local.instance_class
  db_name        = replace(var.service_name, "-", "_")
  username       = "app_admin"
 
  allocated_storage     = 50
  max_allocated_storage = 500
  storage_type          = "gp3"
  storage_encrypted     = true
 
  db_subnet_group_name   = aws_db_subnet_group.this.name
  vpc_security_group_ids = [aws_security_group.db.id]
  publicly_accessible    = false
 
  multi_az                            = local.is_prod
  backup_retention_period             = local.is_prod ? 30 : 7
  deletion_protection                 = local.is_prod
  skip_final_snapshot                 = !local.is_prod
  final_snapshot_identifier           = local.is_prod ? "${local.name}-final" : null
  copy_tags_to_snapshot               = true
  auto_minor_version_upgrade          = true
  apply_immediately                   = !local.is_prod
  manage_master_user_password         = true
  iam_database_authentication_enabled = true
 
  tags = {
    Service     = var.service_name
    Environment = var.environment
    Owner       = var.owner_team
    ManagedBy   = "golden-path/rds-postgres"
  }
}
 
# DB subnet group, security group, and ingress rule: see the companion repository

Production gets Multi-AZ, 30-day backups, deletion protection, and a final snapshot on delete. Dev and staging have cheaper configs. Credentials are stored in Secrets Manager, and storage is encrypted. The module creates its own DB subnet group and a security group that allows Postgres only from inside the VPC.

The outputs give the service team what it needs to connect:

outputs.tf

output "endpoint" {
  value = aws_db_instance.this.endpoint
}
 
output "master_user_secret_arn" {
  value = aws_db_instance.this.master_user_secret[0].secret_arn
}


output "db_name" {
  value = aws_db_instance.this.db_name
}
 
output "security_group_id" {
  value = aws_security_group.db.id
}

A .spacelift/config.yml file in the module directory sets module_version: 1.0.0. Spacelift publishes that version to the account’s private registry, where stacks reference it as spacelift.io/<account>/rds-postgres/aws.

Step 2: Wrap it in a template

A small root configuration in the golden-paths repository calls the module from the registry. Region and credentials come from the environment, so the provider block stays empty:

provider "aws" {}
 
module "postgres" {
  source  = "spacelift.io/acme/rds-postgres/aws"
  version = "~> 1.0"
 
  service_name = var.service_name
  environment  = var.environment
  size         = var.size
  owner_team   = var.owner_team
  vpc_id       = var.vpc_id
  subnet_ids   = var.subnet_ids
}
 
# Variable declarations and outputs that re-export the module's outputs omitted

The platform team then defines a Spacelift template around it:

postgres.yaml

space: ${{ maps[inputs.environment].space }}
 
inputs:
  - id: service_name
    name: Service name
    type: short_text
    validations:
      required: true
      pattern: "^[a-z][a-z0-9-]{1,40}$"
  - id: environment
    name: Environment
    type: select
    options:
      - dev
      - staging
      - prod
  - id: size
    name: Database size
    type: select
    default: small
    options:
      - small
      - medium
      - large
  - id: owner_team
    name: Owning team
    type: short_text
    validations:
      required: true
      pattern: "^[a-z][a-z0-9-]{1,40}$"
 
# Space and integration values are generated IDs from your account
maps:
  dev:
    space: golden-paths-nonprod-XXXX
    aws_integration: XXXXX
  staging:
    space: golden-paths-nonprod-XXXX
    aws_integration: XXXXX
  prod:
    space: golden-paths-prod-XXXX
    aws_integration: XXXXX
 
stacks:
  - key: postgres
    name: ${{ inputs.service_name }}-postgres-${{ inputs.environment }}
    labels:
      - golden-path/postgres
      - env/${{ inputs.environment }}
      - team/${{ inputs.owner_team }}
    autodeploy: true
    worker_pool: XXXXX
    vcs:
      reference:
        value: main
        type: branch
      repository: golden-paths
      provider: GITHUB
      namespace: acme-platform
      project_root: postgres
    vendor:
      terraform:
        manage_state: true
        workflow_tool: OPEN_TOFU
        version: "1.10.3"
    environment:
      variables:
        - name: TF_VAR_service_name
          value: ${{ inputs.service_name }}
        - name: TF_VAR_environment
          value: ${{ inputs.environment }}
        - name: TF_VAR_size
          value: ${{ inputs.size }}
        - name: TF_VAR_owner_team
          value: ${{ inputs.owner_team }}
    attachments:
      contexts:
        - id: aws-network-${{ inputs.environment }}
          priority: 1
      clouds:
        aws:
          id: ${{ maps[inputs.environment].aws_integration }}
          read: true
          write: true
    schedules:
      drift:
        cron:
          - "0 3 * * *"
        reconcile: false
        timezone: UTC

The maps block routes to each environment. Dev and staging deployments land in a non-production space, with production in its own space. Spaces and cloud integrations are referenced by their generated IDs.

The aws-network-<env> contexts supply AWS_REGION, TF_VAR_vpc_id, and TF_VAR_subnet_ids. The input patterns reject an invalid service name in the form, before any run starts. With autodeploy: true, a plan that passes the policies applies without a manual confirmation; production still waits for a reviewer (check Step 3). Every deployment gets a nightly drift check.

Publishing this as version 1.0 pins it to the current commit; when the platform team bumps the Postgres version or changes a default, they publish 1.1, and each deployment moves over when its owners (or the platform team) choose to.

Step 3: Attach the guardrails

A plan policy checks every aws_db_instance that a run creates, updates, or replaces:

rds-guardrails.plan.rego

package spacelift
 
import rego.v1
 
allowed_classes := {"db.t4g.medium", "db.r7g.large", "db.r7g.2xlarge"}
 
# Creates, updates, and replacements; plain deletes have no "after" state
db_changes contains rc if {
  some rc in input.terraform.resource_changes
  rc.type == "aws_db_instance"
  some action in rc.change.actions
  action in {"create", "update"}
}
 
deny contains sprintf("%s: public access is not allowed", [rc.change.after.identifier]) if {
  some rc in db_changes
  rc.change.after.publicly_accessible
}
 
deny contains sprintf("%s: storage must be encrypted", [rc.change.after.identifier]) if {
  some rc in db_changes
  not rc.change.after.storage_encrypted
}
 
deny contains sprintf("%s: instance class %s is not approved", [rc.change.after.identifier, rc.change.after.instance_class]) if {
  some rc in db_changes
  not rc.change.after.instance_class in allowed_classes
}
 
deny contains sprintf("%s: missing Owner tag", [rc.change.after.identifier]) if {
  some rc in db_changes
  not rc.change.after.tags.Owner
}

The policy lives in the parent golden-paths space with the autoattach:* label, so it attaches to every stack in that space and its child spaces.

Production environments also have an approval policy in the same space that carries the autoattach:env/prod label, so it attaches only to stacks the template labels env/prod:

prod-review.approval.rego

package spacelift
 
import rego.v1
 
approve if {
  count(input.reviews.current.approvals) >= 1
  count(input.reviews.current.rejections) == 0
}
 
reject if count(input.reviews.current.rejections) > 0

A production run now waits for one approval and stops on any rejection. Dev and staging stacks don’t carry the label, so their runs apply as soon as the plan policy passes.

Step 4: Deploy it, as a human or as an agent

A developer opens the templates list in Spacelift, picks “Postgres for services”, fills in checkout, staging, small, and payments, and deploys. Spacelift creates the checkout-postgres-staging stack in the non-production space, attaches the staging context and AWS integration, runs the plan, evaluates rds-guardrails, and applies.

A few minutes later, the stack outputs hold everything the service needs:

db_name                = "checkout"
endpoint               = "checkout-staging.c1a2b3c4d5e6.eu-west-1.rds.amazonaws.com:5432"
master_user_secret_arn = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:rds!db-..."
security_group_id      = "sg-0a1b2c3d4e5f67890"

The same developer could instead type this into Claude Code, with the Spacelift MCP server connected:

The checkout service needs a small Postgres database in staging. The owner is the payments team. Use our golden path.

The agent uses the MCP server’s discover and query tools to find templates labeled golden-path/postgres and read their input schema. It then calls mutate to create a deployment with the same four inputs a human would type into the form, and polls the run until it finishes. A production request then hits the approval policy, and a person needs to approve.

The full example, module configuration, template, and policies are available in the companion repository.

Key takeaways

A Golden Path is a product, not a template. It needs an owner, a version, a changelog, and a deprecation date, or you end up with a catalog nobody trusts and a queue that never shrinks. The agent part changes less than it looks. If your paths are already documented, versioned, and policy-checked, serving an agent is mostly a question of exposing them through an API or an MCP server instead of a form.

Spin up a free trial, connect the Spacelift MCP server to your coding agent, and have it build the path with you.

Solve your infrastructure challenges

Spacelift is a flexible orchestration solution for IaC development. It delivers enhanced collaboration, automation, and controls to simplify and accelerate the provisioning of cloud-based infrastructures.

Learn more

Frequently asked questions

  • What's the difference between a Golden Path and a paved road?

    “Paved road” and “Golden Path” are generally used interchangeably to describe the opinionated, supported route for a common engineering task. Netflix popularized “paved road” while Spotify coined “Golden Path,” and most practitioners now treat the two as synonyms.

  • What's the difference between a Golden Path and a service catalog?

    A Golden Path is an opinionated, supported workflow that guides developers through tasks like creating a service, while a service catalog is an inventory that tracks existing services with their owners, dependencies, and metadata. One shows how to build, the other shows what already exists.

  • Who owns Golden Paths in an organization?

    The platform engineering team owns Golden Paths and treats them as internal products with versioning, documentation, and support. Security, SRE, and compliance groups typically contribute policies and guardrails that get baked into the path.

  • What are examples of Golden Paths?

    Typical examples include bootstrapping a new microservice with CI/CD, Dockerfile, Helm chart, and dashboards pre-wired, provisioning a managed database, spinning up an ephemeral preview environment, and deploying a stateless API with observability, security scanning, and ownership metadata registered automatically in the catalog.