Terraform in CI/CD Pipelines: GitHub Actions, GitLab CI & Terraform Cloud
In this tutorial, you'll learn about Terraform in CI/CD Pipelines: GitHub Actions, GitLab CI & Terraform Cloud. We cover key concepts, practical examples, and best practices.
Running Terraform in CI/CD pipelines transforms infrastructure changes from manual CLI operations into automated, reviewed, and audited deployment workflows integrated with your software delivery process.
What You'll Learn
In this tutorial, you will learn how to build Terraform CI/CD pipelines with GitHub Actions, GitLab CI, and Terraform Cloud, implement plan-only checks on pull requests, design safe approval workflows for production, use OIDC for cloud authentication, and manage state across environments in CI.
Why It Matters
Manual <a href="/devops/terraform/">terraform</a> apply in production lacks audit trails and depends on individual judgment. CI/CD pipelines enforce consistent workflows, require code review, prevent unapproved changes, and provide full deployment history for compliance.
Real-World Use
DodaTech runs Terraform in GitHub Actions for dev and staging environments and Terraform Cloud for production. Durga Antivirus Pro's platform team reviews every production plan through Terraform Cloud's run UI before applying, maintaining SOC 2 compliance with full audit trails.
Pipeline Architecture
graph LR
A[Git Push / PR] --> B{CI Trigger}
B --> C[Feature Branch]
B --> D[Main Branch]
C --> E[Plan-Only Check]
E --> F[PR Comment with Plan]
F --> G[Code Review]
G --> H[Merge to Main]
D --> I[Init + Validate]
I --> J[Plan]
J --> K{Environment}
K --> L[Dev: Auto-Apply]
K --> M[Staging: Auto-Apply]
K --> N[Production: Manual Approve]
L --> O[Terraform Apply]
M --> O
N --> P[Approval Gate]
P --> O
style A fill:#4a90d9,color:#fff
style H fill:#50c878,color:#fff
style P fill:#ff9900,color:#fff
GitHub Actions Pipelines
Plan-Only on Pull Request
# .github/workflows/terraform-plan.yml
name: Terraform Plan
on:
pull_request:
branches: [main]
paths:
- 'terraform/**'
- '.github/workflows/terraform-*'
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform/environments/dev
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-terraform
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.5
- name: Terraform Init
run: terraform init -backend-config=backend.hcl
- name: Terraform Format
run: terraform fmt -check -recursive
- name: Terraform Validate
run: terraform validate -no-color
- name: Terraform Plan
run: terraform plan -no-color
Expected output in PR check:
Terraform Plan succeeded.
No changes. Your infrastructure matches the configuration.
Or:
Terraform will perform the following actions:
# aws_instance.web will be updated in-place
~ resource "aws_instance" "web" {
~ instance_type = "t3.medium" -> "t3.large"
}
Plan: 0 to add, 1 to change, 0 to destroy.
Apply on Merge with Approval Gate
# .github/workflows/terraform-apply.yml
name: Terraform Apply
on:
push:
branches: [main]
paths:
- 'terraform/**'
permissions:
id-token: write
contents: read
jobs:
apply-production:
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform/environments/production
environment:
name: production
url: https://console.aws.amazon.com
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-terraform
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.5
- name: Terraform Init
run: terraform init -backend-config=backend.hcl
- name: Terraform Plan
run: terraform plan -no-color -out=plan.tfplan
- name: Terraform Apply
run: terraform apply -auto-approve plan.tfplan
GitLab CI Pipelines
Multi-Environment Pipeline
# .gitlab-ci.yml
image: hashicorp/terraform:1.9
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- .terraform/
variables:
TF_ROOT: terraform/environments/${ENVIRONMENT_NAME}
terraform:plan:
stage: plan
script:
- cd ${TF_ROOT}
- terraform init -backend-config=backend.hcl
- terraform fmt -check -recursive
- terraform validate
- terraform plan -no-color
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- terraform/**/*
terraform:apply-dev:
stage: apply
script:
- cd ${TF_ROOT}
- terraform init -backend-config=backend.hcl
- terraform apply -auto-approve
variables:
ENVIRONMENT_NAME: dev
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
changes:
- terraform/**/*
terraform:apply-production:
stage: apply
script:
- cd ${TF_ROOT}
- terraform init -backend-config=backend.hcl
- terraform plan -no-color -out=plan.tfplan
- terraform apply plan.tfplan
variables:
ENVIRONMENT_NAME: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
changes:
- terraform/**/*
when: manual
Expected output: GitLab runs terraform plan automatically on merge requests. Dev applies on merge to develop. Production requires a manual button click in the GitLab UI.
OIDC Authentication
GitHub Actions OIDC to AWS
# aws-oidc.tf
resource "aws_iam_role" "github_actions" {
name = "github-actions-terraform"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow]
Principal = {
Federated = "arn:aws:iam::${var.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/${var.github_repo}:*"
}
}
}
]
})
}
resource "aws_iam_role_policy" "terraform" {
name = "terraform-execution"
role = aws_iam_role.github_actions.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow]
Action = [
"ec2:*",
"s3:*",
"dynamodb:*",
"iam:*]
]
Resource = ["*"]
}
]
})
}
Terraform Cloud Workflows
VCS-Driven Pipeline
terraform {
cloud {
organization = "dodatech"
workspaces {
name = "production"
}
}
}
terraform plan
Expected output: Terraform Cloud receives the plan request. The plan runs in Terraform Cloud's infrastructure. Output is visible in the TFC UI with cost estimation and policy check results.
Common Mistakes
1. Auto-Approving Production
Skipping manual approval in production can cause unreviewed destruction of critical resources.
2. Storing Credentials in CI Variables
AWS access keys in CI variables get leaked through logs. Always use OIDC for cloud authentication.
3. Sharing State Across CI Runners
Multiple CI runners applying to the same state file cause lock conflicts. Isolate state per environment.
4. Skipping Fmt Check in CI
Formatting inconsistencies accumulate. Always run <a href="/devops/terraform/">terraform</a> fmt -check early in the pipeline.
5. Not Using Plan Files
Applying without a saved plan file means the apply may differ from what was reviewed. Always save and reuse plan files.
Practice Questions
1. How does OIDC improve security in Terraform CI/CD pipelines? OIDC removes static cloud credentials from CI variables by exchanging short-lived tokens with the cloud provider's IAM service.
2. What is the difference between plan-only and apply pipelines? Plan-only runs on PRs to preview changes without modifying infrastructure. Apply runs on merge to execute approved changes.
3. How do you enforce manual approval for production Terraform applies? Use GitHub Environments with required reviewers or Terraform Cloud's manual apply setting in the workspace.
4. Challenge: Set up a GitHub Actions workflow with three stages: lint (fmt + validate), plan (with PR comment), and apply (on merge to main with environment approval gate). Use OIDC authentication.
Mini Project: Complete Terraform CI/CD Pipeline
Create a GitHub Actions workflow with plan-only on PR, auto-apply for dev and staging, and manual-approve for production. Include OIDC authentication, terraform fmt check, validate, tflint, and a PR comment with the plan output. Set up separate state backends per environment.
Related Concepts
What's Next
Build Terraform CI/CD pipelines to automate your infrastructure deployments, then implement Testing for validation. Study Docker and DevOps practices for end-to-end automation.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro