Pulumi YAML doesn’t just let you describe infrastructure; it actually lets you program it declaratively, running standard YAML through a Pulumi engine that interprets it as a desired state.

Let’s see it in action. Imagine you want to deploy a simple S3 bucket.

name: my-pulumi-app
runtime: yaml
description: A simple S3 bucket example

resources:
  myBucket:
    type: aws:s3:Bucket
    properties:
      bucket: my-unique-pulumi-bucket-12345
      acl: public-read

When you run pulumi up with this file, Pulumi doesn’t just create an S3 bucket. It parses this YAML, understands aws:s3:Bucket means it needs to interact with AWS, and then uses the AWS SDK to provision a bucket named my-unique-pulumi-bucket-12345 with the specified ACL. The name and runtime fields tell Pulumi how to manage this as a distinct Pulumi program.

The core problem Pulumi YAML solves is bridging the gap between the declarative nature of infrastructure-as-code and the imperative execution needed to actually make it happen. You declare what you want (an S3 bucket with specific properties), and Pulumi, acting as the engine, figures out how to get there using the appropriate cloud provider SDKs. It manages state, handles updates, and allows for easy deletion, all driven by your YAML definitions.

Internally, Pulumi parses your YAML into an Abstract Syntax Tree (AST). This AST is then translated into a Pulumi program (conceptually, if not literally in code). The Pulumi engine then takes this program and executes it. For cloud resources, this means interacting with the respective cloud provider APIs. For example, an aws:s3:Bucket resource definition in YAML is mapped to the aws.s3.Bucket resource in the Pulumi AWS SDK. The properties in your YAML become arguments to the resource constructor.

The resources block is where the magic happens. Each key under resources (e.g., myBucket) becomes a logical resource in your Pulumi program. The type field is crucial; it tells Pulumi which resource provider and resource type to use (e.g., aws:s3:Bucket for an AWS S3 bucket). The properties object contains the desired configuration for that resource, mirroring the properties available in the Pulumi provider’s SDK. Pulumi uses these properties to create or update the actual infrastructure.

You have levers to control not just the resources themselves but also their dependencies and outputs. For instance, you can reference outputs from one resource as inputs to another. If myBucket had an output like arn, you could use it in another resource’s configuration:

resources:
  myBucket:
    type: aws:s3:Bucket
    properties:
      bucket: my-unique-pulumi-bucket-12345
      acl: public-read

  myPolicy:
    type: aws:s3:BucketPolicy
    properties:
      bucket: ${myBucket.id} # Referencing the bucket ID
      policy: |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": "*",
              "Action": "s3:GetObject",
              "Resource": "${myBucket.arn}/*" # Referencing the bucket ARN
            }
          ]
        }

Here, ${myBucket.id} and ${myBucket.arn} are Pulumi expressions that dynamically resolve to the actual ID and ARN of the myBucket resource once it’s provisioned. This allows for building complex, interconnected infrastructure.

A common point of confusion is how Pulumi handles state for YAML deployments. Unlike traditional YAML templating that just generates output, Pulumi YAML is stateful. When you run pulumi up, Pulumi records the deployed resources and their properties in a state file. This state file is critical for subsequent pulumi up operations to know what has changed, what needs updating, or what can be safely removed. The name field in the Pulumi.yaml file is used to identify the stack, and the state is typically stored in a backend like the Pulumi Service, an S3 bucket, or a local file.

When you define a resource with a specific name, like myBucket, Pulumi tracks that specific instance. If you change a property, say from acl: public-read to acl: private, Pulumi will detect this drift and propose an update to change the ACL. If you delete the myBucket resource definition from your YAML and run pulumi up again, Pulumi will recognize that the resource is no longer declared and will propose to destroy it. This state management is what elevates Pulumi YAML from a templating language to a true infrastructure-as-code tool.

The other aspect that people often overlook is the full power of the Pulumi engine, which extends beyond just resource provisioning. You can define outputs directly in your YAML, making information about your deployed infrastructure easily accessible. For example:

outputs:
  bucketName: ${myBucket.bucket}
  bucketArn: ${myBucket.arn}

After running pulumi up, you can then run pulumi stack output to retrieve these values, which is incredibly useful for passing configuration to other systems or for human readability.

The next step in mastering Pulumi YAML involves understanding how to integrate it with CI/CD pipelines for automated deployments.

Want structured learning?

Take the full Pulumi course →