Skip to main content
Running ubx apply from a developer’s laptop works fine when you’re the only person touching the infrastructure. The moment a second engineer joins, you need a shared, auditable, automatable pipeline. The standard pattern is PR-plan / merge-apply: when a pull request is opened, the CI pipeline runs ubx plan and posts the output as a PR comment so reviewers can see exactly what will change before approving. When the PR merges to main, the pipeline runs ubx apply automatically. This creates a clear audit trail, prevents “it worked on my machine” surprises, ensures every production change goes through review, and makes rollback straightforward (revert the commit, CI applies the revert). This tutorial is the complete, working GitHub Actions workflow — not pseudocode, but the actual YAML you’d commit to .github/workflows/.

What you’ll learn

  • The PR-plan / merge-apply GitHub Actions pattern for ubx
  • How to post ubx plan output as a PR comment
  • How environment protection rules gate ubx apply to reviewed changes only

Why this matters

A CI pipeline that runs ubx validate on every push and ubx apply on every merge to main is the minimum viable infrastructure pipeline. It costs nothing extra, prevents most classes of production outage from accidental or unreviewed changes, and gives you a searchable history of every change in your CI logs.

The source code

unit "aws_s3_bucket_v2" "app" {
  bucket = "myapp-${input.env}-artifacts"
  versioning = { enabled = true }
}

unit "aws_rds_instance" "db" {
  engine                  = "postgres"
  instance_class          = "db.t3.micro"
  identifier              = "myapp-${input.env}-db"
  storage_encrypted       = true
  backup_retention_period = 7
}

How it works

1

Pull request triggers the plan job

Any PR targeting main triggers the plan job. It runs ubx validate (fast, no state access), then ubx plan --out plan.ubx (requires AWS credentials and S3 state access). The plan output is captured to plan-output.txt and posted as a PR comment.
2

The plan file is uploaded as an artifact

--out plan.ubx saves the plan to a file. The artifact is uploaded with a name tied to the commit SHA — plan-${{ github.sha }}. This links the exact plan that was reviewed to the exact commit that was approved.
3

Merge to main triggers the apply job

The apply job runs only on push to main — not on PRs. The environment: production block (configured in GitHub Settings → Environments) can require manual approval from a designated reviewer before the apply runs, adding a final human gate.

Common mistakes

ubx apply --yes skips the interactive confirmation prompt — required in CI since there’s no human to type “yes”. Ensure your environment: production in GitHub Settings requires a reviewer to approve the apply job before it runs, so --yes doesn’t mean “apply without any human review ever”.
Set UBX_ACTOR=github-actions/${{ github.workflow }}/${{ github.run_id }} in the apply step environment to get meaningful actor names in ubx history output. The default AWS IAM role ARN is technically accurate but hard to read in the history timeline.

Run it

# Locally:
ubx validate
ubx plan --out plan.ubx
ubx apply --plan plan.ubx  # apply from saved plan (tutorial 53)

# In CI: push a PR to trigger plan, merge to main to trigger apply

What you learned

The PR-plan / merge-apply pattern gives every change a review step before production
ubx plan --out plan.ubx saves the plan for upload as a CI artifact
environment: production in GitHub Settings adds a required-reviewer gate before apply

Next steps

Plan approval workflow

Apply from a saved plan file for two-step approval

Shared apply history

Track every CI apply in a shared audit log