Skip to main content
Go’s appeal for infrastructure code is the same as its appeal for systems code: static typing that catches errors at compile time rather than runtime, fast compilation, a single static binary with no runtime dependencies, and explicit error handling that makes failure modes clear and auditable. Teams building platform tooling in Go — controllers, operators, CLIs — often find it natural to run their infrastructure in Go too. Set runtime = "go" in ubx.iac and ubx generates a main.go and go.mod — idiomatic Go code using the Pulumi Go SDK. The .iac source is still identical to tutorials 42 and 43. The generated Go is more verbose than the TypeScript or Python equivalents, but it’s also the most explicit: every type is declared, every error is handled, and the dependency graph is visible in the code structure.

What you’ll learn

  • What Go output ubx generates from a .iac stack
  • How Go’s explicit error handling appears in generated Pulumi programs
  • The three-runtime comparison: TypeScript, Python, Go

Why this matters

Go runtime produces a fully static binary — no Node.js, no Python runtime, no virtualenv. In containerised CI environments where you control the base image, a Go Pulumi program has the smallest footprint and the most predictable execution environment.

The source code

# Identical source to tutorials 42 and 43 — only ubx.iac changes.
unit "aws_s3_bucket_v2" "store" {
  bucket = "myapp-${input.env}-store"
}

What ubx generates

// Generated by ubx — do not edit
package main

import (
    "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
    "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.Get("env")
        if env == "" {
            env = "dev"
        }

        store, err := s3.NewBucketV2(ctx, "store", &s3.BucketV2Args{
            Bucket: pulumi.Sprintf("myapp-%s-store", env),
        })
        if err != nil {
            return err
        }

        ctx.Export("bucketName", store.Bucket)
        return nil
    })
}

All three runtimes — side by side

The same S3 bucket in TypeScript, Python, and Go:
// TypeScript
const store = new aws.s3.BucketV2("store", { bucket: `myapp-${env}-store` });
export const bucketName = store.bucket;
# Python
store = aws.s3.BucketV2("store", bucket=f"myapp-{env}-store")
pulumi.export("bucket_name", store.bucket)
// Go
store, err := s3.NewBucketV2(ctx, "store", &s3.BucketV2Args{
    Bucket: pulumi.Sprintf("myapp-%s-store", env),
})
if err != nil { return err }
ctx.Export("bucketName", store.Bucket)
The Go version is more verbose but completely explicit — every operation, every error, every export is visible. ubx generates idiomatic Go that Go developers will recognise.

Run it

ubx validate
ubx plan    # requires AWS credentials
ubx apply

What you learned

runtime = "go" generates main.go + go.mod — idiomatic Go with explicit error handling
The .iac source is identical across all three runtimes — only ubx.iac changes
Go produces more verbose but maximally explicit code — every type, every error, every export declared

Next steps

Configuration as code (ubx block)

Put all project config in a ubx block instead of ubx.yaml

TypeScript runtime

Compare with the TypeScript output