Pulumi’s stack configuration is designed to let you manage your infrastructure settings on a per-environment basis, so you can have different database passwords for dev and prod, or different instance sizes.
Here’s a Pulumi program that deploys a simple AWS S3 bucket.
import pulumi
import pulumi_aws as aws
# Get configuration values
config = pulumi.Config()
bucket_name_prefix = config.get("bucketNamePrefix") or "my-bucket"
acl = config.get_object("acl") or "private"
# Create an S3 bucket
bucket = aws.s3.Bucket(
"my-bucket",
bucket=f"{bucket_name_prefix}-{pulumi.get_stack()}",
acl=acl,
tags={
"Environment": pulumi.get_stack(),
}
)
# Export the bucket name
pulumi.export("bucket_name", bucket.id)
To run this, you’d first create a Pulumi project and then set up different stacks for your environments.
pulumi new aws-python # (if you don't have a project already)
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
Now, you can set configuration values specific to each stack. For the dev stack:
pulumi config set --stack dev bucketNamePrefix my-dev-bucket
pulumi config set --stack dev acl public-read
For the prod stack:
pulumi config set --stack prod bucketNamePrefix my-prod-bucket
# Keep the default ACL for prod (private)
When you run pulumi up for a specific stack, Pulumi automatically loads the configuration for that stack.
pulumi up --stack dev
# Output will show a bucket named something like "my-dev-bucket-dev" with public-read ACL.
pulumi up --stack prod
# Output will show a bucket named something like "my-prod-bucket-prod" with private ACL.
The core problem Pulumi stack configuration solves is the need to manage the same infrastructure code across different deployment targets (like development, staging, production, or even different regions) without duplicating code. Instead of if/else statements or separate configuration files for each environment, you use Pulumi’s built-in mechanism. Each pulumi stack init creates a new, isolated context for your configuration values. These values are stored in a .pulumi/ directory within your project, typically as JSON files (e.g., dev.json, prod.json). When you select a stack with pulumi stack select <stack-name>, Pulumi loads the corresponding configuration.
The pulumi.Config() object in your code is the entry point. config.get("key") retrieves a configuration value by its key. If the key is not found, it returns None. config.get_object("key") is used for values that might be complex types like lists or maps, or if you need to ensure a specific type. The or clause in the example (config.get("bucketNamePrefix") or "my-bucket") provides a sensible default if no configuration value is set for that key in the current stack. pulumi.get_stack() is a special Pulumi function that dynamically returns the name of the currently active stack, allowing you to embed stack-specific information directly into resource names or tags.
The pulumi config set command is how you populate these values. You can set them globally for the current stack or for a specific stack using the --stack flag. For sensitive values like passwords or API keys, you should use pulumi config set --secret <key> <value>. These secret values are encrypted by Pulumi using a local Pulumi secret provider (or an external one if configured) and stored securely. When retrieved via config.get_secret("key"), they are decrypted at runtime.
The pulumi.export statements make outputs from your deployment visible after pulumi up completes. For example, pulumi.export("bucket_name", bucket.id) will display the S3 bucket’s ID in the command-line output and can be retrieved using pulumi stack output bucket_name. This is crucial for referencing outputs from one stack as inputs to another, or for providing users with key information about their deployed infrastructure.
Behind the scenes, Pulumi stores stack configurations as JSON files within the .pulumi/stacks/ directory of your project. For instance, if you have a dev stack, there will be a dev.json file containing key-value pairs. When you run pulumi up --stack dev, Pulumi reads this dev.json file and makes its contents available through the pulumi.Config() object. This separation keeps your core infrastructure code clean and reusable, with environment-specific details managed externally.
It’s important to understand how Pulumi resolves configuration values when you don’t explicitly set them for a stack. If a configuration key is not set for the current stack, and no default is provided in the code, config.get() will return None. However, if you try to use None in a context where a string or other specific type is expected by the cloud provider’s SDK (like the S3 bucket name or ACL), the Pulumi engine will raise an error during the update phase, indicating a missing required property. This prevents deploying resources with incomplete or invalid configurations.
The next concept you’ll likely encounter is managing dependencies between different stacks, often referred to as "stack references."