A GitHub Action that runs terraform plan and posts a formatted comment to your pull request.
Subsequent pushes to the PR's branch will update the existing comment with the latest plan.
This makes it easy for reviewers (who won't have access to run terraform plan) to quickly and easily see what infrastructure changes would be applied by the PR.
- Updates existing comments instead of creating duplicates
- Collapsible plan output section
- handles large plans gracefully-ish with truncation
- Shows plan summary, count of import/create/update/destroy
- Multi-directory support via
working-directoryinput (for mono repos) - Terraform workspace support - works with multiple workspaces (dev/staging/prod)
- Accessibility themes - colorblind-friendly emoji options
name: Terraform Plan
on:
pull_request:
branches: [main]
jobs:
plan:
runs-on: ubuntu-latest
concurrency:
group: terraform
cancel-in-progress: false
permissions:
contents: read
pull-requests: write
id-token: write # If using OIDC
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
# Configure your cloud credentials (example: AWS OIDC)
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/my-role
aws-region: us-east-2
# Run the plan
# Prefer a full 40-character commit SHA in production. Keep the release tag
# in a trailing comment for human review.
- uses: thekbb/terraform-plan-commenter-action@<full-commit-sha> # v1.2.1
with:
init-args: '-lockfile=readonly'That is the recommended starting point:
- trigger on
pull_request, notpull_request_target - grant only the permissions the job needs
- use a full 40-character commit SHA if you want an immutable workflow reference
- keep the release tag in a trailing comment so humans can see the intended version quickly
| Input | Description | Required | Default |
|---|---|---|---|
github-token |
GitHub token for posting PR comments | No | ${{ github.token }} |
working-directory |
Directory containing Terraform configuration | No | . |
terraform-version |
Terraform version to use | No | latest |
setup-terraform |
Whether to setup Terraform (set false if already configured) |
No | true |
init-args |
Trusted-only additional arguments for terraform init |
No | '' |
plan-args |
Trusted-only additional arguments for terraform plan |
No | '' |
summary-theme |
Emoji theme: default, colorblind, or minimal |
No | default |
Use init-args and plan-args only for trusted, repo-controlled values.
| Output | Description |
|---|---|
plan-exit-code |
Exit code from terraform plan (0=no changes, 1=error, 2=changes) |
has-changes |
Whether the plan has changes (true/false) |
plan-stdout |
Standard output from terraform plan |
Though Terraform state locking protects against concurrent runs, multiple commits in the same PR, multiple PRs, or a Terraform apply from another GitHub Action can still collide and may require manually unlocking state.
You should use GitHub actions concurrency to queue up jobs. You'll need to get fancier if you have multiple workspaces, or a matrix setup - action inputs and matrix values will help make the group name.
concurrency:
group: terraform
cancel-in-progress: false- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
terraform-version: '1.14.3'- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
working-directory: 'infrastructure/'- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
plan-args: '-var-file=prod.tfvars'If you're using a matrix or already have Terraform configured:
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: '1.14.3'
terraform_wrapper: false # Important if capturing output
- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
setup-terraform: 'false'- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
summary-theme: 'colorblind'Available themes:
| Theme | Import | Create | Update | Destroy |
|---|---|---|---|---|
default |
π΅ | π’ | π‘ | π΄ |
colorblind |
π₯ | β | βοΈ | β |
minimal |
[import] | [create] | [update] | [destroy] |
The action automatically detects your Terraform workspace:
- Workspaces: Detects the current workspace (via
terraform workspace show) and creates separate comments for each workspace (dev/staging/prod) - Monorepos: Each
working-directorygets its own independent comment - Matrix builds: Jobs running different workspace/directory combinations maintain separate comments
Select the workspace before running the action:
- name: Select Terraform workspace
run: terraform workspace select staging || terraform workspace new staging
working-directory: ./infrastructure
- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'
working-directory: ./infrastructureconcurrency:
group: terraform
cancel-in-progress: false
strategy:
matrix:
workspace: [dev, staging, prod]
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Configure AWS
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/terraform-${{ matrix.workspace }}
aws-region: us-east-1
- name: Select workspace
run: terraform workspace select ${{ matrix.workspace }} || terraform workspace new ${{ matrix.workspace }}
- uses: thekbb/terraform-plan-commenter-action@v1
with:
init-args: '-lockfile=readonly'Each workspace gets its own independent PR comment that updates separately!
The action posts a comment like this:
π΅ import
2Β· π’ create3Β· π‘ update1Β· π΄ destroy0Terraform used the selected providers to generate the following execution plan:Pusher: @username, Action:
pull_request
For security, prefer a full 40-character commit SHA over a moving tag such as @v1. GitHub recommends full-length
commit SHAs as the immutable option for third-party actions in its
Secure use reference. If you want automatic
updates while still using immutable workflow references, enable Dependabot for GitHub Actions in your repository:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'Dependabot updates workflow uses: references in .github/workflows, including commit SHAs for GitHub Actions. The
trailing # v1.2.1 comment is mainly for human review so maintainers can see which release a referenced SHA
corresponds to.
Use a release-specific tag such as @v1.2.1 if you want a human-readable reference to a single published release. Use
@v1 only if you deliberately want the convenience of a moving major tag. For GitHub's model for combining fixed
release tags with movable major tags, see
Using immutable releases and tags to manage your action's releases.
- Default token friendly -
github-tokendefaults to${{ github.token }} - Minimal permissions - the action only needs the permissions granted to the job that invokes it
- GitHub-aligned workflow security guidance - GitHub recommends full commit SHAs for third-party actions in its Secure use reference
- Immutable workflow references available - prefer a full 40-character commit SHA for production workflows
- Signed release tags - release tags are signed with the published project GPG key
- Published release signing key - import
keys/release-signing-key.ascbefore verifying a tag - Moving major tag is explicit -
@v1is intentionally movable and should not be treated as an immutable reference
- uses: thekbb/terraform-plan-commenter-action@<full-commit-sha>
with:
init-args: '-lockfile=readonly'If you prefer a release-specific tag in uses:, pin to the current release instead:
- uses: thekbb/terraform-plan-commenter-action@v1.2.2
with:
init-args: '-lockfile=readonly'Release tags in this repository are signed with the GPG key whose public half is included at
keys/release-signing-key.asc.
Fingerprint:
353A AFB2 1CE8 1D84 3634 AD3E DE52 EEA6 AF0D 8779
To verify a release tag locally:
gpg --import keys/release-signing-key.asc
gpg --show-keys --fingerprint keys/release-signing-key.asc
git fetch origin --tags --force
git verify-tag v1.2.1
git rev-parse v1.2.1^{commit}For an additional cross-check, you can confirm the same public key is published on keys.openpgp.org for
kevin@thekbb.net:
gpg --keyserver hkps://keys.openpgp.org --search-keys kevin@thekbb.netThe fingerprint should still match exactly:
353A AFB2 1CE8 1D84 3634 AD3E DE52 EEA6 AF0D 8779
See CONTRIBUTING.md for development setup.
