CI/CD Overview
Why CI/CD for Infrastructure?
The Problem: Manual infrastructure changes are error-prone, hard to audit, and create bottlenecks. Different team members make conflicting changes without visibility.
The Solution: Pulumi integrates natively with CI/CD pipelines, enabling preview-on-PR and deploy-on-merge workflows with full audit trails.
Real Impact: Teams with automated IaC pipelines deploy 10x more frequently with 95% fewer change-related incidents.
Real-World Analogy
Think of CI/CD for infrastructure as a quality-controlled assembly line:
- PR Preview = Quality inspector checking the blueprint before production
- Review Stacks = Building a prototype to test before mass production
- Deploy on Merge = Green-lighting the blueprint for production deployment
- Automation API = Robot arms that build exactly what the blueprint specifies
CI/CD Pipeline Patterns
Preview on PR
Automatically run pulumi preview on every pull request to show exactly what changes will be made.
Deploy on Merge
Automatically deploy to staging or production when PRs are merged to the main branch.
Review Stacks
Spin up ephemeral environments per PR for testing, then tear them down when the PR is closed.
Automation API
Embed Pulumi inside custom applications for programmatic infrastructure management without the CLI.
GitHub Actions
Preview on Pull Request
name: Pulumi Preview
on:
pull_request:
branches: [main]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: pulumi/actions@v5
with:
command: preview
stack-name: org/project/staging
comment-on-pr: true
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Deploy on Merge
name: Pulumi Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: pulumi/actions@v5
with:
command: up
stack-name: org/project/production
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GitLab CI
stages:
- preview
- deploy
preview:
stage: preview
image: pulumi/pulumi-nodejs:latest
script:
- npm ci
- pulumi login
- pulumi stack select staging
- pulumi preview --diff
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
deploy:
stage: deploy
image: pulumi/pulumi-nodejs:latest
script:
- npm ci
- pulumi login
- pulumi stack select production
- pulumi up --yes
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
Automation API
import { LocalWorkspace } from "@pulumi/pulumi/automation";
import express from "express";
const app = express();
app.post("/deploy/:env", async (req, res) => {
const { env } = req.params;
const stack = await LocalWorkspace.createOrSelectStack({
stackName: env,
workDir: "./infra",
});
// Set config based on environment
await stack.setConfig("app:replicas", {
value: env === "production" ? "3" : "1",
});
const result = await stack.up({ onOutput: console.log });
res.json({
status: result.summary.result,
outputs: result.outputs,
});
});
app.listen(3000);
Review Stacks
name: Review Stack
on:
pull_request:
types: [opened, synchronize, closed]
jobs:
review:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- uses: pulumi/actions@v5
with:
command: up
stack-name: review-${{ github.event.number }}
comment-on-pr: true
cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- uses: pulumi/actions@v5
with:
command: destroy
stack-name: review-${{ github.event.number }}
- run: pulumi stack rm review-${{ github.event.number }} --yes
Quick Reference
| CI/CD Platform | Integration | Key Features |
|---|---|---|
| GitHub Actions | pulumi/actions@v5 | PR comments, OIDC auth, matrix builds |
| GitLab CI | Docker image | MR integration, environments, artifacts |
| Jenkins | Shell steps | Pipeline DSL, shared libraries |
| CircleCI | Orb | Parallelism, caching, approval gates |
| Azure DevOps | Task extension | Azure auth, release gates |
CI/CD Best Practices
- Always run
pulumi previewon PRs and post the diff as a comment - Use OIDC tokens instead of long-lived credentials when possible
- Implement review stacks for testing infrastructure changes in isolation
- Pin Pulumi CLI and provider versions for reproducible builds
- Use
--expect-no-changesin drift detection workflows