1. Docs
  2. Secrets & Configuration
  3. Guides
  4. Integrate with Pulumi IaC

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.

    This guide is for existing Pulumi IaC users. If you’re new to Pulumi IaC, start with the Pulumi IaC Get Started guide first.

    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

      Neo just got smarter about infrastructure policy automation