Most teams begin their Infrastructure as Code (IaC) journey by placing secrets directly into configuration files or environment variables with minimal control. But this common practice inevitably leads to significant security vulnerabilities, operational overhead, and compliance nightmares when scaled across multiple environments and projects.
TL;DR Box
Encrypt sensitive configuration data using Pulumi's built-in secrets management or integrate with a dedicated secret backend.
Adopt a clear, hierarchical stack structure (e.g., `org/project/environment/stack`) to manage infrastructure lifecycle effectively.
Leverage Pulumi stack references to securely share outputs and dependencies between stacks without duplicating secrets.
Implement robust IAM policies to control access to Pulumi state files and secret decryption keys.
Design your CI/CD pipelines to interact with encrypted secrets using least-privilege principles, preventing exposure.
The Problem: When IaC Secrets and Stacks Go Sideways
In 2026, relying on unencrypted secrets in IaC configurations remains a critical security lapse. I've observed countless teams struggle with this, often resorting to hardcoding API keys, database credentials, or sensitive network configurations directly into `Pulumi.dev.yaml` files. This approach might seem convenient for quick development, but it rapidly escalates into a major risk factor in production.
Consider a backend team managing 15 distinct microservices across `development`, `staging`, and `production` environments. If each service's Pulumi project has its own `Pulumi.
How It Works: Securing Your IaC with Pulumi Secrets
Pulumi provides first-class support for secrets, treating them as encrypted configuration values that are never stored in plain text within your state file. When you mark a configuration value as a secret, Pulumi encrypts it before storing it and decrypts it only when required during an update or preview operation. This mechanism fundamentally changes how you handle sensitive data in your infrastructure definitions.
Pulumi Secrets Encryption Mechanisms
Pulumi offers flexibility in how your secrets are encrypted:
Pulumi Service Backend (Default): For projects using the Pulumi Service for state management, secrets are encrypted using a service-managed key. This is often the simplest and most secure option for many teams, as Pulumi handles key rotation and access control for the encryption key itself.
Cloud-Specific Key Management Services (KMS): You can configure Pulumi to use your own keys in services like AWS KMS, Azure Key Vault, or Google Cloud Secret Manager. This provides maximum control over the encryption key lifecycle, which is crucial for organizations with stringent compliance requirements.
Passphrase-based Encryption: For local state files or specific offline scenarios, Pulumi can encrypt secrets using a passphrase you provide. While offering local control, this places the burden of passphrase management and security directly on your team, which can be challenging at scale.
When you interact with a secret, Pulumi ensures it's decrypted only within the Pulumi engine process, minimizing exposure. The encrypted value lives in your `Pulumi.
// project1/index.ts - Defining a resource with a secret
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
// Retrieve a secret value from configuration.
// This value will be encrypted in Pulumi.dev.yaml.
const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
// Create an RDS instance, using the secret for its master password.
// The password will be handled securely by Pulumi.
const db = new aws.rds.Instance("my-app-db-2026", {
engine: "postgresql",
engineVersion: "13.4",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
username: "admin",
password: dbPassword, // Using the decrypted secret here
skipFinalSnapshot: true, // For example purposes
});
// Export the database endpoint (but not the password itself!)
export const dbEndpoint = db.endpoint;The code above defines an AWS RDS instance and fetches its master password from the Pulumi configuration. By calling `config.requireSecret("dbPassword")`, we explicitly tell Pulumi to treat this value as sensitive, ensuring it's encrypted in the stack configuration file and only decrypted during `pulumi up`.
How It Works: Pulumi Stack Structure Best Practices
Effective stack structure goes beyond just environment separation; it's about defining clear ownership, managing blast radius, and enabling secure cross-stack communication. A well-organized structure reduces cognitive load and allows teams to scale their infrastructure without tangled dependencies.
Hierarchical Stack Organization
A recommended approach for multi-environment, multi-project organizations is a hierarchical structure: `organization/project/environment/stack`.
Organization: The top-level grouping, often mapping to your company or a major division.
Project: A logical unit of infrastructure, typically aligning with a service, application, or shared platform component (e.g., `vpc-network`, `api-gateway`, `data-processing`).
Environment: Represents a deployment stage, such as `dev`, `staging`, `production`, `qa`, etc.
Stack: The specific instance of infrastructure within an environment.
This structure allows for fine-grained control and clear separation. For instance, `myorg/platform/production/network` might define your production VPC, while `myorg/backend-service/staging/app-server` defines a specific application's servers in staging.
Securely Sharing Data with Stack References
The challenge with segregated stacks often involves sharing outputs, such as a VPC ID, subnet IDs, or a database endpoint, from one stack to another without re-provisioning or duplicating configuration. Pulumi's Stack References solve this elegantly and securely.
A stack reference allows a Pulumi program to retrieve the outputs of another stack programmatically. This is crucial when the referenced stack output contains sensitive information that should not be manually copied. When a stack output is itself a Pulumi `Secret`, the stack reference will also return it as a `Secret`, maintaining its encrypted state across stack boundaries.
// project2/index.ts - Referencing an output from project1
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
// Reference the stack that created the database (e.g., 'dev' stack of 'project1').
// Format: <org>/<project>/<stack-name>
const dbStackRef = new pulumi.StackReference("myorg/project1/dev");
// Retrieve the database endpoint from project1's dev stack.
// If dbEndpoint was exported as a Secret in project1, it remains a Secret here.
const referencedDbEndpoint = dbStackRef.getOutput("dbEndpoint");
// Example: Create an EC2 instance that needs to connect to the database.
// The database endpoint is securely passed via the stack reference.
const appServer = new aws.ec2.Instance("app-server-2026", {
ami: "ami-0abcdef1234567890", // Placeholder AMI ID for us-east-1 (2026)
instanceType: "t3.micro",
























Responses (0)