Integrate ESC with Pulumi IaC
This guide shows you how to integrate Pulumi ESC with your Pulumi IaC projects to centralize configuration and secrets across all your stacks.
If you completed the ESC Get Started guide, you already have an environment with values like region and apiKey. This guide shows you how to reference that same environment from your Pulumi stack configuration.
Prerequisites
- Pulumi CLI installed
- Pulumi account created
- An existing Pulumi project (or create one with
pulumi new) - An ESC environment with configuration values (see Get Started to create one)
Add ESC to your Pulumi stack
Step 1: Reference your ESC environment
In your stack configuration file (Pulumi.<stack-name>.yaml), add an environment block that references your ESC environment:
environment:
- <your-org>/<your-environment-name>
For example, if your ESC environment is my-project/dev:
environment:
- my-project/dev
You can also reference multiple environments, which will be merged in order (later values override earlier ones):
environment:
- my-project/common
- my-project/dev
Step 2: Define configuration in your ESC environment
ESC environments are YAML documents that you can edit using the CLI or Pulumi Cloud console. Use the CLI to edit your environment:
esc env edit <your-org>/<your-environment-name>
You can also edit environments in the Pulumi Cloud console if you prefer a visual editor.
In your ESC environment, use the pulumiConfig block to expose values to Pulumi IaC. If you’re using the environment from the Get Started guide, wrap your existing values in a pulumiConfig block:
values:
pulumiConfig:
region: us-west-2
apiKey:
fn::secret: demo-secret-123
Values in pulumiConfig can be strings, numbers, booleans, or secrets (using fn::secret).
The pulumiConfig block is the bridge between ESC and Pulumi IaC. Any values you define under pulumiConfig become available in your Pulumi program through the standard Configuration API.
This allows you to centralize all your configuration and secrets in ESC while accessing them through familiar Pulumi config patterns like config.get() or config.require().
Step 3: Access configuration in your code
Use Pulumi’s standard Configuration API to access these values in your infrastructure code:
"use strict";
const pulumi = require("@pulumi/pulumi");
// Create a new Pulumi Config
const config = new pulumi.Config();
// Retrieve the values of "region" and "apiKey"
const region = config.get("region");
const apiKey = config.getSecret("apiKey");
// Export values as stack outputs
module.exports = {
Region: region,
ApiKey: apiKey,
};
import * as pulumi from "@pulumi/pulumi";
// Create a new Pulumi Config
const config = new pulumi.Config();
// Retrieve the values of "region" and "apiKey"
const region = config.get("region");
const apiKey = config.getSecret("apiKey");
// Export values as stack output
export const Region = region;
export const ApiKey = apiKey;
import pulumi
# Import the configuration values
config = pulumi.Config()
# Retrieve the values of "region" and "apiKey"
region = config.get("region")
api_key = config.get_secret("apiKey")
# Export the values as an output
pulumi.export('Region', region)
pulumi.export("ApiKey", api_key)package main
import (
"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 {
// Create a Pulumi Config
config := config.New(ctx, "")
// Retrieve the value of "region" and "apiKey"
region := config.Get("region")
apiKey := config.GetSecret("apiKey")
// Export values as outputs
ctx.Export("Region", pulumi.String(region))
ctx.Export("ApiKey", pulumi.StringOutput(apiKey))
return nil
})
}
using Pulumi;
using System.Threading.Tasks;
using System.Collections.Generic;
class Program
{
static async Task<int> Main(string[] args)
{
return await Deployment.RunAsync(() =>
{
// Import the configuration values
var config = new Config();
// Retrieve the value of "region" and "apiKey"
var region = config.Get("region");
var apiKey = config.GetSecret("apiKey");
// Return a dictionary of outputs
return new Dictionary<string, object?>
{
["Region"] = region,
["ApiKey"] = apiKey
};
});
}
}
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
// Create a Pulumi Config
var config = ctx.config();
// Retrieve the values of "region" and "apiKey"
var region = config.get("region");
var apiKey = config.getSecret("apiKey");
// Export the values as a stack outputs
ctx.export("Region", Output.of(region));
ctx.export("ApiKey", Output.of(apiKey));
});
}
}Your Pulumi program now retrieves configuration and secrets from ESC. Run pulumi preview or pulumi up to see it in action.
Common patterns
Using dynamic cloud credentials
To share AWS OIDC credentials across multiple stacks, configure your ESC environment to generate short-lived credentials:
values:
aws:
login:
fn::open::aws-login:
oidc:
roleArn: arn:aws:iam::123456789012:role/pulumi-deployment-role
sessionName: pulumi-session
pulumiConfig:
aws:region: ${aws.login.region}
environmentVariables:
AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey}
AWS_SESSION_TOKEN: ${aws.login.sessionToken}
This pattern works everywhere Pulumi runs: locally, in CI/CD, Pulumi Deployments, and GitHub Actions. Similar patterns are available for Azure (fn::open::azure-login) and GCP (fn::open::gcp-login).
Learn more in Dynamic login credentials and Configuring OIDC.
Managing API keys and secrets
Pull third-party API keys from external secret stores:
values:
pulumiConfig:
myApp:datadogApiKey:
fn::secret:
fn::open::azure-secrets:
login: ${azure.login}
get:
secretId: https://my-keyvault.vault.azure.net/secrets/datadog-api-key
Learn more in Dynamic secrets.
Environment-specific configuration
Compose environments to share common configuration while overriding values per environment:
# common environment
values:
pulumiConfig:
myApp:instanceType: t3.micro
myApp:replicas: 1
# production environment (imports common)
imports:
- common
values:
pulumiConfig:
myApp:instanceType: t3.large # override for production
myApp:replicas: 3 # override for production
Learn more in Importing environments.
Next steps
- Dynamic login credentials - Generate dynamic cloud credentials with OIDC
- Dynamic secrets - Pull from AWS, Azure, GCP secret stores
- Importing environments - Compose configuration hierarchies
- Pulumi IaC integration reference - Complete integration documentation
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.
