Skip to main content
When ubx compiles your .iac files to TypeScript, Python, or Go, it generates a package.json, requirements.txt, or go.mod alongside the Pulumi program. Without provider version constraints, these generated dependency files use the latest available version — which changes over time, breaks reproducibility, and can introduce provider bugs silently when someone runs ubx apply weeks after the last successful apply. The provider block pins the version of a Pulumi provider in your .iac source. This version constraint is propagated into the generated dependency file for whatever runtime you’re using, ensuring every environment and every apply uses the same provider version unless you explicitly update it.

What you’ll learn

  • How provider blocks pin Pulumi provider versions in generated dependency files
  • The version constraint syntax (NPM semver)
  • How ubx schema sync uses the pinned version to cache the correct schema

Why this matters

Provider version pinning is the difference between reproducible infrastructure and infrastructure that silently starts failing when a provider releases a breaking change. Pin your providers in .iac source the same way you pin library versions in application code — it’s not optional in production.

The source code

unit "aws_s3_bucket_v2" "store" {
  bucket = "myapp-${input.env}-store"
}

How it works

1

The provider block is read during code generation

When ubx generates the runtime project (TypeScript, Python, or Go), it reads every provider block and translates the version constraint into the appropriate dependency format for the target runtime.
2

The version constraint is written to the dependency file

For TypeScript: "@pulumi/aws": "~6.0.0" in package.json. For Python: pulumi-aws~=6.0 in requirements.txt. For Go: the specific module version in go.mod. The constraint format is adapted to each ecosystem’s syntax.
3

ubx schema sync uses the pinned version

When you run ubx schema sync aws, ubx downloads the schema for the pinned version (~> 6.0 resolves to the latest 6.x release). The schema cache is version-aware — running ubx validate after changing the version constraint will trigger a re-sync if the new version’s schema isn’t cached.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const env = config.get("env") || "dev";

const store = new aws.s3.BucketV2("store", {
    bucket: `myapp-${env}-store`,
});

export const bucketName = store.bucket;

Common mistakes

If you omit the provider block entirely, ubx generates the dependency file with the latest available provider version. This is fine for getting started but not for production — the provider version will drift between applies as new releases are published. Always add a provider block before your first production apply.
Use ~> 6.0 (compatible release) rather than = 6.54.0 (exact pin) unless you have a specific reason to lock to an exact version. Compatible release allows patch updates (bug fixes) while preventing minor version changes that might include breaking changes.

Run it

ubx schema sync aws            # syncs schema for pinned version
ubx validate
ubx plan   # requires AWS credentials

What you learned

provider blocks pin Pulumi provider versions in generated package.json, requirements.txt, or go.mod
Version constraints use NPM semver syntax regardless of the target runtime
ubx schema sync uses the pinned version to cache the correct provider schema

Next steps

Third-party provider (Datadog)

Use a non-AWS provider with dynamic schema sync

provider block reference

Full provider block syntax