Pulumi GitHub Actions let you automate your infrastructure provisioning and updates directly from your GitHub repository, bringing CI/CD principles to your cloud resources.
Here’s a Pulumi program in TypeScript for AWS that provisions an S3 bucket:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Create an S3 bucket
const myBucket = new aws.s3.Bucket("my-pulumi-bucket", {
acl: "private", // Set the Access Control List to private
tags: {
Environment: "Dev",
ManagedBy: "Pulumi",
},
});
// Export the bucket name
export const bucketName = myBucket.id;
When this code runs in a GitHub Action, Pulumi interacts with the AWS API to create the S3 bucket. The acl: "private" setting ensures that the bucket is not publicly accessible by default, and the tags provide metadata for organization and cost allocation. The bucketName export makes the name of the created bucket available as an output of the Pulumi deployment.
The core problem Pulumi solves is managing infrastructure as code. Instead of manually clicking through cloud provider consoles or writing imperative scripts that can become complex and error-prone, you define your desired infrastructure state in a familiar programming language. Pulumi then calculates the necessary changes and applies them idempotently.
Internally, Pulumi maintains a state file that tracks the resources it manages. When you run pulumi up, Pulumi compares your desired state (defined in your code) with the current state in your cloud provider and the Pulumi state file. It then generates a deployment plan, detailing what resources will be created, updated, or deleted. This plan is presented for your approval before execution.
The key levers you control are the resource definitions in your code and the configuration values. For instance, in the S3 bucket example, you could easily change the acl to "public-read" if needed, or add more complex configurations like versioning, lifecycle rules, or website hosting. Pulumi supports a wide range of cloud resources across AWS, Azure, Google Cloud, and Kubernetes, allowing you to manage diverse infrastructure from a single codebase.
The Pulumi CLI, when executed by a GitHub Action, uses a backend to store the state of your deployments. This backend can be Pulumi’s own service (SaaS) or a self-hosted option like an S3 bucket. When you run pulumi up, the CLI fetches the latest state from the backend, compares it with your code, and then executes the necessary API calls to your cloud provider. The results of these API calls are then used to update the state in the backend.
When you use Pulumi with GitHub Actions, you’re essentially automating the pulumi up command. This involves setting up the Pulumi CLI in your workflow, configuring authentication to your cloud provider (e.g., AWS credentials), and then running the Pulumi commands. The GitHub Action typically checks out your code, sets up the Pulumi environment, and executes pulumi up, pulumi preview, or pulumi destroy based on your workflow triggers (e.g., on push to main branch, on pull request).
The most surprising truth about Pulumi’s state management is that it doesn’t store resource definitions in its state file, but rather the outputs of those resources and their unique identifiers. When Pulumi needs to update a resource, it uses the identifier from its state to find the resource in the cloud provider, then compares the desired properties from your code against the resource’s current properties. This means that even if your Pulumi code changes significantly (e.g., you refactor a resource into a different Pulumi component), as long as the resource’s unique ID remains the same and Pulumi can still find it in the cloud, it will attempt to update it in place. If a resource is truly deleted from the cloud outside of Pulumi’s management, Pulumi will detect this drift and try to recreate it during the next pulumi up, unless it’s explicitly removed from your code.
The next natural step after automating infrastructure deployment is managing secrets within your Pulumi programs.