Skip to main content
Pulumi logo Pulumi logo
  1. Docs
  2. Infrastructure as Code
  3. Concepts
  4. Stash

Stash

    The Stash resource is a built-in Pulumi resource that allows you to save values to your stack’s state for later retrieval. A stash takes a single input value and stores it in state, making it available as an output property. Stashes are commonly used to persist computed values, pass data between program executions, or save intermediate results that need to be accessed later.

    Every Stash resource accepts any value as its input property. It then exposes two output properties — one for the value it stashed, and one that echoes back the value you most recently passed in:

    • output — the value saved in state. This value is stateful: it persists the original input value even after the input property is changed in a later deployment.
    • input — an echo of the most recent value passed to the resource. Reference this output when you need the current value rather than the stashed one.
    The built-in Stash resource was added in Pulumi v3.208.0. Upgrade the Pulumi CLI if your version predates it.

    Create a stash

    To create a new stash, instantiate a Stash resource and provide a value for the input property. The stash stores this value in your stack’s state and makes it available through the output property.

    import * as pulumi from "@pulumi/pulumi";
    
    const myStash = new pulumi.Stash("myStash", {
        input: "Hello, World!",
    });
    
    export const stashedValue = myStash.output;
    
    import pulumi
    
    my_stash = pulumi.Stash("myStash", input="Hello, World!")
    
    pulumi.export("stashedValue", my_stash.output)
    
    package main
    
    import (
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            myStash, err := pulumi.NewStash(ctx, "myStash", &pulumi.StashArgs{
                Input: pulumi.String("Hello, World!"),
            })
            if err != nil {
                return err
            }
    
            ctx.Export("stashedValue", myStash.Output)
            return nil
        })
    }
    
    using Pulumi;
    
    return await Deployment.RunAsync(() =>
    {
        var myStash = new Stash("myStash", new StashArgs
        {
            Input = "Hello, World!",
        });
    
        return new Dictionary<string, object?>
        {
            ["stashedValue"] = myStash.Output,
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.core.Output;
    import com.pulumi.resources.Stash;
    import com.pulumi.resources.StashArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                var myStash = new Stash("myStash", StashArgs.builder()
                    .input("Hello, World!")
                    .build());
    
                ctx.export("stashedValue", myStash.output());
            });
        }
    }
    
    resources:
      myStash:
        type: pulumi:index:Stash
        properties:
          input: "Hello, World!"
    
    outputs:
      stashedValue: ${myStash.output}
    

    In Pulumi HCL, Terraform’s built-in terraform_data resource lowers onto this same built-in stash resource:

    resource "terraform_data" "my_stash" {
      input = "Hello, World!"
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    
    output "stashed_value" {
      value = terraform_data.my_stash.output
    }
    

    Without the lifecycle block, terraform_data keeps Terraform’s semantics: output mirrors input and follows it on every update. Adding ignore_changes for input pins the value the resource was created with, which gives you the stateful output described above.

    Because input is ignored, terraform_data.my_stash.input also returns the pinned value rather than the most recent one. No attribute of the resource echoes the current input expression.

    Like any other resource, a Stash needs a name that is unique across your program.

    Stashing complex values

    The input property of a Stash resource can accept any value, including complex objects, arrays, and nested structures. The value is serialized as a Pulumi property value when stored in state.

    const configStash = new pulumi.Stash("configStash", {
        input: {
            region: "us-west-2",
            instanceType: "t3.micro",
            tags: {
                Environment: "production",
                Team: "platform",
            },
        },
    });
    
    config_stash = pulumi.Stash("configStash", input={
        "region": "us-west-2",
        "instanceType": "t3.micro",
        "tags": {
            "Environment": "production",
            "Team": "platform",
        },
    })
    
    configStash, err := pulumi.NewStash(ctx, "configStash", &pulumi.StashArgs{
        Input: pulumi.Map{
            "region":       pulumi.String("us-west-2"),
            "instanceType": pulumi.String("t3.micro"),
            "tags": pulumi.Map{
                "Environment": pulumi.String("production"),
                "Team":        pulumi.String("platform"),
            },
        },
    })
    
    var configStash = new Stash("configStash", new StashArgs
    {
        Input = new Dictionary<string, object>
        {
            ["region"] = "us-west-2",
            ["instanceType"] = "t3.micro",
            ["tags"] = new Dictionary<string, object>
            {
                ["Environment"] = "production",
                ["Team"] = "platform",
            },
        },
    });
    
    var configStash = new Stash("configStash", StashArgs.builder()
        .input(Map.of(
            "region", "us-west-2",
            "instanceType", "t3.micro",
            "tags", Map.of(
                "Environment", "production",
                "Team", "platform"
            )
        ))
        .build());
    
    resources:
      configStash:
        type: pulumi:index:Stash
        properties:
          input:
            region: us-west-2
            instanceType: t3.micro
            tags:
              Environment: production
              Team: platform
    
    resource "terraform_data" "config_stash" {
      input = {
        region       = "us-west-2"
        instanceType = "t3.micro"
        tags = {
          Environment = "production"
          Team        = "platform"
        }
      }
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    

    Map keys are stored exactly as written — Pulumi HCL’s snake_case to camelCase conversion applies to provider schema properties, not to the keys of a map you define yourself.

    Stashing secret values

    The Stash resource respects secret annotations. If the input value is marked as a secret, the output is also secret, and the value is encrypted in your stack’s state.

    const apiKeyStash = new pulumi.Stash("apiKeyStash", {
        input: pulumi.secret("my-secret-api-key"),
    });
    
    // The output is also marked as secret
    export const apiKey = apiKeyStash.output;
    
    api_key_stash = pulumi.Stash("apiKeyStash",
        input=pulumi.Output.secret("my-secret-api-key"))
    
    # The output is also marked as secret
    pulumi.export("apiKey", api_key_stash.output)
    
    apiKeyStash, err := pulumi.NewStash(ctx, "apiKeyStash", &pulumi.StashArgs{
        Input: pulumi.ToSecret(pulumi.String("my-secret-api-key")),
    })
    if err != nil {
        return err
    }
    
    // The output is also marked as secret
    ctx.Export("apiKey", apiKeyStash.Output)
    
    var apiKeyStash = new Stash("apiKeyStash", new StashArgs
    {
        Input = Output.CreateSecret("my-secret-api-key"),
    });
    
    // The output is also marked as secret
    return new Dictionary<string, object?>
    {
        ["apiKey"] = apiKeyStash.Output,
    };
    
    var apiKeyStash = new Stash("apiKeyStash", StashArgs.builder()
        .input(Output.ofSecret("my-secret-api-key"))
        .build());
    
    // The output is also marked as secret
    ctx.export("apiKey", apiKeyStash.output());
    
    resources:
      apiKeyStash:
        type: pulumi:index:Stash
        properties:
          input:
            fn::secret: my-secret-api-key
    
    outputs:
      apiKey: ${apiKeyStash.output}
    
    resource "terraform_data" "api_key_stash" {
      input = sensitive("my-secret-api-key")
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    
    # The output is also marked as secret
    output "api_key" {
      value = terraform_data.api_key_stash.output
    }
    

    A variable declared with sensitive = true works the same way. Either source makes the stashed value a Pulumi secret, so the output block does not need its own sensitive = true to keep it encrypted.

    The CLI does not show the plaintext content of a stashed secret by default; it displays [secret] instead. Pass --show-secrets to the command to reveal the plaintext value.

    Updating stashed values

    To update the value stored in a Stash you need to replace it. Three ways to do that:

    1. Pass the --target-replace argument to pulumi up to tell the engine to replace the stash.
    2. Run pulumi state taint to mark the resource for replacement on the next deployment.
    3. Set the replacementTrigger resource option to replace the stash whenever a trigger value changes.

    Without a replacement, changes to the input property are reflected in the input output property, but the output property does not change. It continues to return the original value the Stash was constructed with.

    In Pulumi HCL, the replacement trigger is the triggers_replace argument of terraform_data. Because the HCL examples on this page ignore changes to input, both input and output keep returning the stashed value until the resource is replaced, and the replacement stores the current value of input.

    Deleting a stash

    To delete a Stash resource, remove it from your program and run pulumi up. Pulumi removes the stash — and the value it holds — from your stack’s state during the update.

    Common use cases

    The Stash resource is useful for keeping track of a computed value across deployments — for example, the first user to run the deployment, the time the stack was first created, or a generated random value that needs to stay stable.

    Capturing the first deployment user

    When you need to record who first deployed the infrastructure:

    import * as pulumi from "@pulumi/pulumi";
    import * as os from "os";
    
    const firstDeployer = new pulumi.Stash("firstDeployer", {
        input: os.userInfo().username,
    });
    
    // The output will always show the original deployer, even if others run updates later
    export const originalDeployer = firstDeployer.output;
    
    import pulumi
    import getpass
    
    first_deployer = pulumi.Stash("firstDeployer",
        input=getpass.getuser())
    
    # The output will always show the original deployer, even if others run updates later
    pulumi.export("originalDeployer", first_deployer.output)
    
    import (
        "os"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            firstDeployer, err := pulumi.NewStash(ctx, "firstDeployer", &pulumi.StashArgs{
                Input: pulumi.String(os.Getenv("USER")),
            })
            if err != nil {
                return err
            }
    
            // The output will always show the original deployer, even if others run updates later
            ctx.Export("originalDeployer", firstDeployer.Output)
            return nil
        })
    }
    
    using Pulumi;
    using System;
    
    return await Deployment.RunAsync(() =>
    {
        var firstDeployer = new Stash("firstDeployer", new StashArgs
        {
            Input = Environment.UserName,
        });
    
        // The output will always show the original deployer, even if others run updates later
        return new Dictionary<string, object?>
        {
            ["originalDeployer"] = firstDeployer.Output,
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.resources.Stash;
    import com.pulumi.resources.StashArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                var firstDeployer = new Stash("firstDeployer", StashArgs.builder()
                    .input(System.getProperty("user.name"))
                    .build());
    
                // The output will always show the original deployer, even if others run updates later
                ctx.export("originalDeployer", firstDeployer.output());
            });
        }
    }
    

    HCL has no function that reads the current user, so pass it into the program as a variable:

    variable "deployer" {
      type        = string
      description = "The user running the deployment."
    }
    
    resource "terraform_data" "first_deployer" {
      input = var.deployer
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    
    # The output will always show the original deployer, even if others run updates later
    output "original_deployer" {
      value = terraform_data.first_deployer.output
    }
    

    Supply the value on the command line with TF_VAR_deployer="$USER" pulumi up, or from stack configuration.

    Recording the initial creation time

    When you need to persist the timestamp of when the infrastructure was first created:

    import * as pulumi from "@pulumi/pulumi";
    
    const creationTime = new pulumi.Stash("creationTime", {
        input: new Date().toISOString(),
    });
    
    // This will always return the original creation time
    export const firstDeployed = creationTime.output;
    
    import pulumi
    from datetime import datetime
    
    creation_time = pulumi.Stash("creationTime",
        input=datetime.now().isoformat())
    
    # This will always return the original creation time
    pulumi.export("firstDeployed", creation_time.output)
    
    import (
        "time"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            creationTime, err := pulumi.NewStash(ctx, "creationTime", &pulumi.StashArgs{
                Input: pulumi.String(time.Now().Format(time.RFC3339)),
            })
            if err != nil {
                return err
            }
    
            // This will always return the original creation time
            ctx.Export("firstDeployed", creationTime.Output)
            return nil
        })
    }
    
    using Pulumi;
    using System;
    
    return await Deployment.RunAsync(() =>
    {
        var creationTime = new Stash("creationTime", new StashArgs
        {
            Input = DateTime.UtcNow.ToString("o"),
        });
    
        // This will always return the original creation time
        return new Dictionary<string, object?>
        {
            ["firstDeployed"] = creationTime.Output,
        };
    });
    
    package myproject;
    
    import java.time.Instant;
    
    import com.pulumi.Pulumi;
    import com.pulumi.resources.Stash;
    import com.pulumi.resources.StashArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                var creationTime = new Stash("creationTime", StashArgs.builder()
                    .input(Instant.now().toString())
                    .build());
    
                // This will always return the original creation time
                ctx.export("firstDeployed", creationTime.output());
            });
        }
    }
    
    resource "terraform_data" "creation_time" {
      input = timestamp()
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    
    # This will always return the original creation time
    output "first_deployed" {
      value = terraform_data.creation_time.output
    }
    

    timestamp() returns a new value on every run. Ignoring changes to input keeps the stash at the timestamp of the first deployment.

    Preserving a stable random value

    Use a stash when you need a random value that remains constant across deployments. In these examples, generatePassword is your own helper function that produces a fresh value on every run; the stash is what keeps the first value stable.

    import * as pulumi from "@pulumi/pulumi";
    
    // Generate a random password once
    const randomPassword = generatePassword();
    
    // Stash it so it doesn't change on subsequent deployments
    const passwordStash = new pulumi.Stash("passwordStash", {
        input: pulumi.secret(randomPassword),
    });
    
    // Use the stashed password for database configuration
    export const dbPassword = passwordStash.output;
    
    import pulumi
    
    # Generate a random password once
    random_password = generatePassword()
    
    # Stash it so it doesn't change on subsequent deployments
    password_stash = pulumi.Stash("passwordStash",
        input=pulumi.Output.secret(random_password))
    
    # Use the stashed password for database configuration
    pulumi.export("dbPassword", password_stash.output)
    
    import (
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            // Generate a random password once
            randomPassword := generatePassword()
    
            // Stash it so it doesn't change on subsequent deployments
            passwordStash, err := pulumi.NewStash(ctx, "passwordStash", &pulumi.StashArgs{
                Input: pulumi.ToSecret(randomPassword),
            })
            if err != nil {
                return err
            }
    
            // Use the stashed password for database configuration
            ctx.Export("dbPassword", passwordStash.Output)
            return nil
        })
    }
    
    using Pulumi;
    
    return await Deployment.RunAsync(() =>
    {
        // Generate a random password once
        var randomPassword = generatePassword();
    
        // Stash it so it doesn't change on subsequent deployments
        var passwordStash = new Stash("passwordStash", new StashArgs
        {
            Input = Output.CreateSecret(randomPassword),
        });
    
        // Use the stashed password for database configuration
        return new Dictionary<string, object?>
        {
            ["dbPassword"] = passwordStash.Output,
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.core.Output;
    import com.pulumi.resources.Stash;
    import com.pulumi.resources.StashArgs;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                // Generate a random password once
                var randomPassword = generatePassword();
    
                // Stash it so it doesn't change on subsequent deployments
                var passwordStash = new Stash("passwordStash", StashArgs.builder()
                    .input(Output.ofSecret(randomPassword))
                    .build());
    
                // Use the stashed password for database configuration
                ctx.export("dbPassword", passwordStash.output());
            });
        }
    }
    

    uuid() stands in for the generatePassword helper the other examples use: it also produces a fresh value on every run.

    resource "terraform_data" "password_stash" {
      input = sensitive(uuid())
    
      lifecycle {
        ignore_changes = [input]
      }
    }
    
    # Use the stashed password for database configuration
    output "db_password" {
      value = terraform_data.password_stash.output
    }
    

    To rotate the value later, add a triggers_replace argument and change it; that lowers onto replacementTrigger and replaces the stash, re-evaluating uuid().

    Next steps

      The infrastructure as code platform for any cloud.