Skip to main content
Real infrastructure is never a single resource in isolation. An EC2 instance lives in a VPC. An RDS database needs a subnet group. A security group references a VPC ID. Every resource depends on something else existing first, and the order those things get created matters enormously. In raw Pulumi, you manage this by passing output references between resources — Pulumi infers the dependency graph from which outputs are consumed as inputs. ubx preserves exactly this model, but expresses it in the .iac DSL instead of TypeScript or Python. When you reference one resource’s output inside another resource’s attributes, ubx records that as a dependency edge, builds the graph, topologically sorts the resources, and generates code that creates them in the right order. You don’t declare the order — you declare the relationships, and the order follows.

What you’ll learn

  • How to reference one resource’s output attribute from another
  • How ubx infers the dependency order automatically from resource output references
  • When to use explicit depends_on vs letting ubx infer it

Why this matters

The topological sort means you can declare resources in any order in your .iac file. ubx determines the correct apply order from the dependency graph — not from file order. This makes large stacks easier to organise without worrying about declaration order.

The source code

# Resource 1: the VPC
unit "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "vpc-${input.env}" }
}

# Resource 2: DHCP options — no dependency on the VPC yet
unit "aws_vpc_dhcp_options" "main" {
  domain_name         = "${input.env}.internal"
  domain_name_servers = ["AmazonProvidedDNS"]
}

# Resource 3: associate them — depends on both resources above.
# ubx infers this from the output references and creates the association last.
unit "aws_vpc_dhcp_options_association" "main" {
  vpc_id          = unit.aws_vpc.main.id
  dhcp_options_id = unit.aws_vpc_dhcp_options.main.id
}

How it works

1

The IR pass builds a dependency graph

Every unit.x.y.attr reference is recorded as an IRRef with Pending: true. The IR builder constructs a directed graph: the DHCP association depends on both the VPC and the DHCP options object.
2

Topological sort determines apply order

Resources are sorted so that dependencies are always created before dependents. The VPC and DHCP options can be created in parallel; the association waits for both.
3

Pulumi executes in the correct order

The generated code uses pulumi.all([...]).apply(...) to ensure the association resource only runs after both the VPC and DHCP options exist and their IDs are available.

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 main_vpc = new aws.ec2.Vpc("main_vpc", {
    cidrBlock: "10.0.0.0/16",
    enableDnsHostnames: true,
    tags: { Name: `vpc-${env}` },
});

const main_dhcp = new aws.ec2.VpcDhcpOptions("main_dhcp", {
    domainName: `${env}.internal`,
    domainNameServers: ["AmazonProvidedDNS"],
});

const main_assoc = pulumi.all([main_vpc.id, main_dhcp.id])
    .apply(([vpcId, dhcpOptionsId]) =>
        new aws.ec2.VpcDhcpOptionsAssociation("main_assoc", {
            vpcId,
            dhcpOptionsId,
        })
    );

export const vpcId = main_vpc.id;

Common mistakes

Adding explicit depends_on for relationships already expressed by direct attribute references is unnecessary — ubx infers those dependencies automatically. Reserve depends_on for side-effect dependencies that aren’t captured by a direct attribute reference, such as an IAM policy that must exist before a resource is created even though the resource doesn’t directly reference any of the policy’s outputs.
When two resources have no dependency between them — like the VPC and DHCP options in this example — Pulumi creates them in parallel. ubx’s generated .apply() chains express exactly the minimum required ordering, letting Pulumi parallelise everything it safely can.

Run it

ubx validate
ubx plan    # requires AWS credentials
ubx apply   # creates VPC and DHCP options in parallel, then the association

What you learned

ubx builds a dependency graph from resource output references and topologically sorts resources
depends_on is only needed for side-effect dependencies not expressed by direct references
Resources can be declared in any order in .iac — apply order is computed, not declared

Next steps

Pending refs: RDS to Helm

The Pending<T> type system in depth

Pending type concept

Deep dive into Resolved vs Pending values