← Back to Patterns

What goes in Pulumi stack config and what doesn't

We use Pulumi stack config for environment-specific values, not as a hiding place for infrastructure logic.

By Ivan Richter LinkedIn

Last updated: Sep 1, 2026

4 min read

On this page

Pulumi stack config should supply values to a program whose behavior is already legible in code.

If understanding what production creates requires interpreting dozens of config switches, the YAML file has become an untyped second program. Pulumi can execute that design perfectly. That doesn’t make it a good design.

Put environment values in config

Good stack values vary within one known infrastructure shape:

  • project, account, subscription, and region identifiers
  • domains and DNS zones
  • CIDR ranges
  • machine sizes and counts
  • retention periods
  • concurrency or scaling limits
  • budgets and labels
  • references to externally owned resources
  • feature versions when version is genuinely an input
config:
  gcp:project: anriku-public-prd
  platform:region: europe-west1
  platform:domain: anriku.com
  platform:databaseTier: db-custom-1-3840
  platform:backupRetentionDays: 30
  platform:maxInstances: 10

A reader can compare stacks and understand the values that differ without simulating topology.

Use typed accessors and validation. Required production values should fail closed rather than inherit a convenient development default.

Keep behavior in code

Code should show which resources exist, how they relate, and which policies apply.

If production creates a replica, uses deletion protection, or connects to an external integration, make that decision visible in the environment entry point or a named policy function. Avoid config keys such as:

platform:enableSeriousProductionMode: true
platform:useAlternateNetworkShape: true
platform:createExtraSecurityThings: true

The names are absurd, but only slightly more honest than the flags they parody.

A Boolean can be valid when it represents one bounded capability with a clear lifecycle. It becomes suspicious when toggling it changes several unrelated resources or compensates for a component that never found a stable contract.

Prefer an explicit policy object or separate entry point when environments have materially different shapes.

Put shared defaults in code

A value identical across stacks is usually a default or invariant, not stack configuration.

Define common labels, naming rules, log retention, baseline IAM, supported regions, and standard resource settings in reviewed code. Override them only when a stack has a real reason. Copying the same value through every YAML file creates manual drift and makes an invariant look optional.

The default should remain visible and domain-specific. Hiding every provider option in a generic config layer merely recreates the provider schema with worse documentation.

Secrets follow ownership, not encryption capability

Pulumi secret config encrypts values in stack state and configuration. Use it when the secret is a deployment input owned by that stack.

Prefer provider-native secret systems when the secret has its own rotation, audit, access, or multi-consumer lifecycle. Pulumi often needs only the secret resource ID and permission binding, not the payload.

config:
  platform:paymentSecretId: projects/example/secrets/payment-api-key

The application reads the secret at runtime. Pulumi grants access. The payload doesn’t need to pass through deployment outputs or state.

Don’t place credentials in plain config because the repository is private. Private repositories have an impressive history of becoming less private at inconvenient times.

Avoid derived values in config

Resource names, URLs, service-account emails, and connection strings derived from other inputs should usually be computed in code or exported from the resource that owns them.

Duplicating a derived value in config lets it disagree with reality. Cross-stack references or explicit provider lookups are preferable when another state owns the resource.

Keep cross-stack dependencies narrow. A shared network stack can export subnet IDs. It shouldn’t expose a sprawling object that couples consumers to its internal implementation.

Version config contracts

Renaming a key, changing units, or altering allowed values is an interface change. Validate it and migrate stacks deliberately.

Use domain namespaces, descriptive names, and units:

platform:requestTimeoutSeconds
platform:backupRetentionDays
platform:monthlyBudgetEur

Avoid ambiguous keys such as size, timeout, and mode. They save characters and spend review time indefinitely.

CI should load and validate every production stack. Unused keys should fail linting or be removed. Dead config is dangerous because readers assume it still controls something.

Don’t let stack names become hidden config

Using the stack name for naming is reasonable. Using it deep inside components to infer protection, networking, replicas, and access policy isn’t.

Pass consequential values from the environment boundary. This keeps a preview understandable and permits a production-like temporary stack without tricking components through its name.

Keep responsibilities separate

Stack config answers which value this stack uses. Code answers what the platform does with that value. Shared modules own stable mechanics. Secret systems own credentials with independent lifecycle.

Move a setting into config when operators need to vary it without changing infrastructure behavior. Move it back into code when the value has become policy or when several flags jointly define another topology.

Stack config is most useful when it’s boring enough to compare at a glance. Once it becomes more expressive than the program, reviewers can no longer see the behavior the program was supposed to own.

More in this domain: Infrastructure

Browse all

Related patterns