Using Buildkite with Pulumi
Buildkite is a CI/CD platform that runs pipelines on agents you host or on Buildkite-hosted agents. You run Pulumi in a Buildkite pipeline with the pulumi plugin, an official Buildkite plugin that installs and configures the Pulumi CLI for a step.
Because the plugin runs CLI commands, it works with a Pulumi program written in any supported language, and with any cloud provider Pulumi supports.
Prerequisites
Before you begin, make sure you have:
- A Pulumi Cloud account and organization.
- A Buildkite account with a cluster and agent.
- A Git repository connected to a Buildkite pipeline.
- A Pulumi program. If you don’t have one yet, follow a Get started guide.
Install and configure Pulumi
A Buildkite pipeline’s steps can be defined in the web interface or, more commonly, in a pipeline.yml file committed to your repository. The Buildkite agent looks for this file in a few locations; .buildkite/pipeline.yml in the repository root is the conventional choice.
Add the pulumi plugin to any step that runs a Pulumi command. The plugin installs the CLI before the step’s commands run:
# .buildkite/pipeline.yml
steps:
- label: ":pulumi: Preview"
commands:
- cd infra && npm install
- pulumi preview --stack acme/website/dev
plugins:
- pulumi#v1.0.0:
# Pin a specific CLI version. Optional; defaults to the latest release.
version: 3.143.0
As an alternative to the plugin, you can run your step inside one of Pulumi’s official container images, which ship the Pulumi CLI and a language runtime preinstalled. Use the docker plugin to select the image for your language:
plugins:
- docker#v5.9.0:
image: pulumi/pulumi-nodejs
mount-buildkite-agent: true
environment:
- PULUMI_ACCESS_TOKEN
Authenticate with Pulumi Cloud
When your pipeline uses Pulumi Cloud as its backend, it needs only a single Pulumi access token to operate.
Store the token as a Buildkite secret so it isn’t committed to source control, and use the secrets plugin to map it to the PULUMI_ACCESS_TOKEN environment variable:
plugins:
- secrets#v1.0.0:
variables:
PULUMI_ACCESS_TOKEN: PULUMI_ACCESS_TOKEN
- pulumi#v1.0.0: ~
Prefer an organization or team token over a personal token.
You can remove even that static secret with OpenID Connect (OIDC). Register Buildkite as an OIDC issuer in Pulumi Cloud, then enable OIDC on the plugin — it exchanges the OIDC token Buildkite issues for the job for a short-lived Pulumi access token, so no long-lived credential is stored:
plugins:
- pulumi#v1.0.0:
use-oidc: true
audience: "urn:pulumi:org:ACME_ORG"
Pulumi ESC (Environments, Secrets, and Configuration) then supplies cloud credentials, secrets, and configuration to your Pulumi program. Because ESC delivers those values the same way whether the consumer is a pipeline or a developer’s machine, a single environment definition works in both places — you don’t store separate cloud provider keys in the pipeline.
The trunk-based development workflow
The most common way to run Pulumi in Buildkite follows a trunk-based development model, where work merges into a single main branch and deployments flow outward from there:
- Open a pull request. The pipeline runs
pulumi previewand surfaces the proposed changes for review. - Merge to the main branch. The pipeline runs
pulumi upto deploy the change to an environment that receives continuous delivery, such as a shared development or staging environment. - Promote to production. Pushing a git tag triggers a deployment to production, keeping production updates deliberate and traceable.
The pipeline below implements all three stages with conditional steps. It assumes a Pulumi program in an infra/ directory and stacks named acme/website/staging and acme/website/production.
# .buildkite/pipeline.yml
steps:
# Pull request: preview the proposed changes and post them as a build annotation.
- label: ":pulumi: Preview"
if: build.pull_request.id != null
commands:
- cd infra && npm install
- pulumi preview --stack acme/website/staging 2>&1 | tee preview.txt
- printf '```\n%s\n```\n' "$(cat preview.txt)" | buildkite-agent annotate --style info
plugins:
- secrets#v1.0.0:
variables:
PULUMI_ACCESS_TOKEN: PULUMI_ACCESS_TOKEN
- pulumi#v1.0.0: ~
# Merge to the main branch: deploy to the staging environment.
- label: ":pulumi: Deploy to staging"
if: build.branch == "main" && build.pull_request.id == null
commands:
- cd infra && npm install
- pulumi up --yes --stack acme/website/staging
plugins:
- secrets#v1.0.0:
variables:
PULUMI_ACCESS_TOKEN: PULUMI_ACCESS_TOKEN
- pulumi#v1.0.0: ~
# Tag push: promote to production.
- label: ":pulumi: Deploy to production"
if: build.tag != null
commands:
- cd infra && npm install
- pulumi up --yes --stack acme/website/production
plugins:
- secrets#v1.0.0:
variables:
PULUMI_ACCESS_TOKEN: PULUMI_ACCESS_TOKEN
- pulumi#v1.0.0: ~
To promote a release, push a tag:
git tag release-2026-05-20
git push origin release-2026-05-20
Buildkite decides when to start a build — on pull requests, branch pushes, and tags — through your pipeline’s source control settings, not the pipeline file. Enable pull request and tag builds there; the if expressions above then select which steps run for each build.
For an optional ephemeral environment on each pull request, pair the preview step with a Review Stack, which provisions and tears down a per-PR environment automatically.
Buildkite features worth knowing
- Dynamic pipelines. Buildkite can generate pipeline steps at build time with dynamic pipelines. This is useful when you want to fan out a step per Pulumi stack or project rather than hard-coding each one.
- Cache volumes. On hosted agents, cache volumes persist directories across builds. Caching
~/.pulumi/pluginsand your language packages (node_modules, and so on) speeds up runs. Pulumi plugin versions are tied 1:1 to the Pulumi package versions that use them, so the cache stays correct as long as those packages are unchanged.
Managing Buildkite with Pulumi
You can also manage Buildkite itself — pipelines, teams, and agent tokens — as code with the community-maintained Pulumiverse Buildkite provider in the Pulumi Registry.
Additional resources
- Continuous delivery — overview of running Pulumi in CI/CD.
- Pulumi ESC — deliver credentials, secrets, and configuration to pipelines and developers consistently.
- OIDC issuers — exchange a CI/CD system’s OIDC token for a short-lived Pulumi access token.
- Review Stacks — ephemeral environments created automatically for each pull request.
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.