Skip to main content
  1. Docs
  2. Infrastructure as Code
  3. Guides
  4. Building & Extending
  5. Automation API

Using Automation API

    The Pulumi Automation API lets you provision infrastructure programmatically by driving the Pulumi engine from your own code, exposing Pulumi programs and stacks as strongly typed, composable building blocks.

    In this guide, you’ll deploy an inline Pulumi program that creates a static website with Automation API. For background on workspaces, stacks, and inline versus local programs, see Automation API concepts.

    Prerequisites

    Before you begin, make sure you have:

    • The Pulumi CLI installed and available on your PATH. Automation API drives the CLI under the hood. Or, your program can install the CLI programmatically at runtime.
    • The runtime for your chosen language installed: Node.js, Python, Go, .NET, or Java.
    • A Pulumi access token so your program can store state in Pulumi Cloud. Run pulumi login to authenticate, or set the PULUMI_ACCESS_TOKEN environment variable.
    • AWS credentials configured, since this guide deploys resources to AWS.

    Install the CLI programmatically

    Because Automation API runs the Pulumi CLI for you, the CLI must be available at runtime. Rather than requiring everyone who runs your program to install it themselves, you can have the program download and manage its own copy. The TypeScript, Python, Go, and .NET SDKs expose an install method that downloads a specific CLI version—by default, the version matching the SDK—into ~/.pulumi/versions/<version>. You then pass the resulting command object to the workspace that runs your stack.

    import { LocalWorkspace, PulumiCommand } from "@pulumi/pulumi/automation";
    
    // Install the CLI version matching the SDK into ~/.pulumi/versions/<version>.
    const pulumiCommand = await PulumiCommand.install();
    

    Pass it to the workspace through the pulumiCommand option when you create the stack:

    const stack = await LocalWorkspace.createOrSelectStack(args, { pulumiCommand });
    
    from pulumi import automation as auto
    
    # Install the CLI version matching the SDK into ~/.pulumi/versions/<version>.
    pulumi_command = auto.PulumiCommand.install()
    

    Pass it to the workspace through the pulumi_command option when you create the stack:

    stack = auto.create_or_select_stack(stack_name=stack_name,
                                        project_name=project_name,
                                        program=pulumi_program,
                                        opts=auto.LocalWorkspaceOptions(pulumi_command=pulumi_command))
    
    import "github.com/pulumi/pulumi/sdk/v3/go/auto"
    
    ctx := context.Background()
    
    // Install the CLI version matching the SDK into ~/.pulumi/versions/<version>.
    pulumiCmd, err := auto.InstallPulumiCommand(ctx, &auto.PulumiCommandOptions{})
    if err != nil {
        fmt.Printf("Failed to install the Pulumi CLI: %v\n", err)
        os.Exit(1)
    }
    

    Pass it to the workspace with the auto.Pulumi option when you create the stack:

    s, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc, auto.Pulumi(pulumiCmd))
    
    using Pulumi.Automation;
    using Pulumi.Automation.Commands;
    
    // Install the CLI version matching the SDK into ~/.pulumi/versions/<version>.
    var pulumiCommand = await LocalPulumiCommand.Install();
    

    Pass it to the workspace through the PulumiCommand property when you create the stack:

    var stackArgs = new InlineProgramArgs(projectName, stackName, program)
    {
        PulumiCommand = pulumiCommand,
    };
    var stack = await LocalWorkspace.CreateOrSelectStackAsync(stackArgs);
    

    The Java SDK doesn’t support installing the Pulumi CLI programmatically. Install the Pulumi CLI manually and make sure it’s available on your PATH.

    Define your Pulumi program

    First, define the Pulumi program you want to run as a function within your overall program. Note how it looks like a standard Pulumi program.
    This tutorial is based on the inlineProgram-ts example, which is a complete example of how to construct a simple Automation API program.
    const pulumiProgram = async () => {
        // Create a bucket and expose a website index document.
        const siteBucket = new s3.Bucket("s3-website-bucket", {});
    
        const indexContent = `<html><head>
    <title>Hello S3</title><meta charset="UTF-8">
    </head>
    <body><p>Hello, world!</p><p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
    </body></html>
    `
    
        const ownershipControls = new aws.s3.BucketOwnershipControls("ownership-controls", {
            bucket: siteBucket.id,
            rule: {
                objectOwnership: "ObjectWriter",
            },
        });
    
        const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("public-access-block", {
            bucket: siteBucket.id,
            blockPublicAcls: false,
        });
    
        const website = new aws.s3.BucketWebsiteConfigurationV2("website", {
            bucket: siteBucket.id,
            indexDocument: {
                suffix: "index.html",
            },
        });
    
        // Write our index.html into the site bucket.
        const object = new s3.BucketObject("index", {
            bucket: siteBucket.id,
            content: indexContent,
            contentType: "text/html; charset=utf-8",
            key: "index.html",
            acl: "public-read"
        }, {
            dependsOn: [
                publicAccessBlock,
                ownershipControls,
                website,
            ],
        });
    
        return {
            websiteUrl: website.websiteEndpoint,
        };
    };
    
    This tutorial is based on the inline_program example, which is a complete example of how to construct a simple Automation API program.
    def pulumi_program():
        # Create a bucket and expose a website index document.
        site_bucket = s3.Bucket("s3-website-bucket")
    
        index_content = """
        <html>
            <head><title>Hello S3</title><meta charset="UTF-8"></head>
            <body>
                <p>Hello, world!</p>
                <p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
            </body>
        </html>
        """
    
        ownership_controls = s3.BucketOwnershipControls("ownership-controls",
            bucket=site_bucket.id,
            rule={
                "object_ownership": "ObjectWriter",
            })
    
        public_access_block = s3.BucketPublicAccessBlock("public-access-block",
            bucket=site_bucket.id,
            block_public_acls=False)
    
        website = aws.s3.BucketWebsiteConfigurationV2("website",
            bucket=site_bucket.id,
            index_document={
                "suffix": "index.html",
            })
    
        # Write our index.html into the site bucket.
        s3.BucketObject("index",
                        bucket=site_bucket.id,  # Reference to the s3.Bucket object.
                        content=index_content,
                        acl="public-read",
                        key="index.html",  # Set the key of the object.
                        content_type="text/html; charset=utf-8", # Set the MIME type of the file.
                        opts=pulumi.ResourceOptions(depends_on=[
                            public_access_block,
                            ownership_controls,
                            website,
                        ]))
    
        # Export the website URL.
        pulumi.export("website_url", website.website_endpoint)
    
    This tutorial is based on the inline_program example, which is a complete example of how to construct a simple Automation API program.
    deployFunc := func(ctx *pulumi.Context) error {
        // Similar go git_repo_program, our program defines a s3 website.
        // Here we create the bucket.
        siteBucket, err := s3.NewBucket(ctx, "s3-website-bucket", nil)
        if err != nil {
            return err
        }
    
        // We define and upload our HTML inline.
        indexContent := `<html><head>
    <title>Hello S3</title><meta charset="UTF-8">
    </head>
    <body><p>Hello, world!</p><p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
    </body></html>
    `
    
        ownershipControls, err := s3.NewBucketOwnershipControls(ctx, "ownership-controls", &s3.BucketOwnershipControlsArgs{
            Bucket: siteBucket.ID(),
            Rule: &s3.BucketOwnershipControlsRuleArgs{
                ObjectOwnership: pulumi.String("ObjectWriter"),
            },
        })
        if err != nil {
            return err
        }
    
        publicAccessBlock, err := s3.NewBucketPublicAccessBlock(ctx, "public-access-block", &s3.BucketPublicAccessBlockArgs{
            Bucket:          siteBucket.ID(),
            BlockPublicAcls: pulumi.Bool(false),
        })
        if err != nil {
            return err
        }
    
        website, err := s3.NewBucketWebsiteConfigurationV2(ctx, "website", &s3.BucketWebsiteConfigurationV2Args{
            Bucket: siteBucket.ID(),
            IndexDocument: &s3.BucketWebsiteConfigurationV2IndexDocumentArgs{
                Suffix: pulumi.String("index.html"),
            },
        })
        if err != nil {
            return err
        }
        // Upload our index.html.
        if _, err := s3.NewBucketObject(ctx, "index", &s3.BucketObjectArgs{
            Bucket:      siteBucket.ID(), // Reference to the s3.Bucket object.
            Content:     pulumi.String(indexContent),
            Acl:         pulumi.String("public-read"),
            Key:         pulumi.String("index.html"),               // Set the key of the object.
            ContentType: pulumi.String("text/html; charset=utf-8"), // Set the MIME type of the file.
        }, pulumi.DependsOn([]pulumi.Resource{
            publicAccessBlock,
            ownershipControls,
            website,
        })); err != nil {
            return err
        }
    
        // Export the website URL.
        ctx.Export("websiteUrl", website.WebsiteEndpoint)
        return nil
    }
    
    This tutorial is based on the InlineProgram example, which is a complete example of how to construct a simple Automation API program.
    var program = PulumiFn.Create(() =>
    {
        // Create a bucket and expose a website index document.
        var siteBucket = new Pulumi.Aws.S3.Bucket("s3-website-bucket");
    
        const string indexContent = @"
    <html>
        <head><title>Hello S3</title><meta charset=""UTF-8""></head>
        <body>
            <p>Hello, world!</p>
            <p>Made with ❤️ with <a href=""https://pulumi.com"">Pulumi</a></p>
        </body>
    </html>";
    
        var ownershipControls = new Aws.S3.BucketOwnershipControls("ownership-controls", new()
        {
            Bucket = siteBucket.Id,
            Rule = new Aws.S3.Inputs.BucketOwnershipControlsRuleArgs
            {
                ObjectOwnership = "ObjectWriter",
            },
        });
    
        var publicAccessBlock = new Aws.S3.BucketPublicAccessBlock("public-access-block", new()
        {
            Bucket = siteBucket.Id,
            BlockPublicAcls = false,
        });
    
        var website = new Aws.S3.BucketWebsiteConfigurationV2("website", new()
        {
            Bucket = siteBucket.Id,
            IndexDocument = new Aws.S3.Inputs.BucketWebsiteConfigurationV2IndexDocumentArgs
            {
                Suffix = "index.html",
            },
        });
    
        // Write our index.html into the site bucket.
        var @object = new Pulumi.Aws.S3.BucketObject("index", new Pulumi.Aws.S3.BucketObjectArgs
        {
            Bucket = siteBucket.BucketName, // Reference to the s3 bucket object.
            Content = indexContent,
            Acl = "public-read",
            Key = "index.html", // Set the key of the object.
            ContentType = "text/html; charset=utf-8", // Set the MIME type of the file.
        }, new CustomResourceOptions
        {
            DependsOn =
            {
                publicAccessBlock,
                ownershipControls,
                website,
            },
        });
    
        // Export the website url.
        return new Dictionary<string, object?>
        {
            ["website_url"] = website.WebsiteEndpoint
        };
    });
    
    This tutorial is based on the InlineProgram example, which is a complete example of how to construct a simple Automation API program.
    private static void pulumiProgram(Context ctx) {
    
        // Create an AWS resource (S3 Bucket)
        var siteBucket = new Bucket("s3-website-bucket");
    
        var website = new BucketWebsiteConfigurationV2("website", BucketWebsiteConfigurationV2Args.builder()
                .bucket(siteBucket.id())
                .indexDocument(BucketWebsiteConfigurationV2IndexDocumentArgs.builder()
                        .suffix("index.html")
                        .build())
                .build());
    
        var ownershipControls = new BucketOwnershipControls("ownershipControls", BucketOwnershipControlsArgs.builder()
                .bucket(siteBucket.id())
                .rule(BucketOwnershipControlsRuleArgs.builder()
                        .objectOwnership("ObjectWriter")
                        .build())
                .build());
    
        var publicAccessBlock = new BucketPublicAccessBlock("publicAccessBlock", BucketPublicAccessBlockArgs.builder()
                .bucket(siteBucket.id())
                .blockPublicAcls(false)
                .build());
    
        String indexContent = """
                <html>
                    <head><title>Hello S3</title><meta charset="UTF-8"></head>
                    <body>
                        <p>Hello, world!</p>
                        <p>Made with ❤️ with <a href="https://pulumi.com">Pulumi</a></p>
                    </body>
                </html>
                """;
    
        var indexHtml = new BucketObject("index.html", BucketObjectArgs.builder()
                .bucket(siteBucket.id())
                .content(indexContent)
                .contentType("text/html")
                .acl("public-read")
                .build(),
                CustomResourceOptions.builder()
                        .dependsOn(
                                publicAccessBlock,
                                ownershipControls,
                                website)
                        .build());
    
        // Export the name of the bucket
        ctx.export("website_url",
                website.websiteEndpoint().applyValue(websiteEndpoint -> String.format("http://%s", websiteEndpoint)));
    }
    
    The program’s lifecycle must be fully contained within the function, callback, or closure passed as the inline program. It’s unsafe to perform actions outside the scope of the inline program function. Doing so can lead to unpredictable behavior.

    Associate with a stack

    As with executing Pulumi programs through the CLI, you need to associate your Pulumi program with a Stack. Automation API provides methods to create or select stacks.

    Here’s a convenient method to select an existing Stack or create one if none exists:

    const args: InlineProgramArgs = {
        stackName: "dev",
        projectName: "inlineNode",
        program: pulumiProgram
    };
    
    const stack = await LocalWorkspace.createOrSelectStack(args);
    
    project_name = "inline_s3_project"
    stack_name = "dev"
    
    stack = auto.create_or_select_stack(stack_name=stack_name,
                                        project_name=project_name,
                                        program=pulumi_program)
    
    projectName := "inlineS3Project"
    stackName := "dev"
    s, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc)
    
    var projectName = "inline_s3_project";
    var stackName = "dev";
    
    var stackArgs = new InlineProgramArgs(projectName, stackName, program);
    var stack = await LocalWorkspace.CreateOrSelectStackAsync(stackArgs);
    
    var projectName = "inline_s3_project_java";
    var stackName = "dev";
    var stack = LocalWorkspace.createOrSelectStack(projectName, stackName, App::pulumiProgram);
    

    A Stack object operates within the context of a Workspace. A Workspace is the execution context containing a single Pulumi project, a program, and multiple stacks. Workspaces are used to manage the execution environment, providing various utilities such as plugin installation, environment configuration ($PULUMI_HOME), and creation, deletion, and listing of stacks. Because you are deploying AWS resources in this tutorial, you must install the AWS provider plugin within your Workspace so that your Pulumi program will have it available during execution.

    Configure your provider plugins

    The AWS plugin also needs configuration. You can provide that configuration just as you would with other Pulumi programs: either through stack configuration or environment variables. In this tutorial, you’ll use the Stack object to set the AWS region for the AWS provider plugin.

    await stack.workspace.installPlugin("aws", "v4.0.0");
    await stack.setConfig("aws:region", { value: "us-west-2" });
    
    stack.workspace.install_plugin("aws", "v4.0.0")
    stack.set_config("aws:region", auto.ConfigValue(value="us-west-2"))
    
    err = w.InstallPlugin(ctx, "aws", "v4.0.0")
    if err != nil {
      fmt.Printf("Failed to install program plugins: %v\n", err)
      os.Exit(1)
    }
    
    s.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: "us-west-2"})
    
    await stack.Workspace.InstallPluginAsync("aws", "v4.0.0");
    await stack.SetConfigAsync("aws:region", new ConfigValue("us-west-2"));
    
    stack.getWorkspace().installPlugin("aws", "v5.41.0");
    stack.setConfig("aws:region", new ConfigValue("us-west-2"));
    

    Using local packages with Automation API

    When working with Automation API, you may need to use local packages that aren’t published to the Pulumi Registry. This is common when using parameterized providers like terraform-provider or when developing custom providers.

    Generating and installing local packages

    To use a local package with Automation API, you need to:

    1. Generate the SDK for your target language using pulumi package add
    2. Manually install the plugin (not the provider) in your workspace
    3. Reference the generated SDK in your Automation API program
    For local packages, you must install the plugin, not the provider. The plugin is what the Pulumi engine uses to communicate with resources during deployments.

    Example: Using a local Terraform provider

    Here’s how to use a locally-generated Terraform provider SDK with Automation API:

    First, generate the SDK:

    pulumi package add terraform-provider@1.0.2 hashicorp/random 3.5.1
    

    Then, in your Automation API program, install the plugin in the workspace:

    import * as automation from "@pulumi/pulumi/automation";
    import * as random from "@pulumi/random";
    
    const stack = await automation.LocalWorkspace.createOrSelectStack({
        stackName: "dev",
        projectName: "myProject",
        program: async () => {
            const pet = new random.Pet("my-pet", { length: 2 });
            return { petName: pet.id };
        }
    });
    
    // Install the plugin for the local package
    await stack.workspace.installPlugin("terraform-provider", "v1.0.2");
    
    // Configure any required settings
    await stack.setConfig("aws:region", { value: "us-west-2" });
    
    // Run the deployment
    const result = await stack.up({ onOutput: console.info });
    

    First, generate the SDK:

    pulumi package add terraform-provider@1.0.2 hashicorp/random 3.5.1
    

    Then, in your Automation API program, install the plugin in the workspace:

    import pulumi
    import pulumi_random as random
    from pulumi import automation as auto
    
    def pulumi_program():
        pet = random.Pet("my-pet", length=2)
        pulumi.export("pet_name", pet.id)
    
    # Create or select a stack
    stack = auto.create_or_select_stack(
        stack_name="dev",
        project_name="myProject",
        program=pulumi_program
    )
    
    # Install the plugin for the local package
    stack.workspace.install_plugin("terraform-provider", "v1.0.2")
    
    # Configure any required settings
    stack.set_config("aws:region", auto.ConfigValue(value="us-west-2"))
    
    # Run the deployment
    result = stack.up(on_output=print)
    

    First, generate the SDK:

    pulumi package add terraform-provider@1.0.2 hashicorp/random 3.5.1
    

    Then, in your Automation API program, install the plugin in the workspace:

    package main
    
    import (
    	"context"
    	"fmt"
    	"os"
    
    	"github.com/pulumi/pulumi/sdk/v3/go/auto"
    	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    	"github.com/pulumi/pulumi-random/sdk/v4/go/random"
    )
    
    func main() {
    	ctx := context.Background()
    
    	// Define the Pulumi program
    	program := func(ctx *pulumi.Context) error {
    		pet, err := random.NewPet(ctx, "my-pet", &random.PetArgs{
    			Length: pulumi.Float64(2),
    		})
    		if err != nil {
    			return err
    		}
    		ctx.Export("petName", pet.ID())
    		return nil
    	}
    
    	// Create or select a stack
    	s, err := auto.UpsertStackInlineSource(ctx, "dev", "myProject", program)
    	if err != nil {
    		fmt.Printf("Failed to create stack: %v\n", err)
    		os.Exit(1)
    	}
    
    	// Install the plugin for the local package
    	w := s.Workspace()
    	err = w.InstallPlugin(ctx, "terraform-provider", "v1.0.2")
    	if err != nil {
    		fmt.Printf("Failed to install plugin: %v\n", err)
    		os.Exit(1)
    	}
    
    	// Configure any required settings
    	s.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: "us-west-2"})
    
    	// Run the deployment
    	res, err := s.Up(ctx)
    	if err != nil {
    		fmt.Printf("Failed to update stack: %v\n", err)
    		os.Exit(1)
    	}
    
    	fmt.Printf("Deployment succeeded! Pet name: %v\n", res.Outputs["petName"].Value)
    }
    

    First, generate the SDK:

    pulumi package add terraform-provider@1.0.2 hashicorp/random 3.5.1
    

    Then, in your Automation API program, install the plugin in the workspace:

    using System;
    using System.Collections.Generic;
    using Pulumi.Automation;
    using Pulumi.Random;
    
    var program = PulumiFn.Create(() =>
    {
        var pet = new RandomPet("my-pet", new RandomPetArgs { Length = 2 });
        return new Dictionary<string, object?>
        {
            ["petName"] = pet.Id
        };
    });
    
    var projectName = "myProject";
    var stackName = "dev";
    
    // Create or select a stack
    var stackArgs = new InlineProgramArgs(projectName, stackName, program);
    var stack = await LocalWorkspace.CreateOrSelectStackAsync(stackArgs);
    
    // Install the plugin for the local package
    await stack.Workspace.InstallPluginAsync("terraform-provider", "v1.0.2");
    
    // Run the deployment
    var result = await stack.UpAsync(new UpOptions { OnStandardOutput = Console.WriteLine });
    

    First, generate the SDK:

    pulumi package add terraform-provider@1.0.2 hashicorp/random 3.5.1
    

    Then, in your Automation API program, install the plugin in the workspace:

    import com.pulumi.Context;
    import com.pulumi.Pulumi;
    import com.pulumi.automation.*;
    import com.pulumi.random.RandomPet;
    import com.pulumi.random.RandomPetArgs;
    
    public class App {
        public static void main(String[] args) {
            var projectName = "myProject";
            var stackName = "dev";
    
            // Define the Pulumi program
            var program = (Context ctx) -> {
                var pet = new RandomPet("my-pet", RandomPetArgs.builder()
                    .length(2)
                    .build());
                
                ctx.export("petName", pet.id());
            };
            
            try {
                // Create or select a stack
                var stack = LocalWorkspace.createOrSelectStack(LocalProgramArgs.builder()
                    .stackName(stackName)
                    .projectName(projectName)
                    .program(program)
                    .build());
                
                // Install the plugin for the local package
                stack.workspace().installPlugin("terraform-provider", "v1.0.2");
                
                // Run the deployment
                var result = stack.up(UpOptions.builder()
                    .onOutput(System.out::println)
                    .build());
                
                System.out.printf("Deployment succeeded! Pet name: %s%n", 
                    result.outputs().get("petName").value());
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }
    

    For more information about working with local packages, see the Local Packages guide.

    Invoke Pulumi commands against the stack

    You’re now ready to execute commands against the Stack, including update, preview, refresh, destroy, import, and export. If you want to update the stack, invoke the update method (up) against the Stack object:

    const upRes = await stack.up({ onOutput: console.info });
    
    up_res = stack.up(on_output=print)
    
    res, err := s.Up(ctx, stdoutStreamer)
    if err != nil {
      fmt.Printf("Failed to update stack: %v\n\n", err)
      os.Exit(1)
    }
    
    var result = await stack.UpAsync(new UpOptions { OnStandardOutput = Console.WriteLine });
    
    var result = stack.up(UpOptions.builder().onStandardOutput(System.out::println).build());
    

    Notice how you can choose to have a callback function for standard output. In addition, the command returns a result of the update, which you can programmatically use to drive decisions within your program. For example, the result includes the stack outputs as well as a summary of the changes. This means you could choose to take different actions if there were no resources updated. Conversely, you could use the stack outputs to drive another Pulumi program within the same Automation program.

    Next steps

    You’ve now seen how to define, configure, and deploy an inline program with Automation API. To go further:

    • Review the Automation API concepts to understand workspaces, stacks, and the difference between inline and local programs.
    • Explore the automation-api-examples repository for runnable examples in every supported language, covering patterns such as cross-language programs, database migrations, and exposing Pulumi over HTTP.