Using Terraform Data Sources Effectively
Learn how to use Terraform data sources to query existing resources, look up AMIs, reference remote state, and build dynamic configurations. Complete.
DevOps
Learn how to integrate Terraform with GitHub Actions for automated infrastructure deployments. Complete guide with workflows, best practices, and.
Automating infrastructure deployments is a critical practice for modern DevOps teams. By integrating Terraform with GitHub Actions, you can create powerful CI/CD pipelines that automatically plan, validate, and apply infrastructure changes whenever code is pushed to your repository. This approach eliminates manual deployment steps, reduces human error, and ensures consistent infrastructure across environments.
In this comprehensive guide, we'll walk through setting up a production-ready GitHub Actions workflow for Terraform, covering everything from basic configuration to advanced patterns like environment-specific deployments and pull request previews.
Before getting started, ensure you have the following:
The first step is to securely store your cloud provider credentials as GitHub Secrets. Navigate to your repository's Settings → Secrets and variables → Actions and add the following secrets:
AWS_ACCESS_KEY_ID — Your AWS access keyAWS_SECRET_ACCESS_KEY — Your AWS secret keyAWS_REGION — Your preferred AWS region (e.g., us-east-1)For production environments, consider using OpenID Connect (OIDC) instead of long-lived credentials. This approach is more secure and eliminates the need to rotate access keys.
# oidc.tf — Create an OIDC provider for GitHub Actions
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-terraform"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:your-org/your-repo:*"
}
}
}
]
})
}Create a workflow file at .github/workflows/terraform.yml:
name: Terraform CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
id-token: write
env:
TF_VERSION: "1.7.0"
AWS_REGION: "us-east-1"
jobs:
terraform:
name: Terraform Plan & Apply
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/github-actions-terraform
aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
id: plan
run: terraform plan -no-color -out=tfplan
continue-on-error: true
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplanOne of the most valuable features is posting the Terraform plan output as a comment on pull requests. This allows team members to review infrastructure changes before they're applied:
- name: Comment PR with Plan
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const plan = `${{ steps.plan.outputs.stdout }}`;
const truncated = plan.length > 60000
? plan.substring(0, 60000) + '\n\n... (truncated)'
: plan;
const body = `## Terraform Plan Output
\`\`\`hcl
${truncated}
\`\`\`
*Plan: ${{ steps.plan.outcome }}*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});For organizations managing multiple environments (development, staging, production), you can use a matrix strategy or separate workflows:
jobs:
terraform:
name: Deploy to ${{ matrix.environment }}
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, staging, prod]
max-parallel: 1
environment: ${{ matrix.environment }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: |
terraform init \
-backend-config="key=${{ matrix.environment }}/terraform.tfstate"
- name: Terraform Plan
run: |
terraform plan \
-var-file="environments/${{ matrix.environment }}.tfvars" \
-out=tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplanIntegrate security scanning tools to catch misconfigurations before they reach production:
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
soft_fail: true
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: terraform
soft_fail: trueEnsure your Terraform backend is properly configured for CI/CD:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}Always use GitHub Secrets or a secrets manager like AWS Secrets Manager or HashiCorp Vault. Never commit credentials, API keys, or sensitive values to your repository.
Always use a remote backend with state locking enabled. This prevents concurrent modifications and ensures state consistency when multiple pipeline runs occur.
For production environments, use GitHub's environment protection rules to require manual approval before terraform apply runs:
environment:
name: production
url: https://console.aws.amazon.comAlways pin your Terraform and provider versions to avoid unexpected changes:
terraform {
required_version = "~> 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.30"
}
}
}While workspaces can be useful, prefer separate state files per environment for better isolation and security in CI/CD pipelines.
Terraform operations on large infrastructures can take a long time. Increase the job timeout:
jobs:
terraform:
timeout-minutes: 60If a pipeline fails mid-apply, the state might remain locked. Use terraform force-unlock <LOCK_ID> carefully, or configure automatic lock expiration on your DynamoDB table.
GitHub has a 65,536 character limit for PR comments. Truncate the plan output or upload it as an artifact instead.
Learn by doing with interactive courses on CopyPasteLearn:
Integrating Terraform with GitHub Actions creates a robust, automated infrastructure deployment pipeline that enforces code review, security scanning, and consistent deployments across environments. By following the patterns and best practices outlined in this guide, you can build a CI/CD workflow that scales with your infrastructure needs while maintaining security and reliability.
Start with the basic workflow and gradually add features like multi-environment support, security scanning, and PR comments as your team's needs evolve. The investment in automation pays dividends in reduced deployment errors and faster iteration cycles.
Learn how to use Terraform data sources to query existing resources, look up AMIs, reference remote state, and build dynamic configurations. Complete.
Master multi-account AWS management with Terraform. Learn provider aliases, cross-account IAM roles, AWS Organizations integration, and production-ready.
Learn how to implement Terraform state locking with AWS DynamoDB to prevent concurrent modifications and state corruption. Complete setup guide with examples.
Master Terraform version constraints for Terraform core and providers. Covers operators, lock files, required_version, required_providers, and upgrade...