Terraform CI/CD with GitHub Actions (plan/apply)
Learn a safe CI/CD workflow for Terraform using GitHub Actions: formatting, validation, plan, and gated apply.
CI/CD for Terraform is about two things:
- making Terraform runs repeatable
- reducing the risk of applying unintended changes
A common safe pattern is:
- CI:
fmt→init→validate→plan(for every PR) - Apply: run
applyonly after review/approval (often on main)
Learning outcomes
After this tutorial you can:
- understand a Terraform GitHub Actions workflow
- generate and persist an execution plan artifact
- gate apply on branch/approval
1) Terraform steps in CI
Typical CI steps:
terraform fmt -checkterraform initterraform validateterraform plan
Example commands:
terraform fmt -check -recursive
terraform init
terraform validate
terraform plan -out=tfplan
2) Recommended GitHub Actions approach (plan in PR, apply in main)
Create .github/workflows/terraform.yml
Adjust backend/provider configuration to your environment.
name: Terraform
on:
pull_request:
push:
branches: ["main"]
permissions:
contents: read
jobs:
terraform-plan:
name: Terraform Plan (PR)
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Upload plan artifact
uses: actions/upload-artifact@v4
with:
name: tfplan
path: tfplan
terraform-apply:
name: Terraform Apply (main)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: []
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Init
run: terraform init
- name: Download plan artifact (optional)
uses: actions/download-artifact@v4
with:
name: tfplan
path: .
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
Notes on the above workflow
- The plan job runs on PRs.
- The apply job runs on
main. - For the strongest safety, you should ensure the
tfplanused for apply matches the code that triggered the apply.
Many teams implement apply using:
- environments with required reviewers in GitHub
- or by generating plan and applying the plan within the same run
3) Secrets and credentials
You must authenticate Terraform to your cloud provider. Common patterns:
- AWS: use OIDC or long-lived IAM credentials in GitHub Secrets
- Azure: service principal credentials
- GCP: service account key (less preferred) or workload identity federation
At a minimum, store provider credentials in GitHub Secrets and reference them as environment variables.
Example (AWS - conceptual):
env:
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
4) Plan output for human review (optional)
Instead of only saving tfplan, you can also print a human-readable plan:
terraform plan -no-color
Some teams publish it as a PR comment.
5) Cleanup / destroy
Avoid running destroy in CI unless explicitly required. When needed, run manually with careful plan review:
terraform destroy