Pulumi brings infrastructure provisioning into the domain of general-purpose programming languages, replacing YAML templates and DSLs with real code. The Go SDK is particularly well-suited to platform teams already running Go services: the same type system, testing tools, dependency management, and CI pipelines that govern application code govern infrastructure. Resources are declared as Go structs, outputs are strongly typed, and the entire graph of dependencies is inferred by the Pulumi engine at runtime—no explicit DependsOn annotations needed for the common case.
A production IaC codebase demonstrates what this looks like at production scale. Infrastructure is organized into a 13-phase deployment pipeline in which each phase is a distinct Pulumi program that reads outputs from earlier phases and writes its own exports. Phases range from foundational networking (VPCs, subnets, DNS zones) through compute clusters, managed databases, and observability stacks, up to application-layer resources like Kubernetes namespaces and service accounts. Each phase can be deployed independently for incremental rollouts or tested in isolation in ephemeral stacks.
Multi-environment stack management is the other axis of the design. A single Pulumi program runs against dev, staging, and prod by selecting the appropriate stack—pulumi stack select prod—and reading environment-specific configuration from pulumi config. This means the same Go code that provisions a single-node dev database provisions a highly-available prod cluster with read replicas; the difference is configuration, not code. This same pattern can provision ephemeral evaluation harness environments used for LLM quality benchmarking.
Every Pulumi resource call (e.g., ec2.NewInstance) returns a resource object whose fields are pulumi.Output[T] values rather than plain Go values. Outputs are promises: they resolve after the resource is created. You compose Outputs using pulumi.All(...).ApplyT(...) or type-safe Output.ApplyT chains. The Pulumi engine builds a DAG from these output-to-input relationships and uses it to determine the optimal parallelism and ordering for all create, update, and delete operations.
A Pulumi stack is a named instantiation of a program—think of it as an environment slot. Each stack has its own state file (stored in the Pulumi Cloud backend, S3, or a local file) and its own configuration key-value store. Programs read stack config via config.Require or config.Get. Secrets are encrypted at rest using a stack-specific data key. In the infrastructure project, stack names follow the convention <phase>-<env> (e.g., networking-prod, compute-staging), making the relationship between phase and environment unambiguous.
A pulumi.StackReference lets one Pulumi program read the exported outputs of another stack. This is the primary mechanism for cross-phase dependency in the 13-phase pipeline: the compute phase reads the VPC ID and subnet IDs exported by the networking phase without duplicating their definitions. Stack references enforce a strict separation of concerns—later phases cannot accidentally modify earlier-phase resources, and the dependency graph across phases is explicit and auditable.
Every resource constructor accepts variadic pulumi.ResourceOption arguments that control lifecycle behavior. pulumi.DependsOn adds explicit ordering edges (useful when one resource logically depends on another but does not use its outputs). pulumi.IgnoreChanges prevents drift detection on fields managed outside Pulumi. pulumi.Protect guards stateful resources like databases from accidental deletion. pulumi.Provider injects an explicit cloud provider instance, enabling multi-region or multi-account deployments within a single stack.
The Pulumi Automation API exposes the full Pulumi lifecycle—stack creation, config set, preview, up, destroy, output retrieval—as Go function calls rather than CLI invocations. The infrastructure project’s pipeline orchestrator uses the Automation API to execute the 13 phases in sequence, passing outputs between them programmatically. This eliminates shell scripts as the glue layer and enables rich error handling, structured logging, and integration testing of the deployment pipeline itself.
A Pulumi Go program’s entry point is a call to pulumi.Run with a function that declares resources. The engine calls this function, records every resource declaration into an in-memory DAG, diffs the DAG against the current state, and executes the minimal set of operations to converge actual state to desired state.
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
env := cfg.Require("environment") // "dev" | "staging" | "prod"
vpc, err := ec2.NewVpc(ctx, "main-vpc", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.0.0.0/16"),
EnableDnsHostnames: pulumi.Bool(true),
Tags: pulumi.StringMap{
"Name": pulumi.String("main-vpc-" + env),
"Environment": pulumi.String(env),
"ManagedBy": pulumi.String("pulumi"),
},
})
if err != nil {
return err
}
ctx.Export("vpcId", vpc.ID())
ctx.Export("vpcCidr", vpc.CidrBlock)
return nil
})
}
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/eks"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
env := cfg.Require("environment")
// Read networking outputs from the prior phase.
netRef, err := pulumi.NewStackReference(ctx,
"org/networking-"+env+"/"+env, nil)
if err != nil {
return err
}
vpcId := netRef.GetOutput(pulumi.String("vpcId")).
ApplyT(func(v interface{}) string {
return v.(string)
}).(pulumi.StringOutput)
privateSubnetIds := netRef.GetOutput(
pulumi.String("privateSubnetIds")).
ApplyT(func(v interface{}) []string {
raw := v.([]interface{})
ids := make([]string, len(raw))
for i, id := range raw {
ids[i] = id.(string)
}
return ids
}).(pulumi.StringArrayOutput)
cluster, err := eks.NewCluster(ctx, "main-cluster", &eks.ClusterArgs{
Name: pulumi.String("cluster-" + env),
RoleArn: pulumi.String(cfg.Require("eksRoleArn")),
VpcConfig: &eks.ClusterVpcConfigArgs{
SubnetIds: privateSubnetIds,
},
})
if err != nil {
return err
}
ctx.Export("clusterName", cluster.Name)
ctx.Export("clusterEndpoint", cluster.Endpoint)
return nil
})
}
package pipeline
import (
"context"
"fmt"
"github.com/pulumi/pulumi/sdk/v3/go/auto"
"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"
)
type Phase struct {
Name string
WorkDir string
}
func RunPhase(ctx context.Context, phase Phase, env string) (auto.OutputMap, error) {
stackName := auto.FullyQualifiedStackName("myorg", phase.Name, env)
stack, err := auto.UpsertStackLocalSource(ctx, stackName, phase.WorkDir)
if err != nil {
return nil, fmt.Errorf("init stack %s: %w", phase.Name, err)
}
// Install dependencies declared in go.mod.
if err := stack.Workspace().InstallPlugin(ctx, "aws", "v6.0.0"); err != nil {
return nil, fmt.Errorf("install plugin: %w", err)
}
// Set environment config before running.
if err := stack.SetConfig(ctx, "environment",
auto.ConfigValue{Value: env}); err != nil {
return nil, fmt.Errorf("set config: %w", err)
}
res, err := stack.Up(ctx, optup.ProgressStreams(os.Stdout))
if err != nil {
return nil, fmt.Errorf("up %s: %w", phase.Name, err)
}
return res.Outputs, nil
}
// RunPipeline executes all 13 phases in order for a given environment.
func RunPipeline(ctx context.Context, env string) error {
phases := []Phase{
{Name: "networking", WorkDir: "./phases/01-networking"},
{Name: "dns", WorkDir: "./phases/02-dns"},
{Name: "iam", WorkDir: "./phases/03-iam"},
{Name: "storage", WorkDir: "./phases/04-storage"},
{Name: "databases", WorkDir: "./phases/05-databases"},
{Name: "compute", WorkDir: "./phases/06-compute"},
{Name: "eks-cluster", WorkDir: "./phases/07-eks-cluster"},
{Name: "eks-nodegroups", WorkDir: "./phases/08-eks-nodegroups"},
{Name: "observability", WorkDir: "./phases/09-observability"},
{Name: "ingress", WorkDir: "./phases/10-ingress"},
{Name: "secrets", WorkDir: "./phases/11-secrets"},
{Name: "app-namespaces", WorkDir: "./phases/12-app-namespaces"},
{Name: "service-accounts",WorkDir: "./phases/13-service-accounts"},
}
for _, phase := range phases {
if _, err := RunPhase(ctx, phase, env); err != nil {
return fmt.Errorf("phase %s failed: %w", phase.Name, err)
}
}
return nil
}
package infra
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
type DatabaseConfig struct {
InstanceClass string
AllocatedGB int
MultiAZ bool
BackupRetention int
}
// envDefaults returns sensible defaults per environment, overridable
// via pulumi config set to avoid committing secrets or sizes.
func envDefaults(env string) DatabaseConfig {
switch env {
case "prod":
return DatabaseConfig{
InstanceClass: "db.r6g.xlarge",
AllocatedGB: 200,
MultiAZ: true,
BackupRetention: 30,
}
case "staging":
return DatabaseConfig{
InstanceClass: "db.t3.medium",
AllocatedGB: 50,
MultiAZ: false,
BackupRetention: 7,
}
default: // dev
return DatabaseConfig{
InstanceClass: "db.t3.micro",
AllocatedGB: 20,
MultiAZ: false,
BackupRetention: 1,
}
}
}
func DatabaseArgs(ctx *pulumi.Context) DatabaseConfig {
cfg := config.New(ctx, "")
env := cfg.Require("environment")
base := envDefaults(env)
if v, err := cfg.Try("db:instanceClass"); err == nil {
base.InstanceClass = v
}
return base
}
Component resources are Go structs that embed pulumi.ResourceState and group related child resources under a single logical name. The infrastructure project defines components like PrivateSubnetGroup (a subnet pair across two AZs plus a route table) and ManagedDatabase (an RDS instance with its parameter group, subnet group, and security group). Consumers instantiate the component once and reference its output fields; all child resources are automatically namespaced under the component in the Pulumi state tree.
package components
import (
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
type PrivateSubnetGroup struct {
pulumi.ResourceState
SubnetAId pulumi.IDOutput
SubnetBId pulumi.IDOutput
}
func NewPrivateSubnetGroup(
ctx *pulumi.Context,
name string,
vpcId pulumi.StringInput,
cidrA, cidrB string,
azA, azB string,
opts ...pulumi.ResourceOption,
) (*PrivateSubnetGroup, error) {
sg := &PrivateSubnetGroup{}
err := ctx.RegisterComponentResource(
"platform:network:PrivateSubnetGroup", name, sg, opts...)
if err != nil {
return nil, err
}
subnetA, err := ec2.NewSubnet(ctx, name+"-a", &ec2.SubnetArgs{
VpcId: vpcId,
CidrBlock: pulumi.String(cidrA),
AvailabilityZone: pulumi.String(azA),
Tags: pulumi.StringMap{"Name": pulumi.String(name + "-a")},
}, pulumi.Parent(sg))
if err != nil {
return nil, err
}
subnetB, err := ec2.NewSubnet(ctx, name+"-b", &ec2.SubnetArgs{
VpcId: vpcId,
CidrBlock: pulumi.String(cidrB),
AvailabilityZone: pulumi.String(azB),
Tags: pulumi.StringMap{"Name": pulumi.String(name + "-b")},
}, pulumi.Parent(sg))
if err != nil {
return nil, err
}
sg.SubnetAId = subnetA.ID()
sg.SubnetBId = subnetB.ID()
ctx.RegisterResourceOutputs(sg, pulumi.Map{
"subnetAId": subnetA.ID(),
"subnetBId": subnetB.ID(),
})
return sg, nil
}
package infra_test
import (
"testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/stretchr/testify/assert"
)
func TestNetworkingPhase(t *testing.T) {
err := pulumi.RunErr(func(ctx *pulumi.Context) error {
infra, err := buildNetworking(ctx) // your program function
if err != nil {
return err
}
pulumi.All(infra.vpc.CidrBlock, infra.vpc.EnableDnsHostnames).
ApplyT(func(vals []interface{}) error {
cidr := vals[0].(string)
dns := vals[1].(bool)
assert.Equal(t, "10.0.0.0/16", cidr)
assert.True(t, dns, "DNS hostnames must be enabled")
return nil
})
return nil
}, pulumi.WithMocks("myproject", "dev", mocks{}))
assert.NoError(t, err)
}
type mocks struct{}
func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
return args.Name + "_id", args.Inputs, nil
}
func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
return args.Args, nil
}
A production IaC repository is the canonical example of this architecture. Its 13-phase pipeline provisions a full production environment: networking and DNS in phases 1–2, IAM roles and policies in phase 3, S3 buckets and replication rules in phase 4, Aurora PostgreSQL clusters in phase 5, EC2 auto-scaling groups in phase 6, an EKS cluster and managed node groups in phases 7–8, a Prometheus/Grafana/Loki observability stack deployed via the Pulumi Kubernetes provider in phase 9, an NGINX ingress controller and ACM certificates in phase 10, Secrets Manager entries with rotation lambdas in phase 11, and application-layer Kubernetes namespaces and IRSA service accounts in phases 12–13. Each phase is an independent Go module with its own go.mod, enabling separate versioning and dependency updates.
An evaluation harness project can use a subset of this pipeline—phases 1, 3, 4, and 12—to provision ephemeral evaluation environments. When a new LLM readiness benchmark run is triggered, the Automation API spins up a fresh dev stack in under four minutes, runs the evaluation harness against it, exports the results, and then destroys the stack to avoid idle cloud spend. This ephemeral-stack pattern is only practical because Pulumi programs are real code: parameterized over stack name and environment, the same program provisions both the permanent staging environment and the transient benchmark environments without duplication.
Both of these use cases demonstrate a key operational benefit of Pulumi’s Go SDK: infrastructure refactoring follows the same patterns as application refactoring. When the team extracted the PrivateSubnetGroup component to eliminate duplicated subnet declarations across three phases, the change was a standard Go refactor—move struct to package, update call sites, run go build—rather than a templating DSL transformation. The Pulumi engine detected that the resource URNs changed (due to re-parenting under the component) and performed a targeted resource import to avoid unnecessary re-creation.
pulumi.Output[T] values are not plain Go values. Trying to use them in string interpolation or as map keys without ApplyT will produce the literal string Output<string> at runtime, not the actual resource attribute. Always thread Outputs through ApplyT, pulumi.Sprintf, or pulumi.All.aws CLI) will appear as drift on the next pulumi preview. In production, enforce SCPs or IAM policies that prevent direct console modification of Pulumi-managed resources.up. Version exported output names carefully and deprecate rather than rename when possible.pulumi.Parent in component resources — Omitting pulumi.Parent(sg) when creating child resources inside a component resource causes those children to appear as top-level resources in the state tree, breaking logical grouping, URN namespacing, and cascading deletes.pulumi config set mykey secret-value stores the value in cleartext in Pulumi.stackname.yaml, which ends up in source control. Always use pulumi config set --secret for credentials so the value is envelope-encrypted by the stack’s data key before being written to the config file.