The Workflow
Plan on every pull request. Apply only when merged to main. This gives you code review for infrastructure changes.
name: Terraform
on:
pull_request:
paths: ['infra/**']
push:
branches: [main]
paths: ['infra/**']
permissions:
contents: read
pull-requests: write
jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.8
- name: Terraform Init
run: terraform init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Format Check
run: terraform fmt -check
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Comment Plan on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const output = `#### Terraform Plan
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\``;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Required Secrets
Add these in GitHub → Settings → Secrets:
AWS_ACCESS_KEY_ID— IAM user with deployment permissionsAWS_SECRET_ACCESS_KEY— corresponding secret key
For other clouds, use the equivalent credentials (GCP service account JSON, Azure service principal).
Master this topic with hands-on labs
Go beyond reading — build real projects in sandboxed environments with expert video guidance.
Browse Courses →Remote State Backend
Store state in S3 with DynamoDB locking:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}The CI runner reads and writes state through the same backend. DynamoDB prevents concurrent applies.
Multi-Environment
Use workspaces or matrix strategy:
jobs:
terraform:
strategy:
matrix:
environment: [dev, staging, prod]
steps:
- name: Terraform Plan
run: |
terraform workspace select ${{ matrix.environment }} || \
terraform workspace new ${{ matrix.environment }}
terraform plan -var-file="envs/${{ matrix.environment }}.tfvars"Get weekly IT automation tips
Docker, Ansible, Terraform, MLOps — curated insights delivered to your inbox. No spam.
Subscribe Free →Drift Detection
Schedule a daily plan to detect manual changes:
on:
schedule:
- cron: '0 8 * * *'
jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- name: Check for drift
run: |
terraform plan -detailed-exitcode -no-color || echo "DRIFT DETECTED"Exit code 2 means changes detected — pipe this to Slack or email.
Security Tips
- Never store state locally in CI — always use a remote backend
- Use OIDC instead of static credentials when possible
- Limit IAM permissions to only what Terraform needs
- Pin provider versions to avoid surprise breaking changes
- Review plans before merging — automated apply trusts your review process
Related Posts
- Terraform CI/CD Pipelines for more CI patterns
- Terraform Variables and Outputs for parameterization
- Terraform Security Practices for hardening
---
Ready to go deeper? Check out our hands-on course: Terraform for Beginners — practical exercises you can follow along on your own machine.
Ready to learn by doing?
Stop reading tutorials — start building. Expert video courses with hands-on labs in real sandboxed environments.
Related Articles
Terraform Workspaces for Environments
Use Terraform workspaces for dev, staging, and production. Practical patterns, trade-offs, and best practices for multi-env IaC.
Terraform Ansible Kubernetes Stack
Combine Terraform, Ansible, and Kubernetes for complete DevOps infrastructure. Provision, configure, and deploy end to end.
CI/CD Pipeline Tutorial from Scratch
Build a complete CI/CD pipeline from scratch with GitHub Actions. Lint, test, build, and deploy your application — fully automated on every push to your.
GitOps with ArgoCD Beginner Guide
Get started with GitOps using ArgoCD on Kubernetes. Install ArgoCD, create applications, configure sync, and automate deployments.
Golden Paths in Platform Engineering
Golden paths give developers a paved road through infrastructure complexity. Learn how to design golden paths for your internal developer platform.
Grafana Alloy OpenTelemetry Collector
Grafana Alloy is a programmable OpenTelemetry collector that replaces Prometheus Agent, Grafana Agent, and OTel Collector. Learn how to collect metrics, logs.
Explore topics
Browse more articles on the topics covered here.