1
Create the GitHub Actions workflow file
Set up a workflow that runs terraform plan on pull requests and apply on merge to main.
$ mkdir -p .github/workflows && cat <<'EOF' > .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
TF_VERSION: "1.7.0"
jobs:
plan:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- run: terraform init
- run: terraform validate
- run: terraform plan -no-color -out=tfplan
- uses: actions/upload-artifact@v4
with:
name: tfplan
path: tfplan
EOF
Use -no-color in CI to avoid ANSI escape codes in log output.
2
Add the apply job for main branch
Add a job that applies changes only when code is merged to the main branch.
$ cat <<'EOF' >> .github/workflows/terraform.yml
apply:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- run: terraform init
- run: terraform apply -auto-approve
EOF
The -auto-approve flag skips confirmation. This is only safe when gated by PR review and environment protection rules.
3
Configure cloud credentials as secrets
Add cloud provider credentials to your CI/CD platform's secret store.
$ gh secret set AWS_ACCESS_KEY_ID --body "AKIA..."
gh secret set AWS_SECRET_ACCESS_KEY --body "wJal..."
Never commit credentials to the repository. Always use CI/CD secrets or OIDC federation.
4
Add PR comment with plan output
Post the terraform plan output as a comment on the pull request for team review.
$ cat <<'EOF' >> .github/workflows/terraform.yml
- name: Comment Plan
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
with:
script: |
const output = `#### Terraform Plan
\`\`\`\n${{ steps.plan.outputs.stdout }}\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
EOF
5
Add format and validation checks
Include terraform fmt and validate as early checks to catch formatting and syntax issues.
$ cat <<'EOF' > .github/workflows/terraform-checks.yml
name: Terraform Checks
on: pull_request
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Format Check
run: terraform fmt -check -recursive
- name: Init
run: terraform init -backend=false
- name: Validate
run: terraform validate
EOF
Run fmt -check first. It is the fastest check and catches the most common issues.
6
Set up environment protection rules
Add GitHub environment protection rules to require approval before applying to production.
$ gh api repos/{owner}/{repo}/environments/production -X PUT -f 'reviewers[][type]=User' -f 'reviewers[][id]=1'
Combine environment protection with branch protection rules for defense in depth.