How to Use Terraform with GitHub Actions - CICD Pipeline Guide
Set up Terraform CI-CD with GitHub Actions. Covers plan on PR, apply on merge, state locking, secrets management, and environment protection.
Terraform
Understand the Terraform required_providers block for version pinning. Covers source addresses, version constraints, lock files, and multi-provider configs.
The required_providers block declares which providers your config needs, where to download them, and which versions are compatible. Always pin versions to avoid unexpected breaking changes.
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}# Format: namespace/provider
# Registry: registry.terraform.io (default)
required_providers {
# HashiCorp providers
aws = { source = "hashicorp/aws" }
azurerm = { source = "hashicorp/azurerm" }
google = { source = "hashicorp/google" }
# Third-party providers
datadog = { source = "DataDog/datadog" }
cloudflare = { source = "cloudflare/cloudflare" }
mongodb = { source = "mongodb/mongodbatlas" }
# Custom/private registry
internal = { source = "company.example.com/team/internal" }
}| Constraint | Meaning | Example |
|---|---|---|
= 5.40.0 | Exact version | Only 5.40.0 |
>= 5.40 | Minimum version | 5.40 or higher |
~> 5.40 | Pessimistic (recommended) | >= 5.40.0, < 6.0.0 |
~> 5.40.0 | Patch only | >= 5.40.0, < 5.41.0 |
>= 5.30, < 6.0 | Range | Between 5.30 and 5.99 |
# Recommended: pessimistic constraint
aws = {
source = "hashicorp/aws"
version = "~> 5.40" # Allows 5.40.x, 5.41.x, ... but not 6.0
}
# Strict pinning (for reproducibility)
aws = {
source = "hashicorp/aws"
version = "= 5.40.0"
}# Generated by terraform init
# Commit this file to version control!
terraform init # Creates/updates lock file
terraform init -upgrade # Upgrades within version constraints# .terraform.lock.hcl (auto-generated)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.40.0"
constraints = "~> 5.40"
hashes = [
"h1:abc123...",
"zh:def456...",
]
}Always commit .terraform.lock.hcl — it ensures everyone uses the exact same provider version.
Child modules should declare required_providers but not pin exact versions — let the root module control:
# modules/networking/versions.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0" # Minimum, not exact
}
}
}terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
resource "aws_s3_bucket" "east" {
bucket = "my-bucket-east"
}
resource "aws_s3_bucket" "west" {
provider = aws.west
bucket = "my-bucket-west"
}| Mistake | Fix |
|---|---|
| No version constraint | Always add version = "~> X.Y" |
| Not committing lock file | Add .terraform.lock.hcl to git |
| Exact versions in modules | Use >= in modules, ~> in root |
Missing source | Required since Terraform 0.13 |
Always declare required_providers with source and version constraints. Use ~> for the root module, >= for reusable modules, commit the lock file, and run terraform init -upgrade when you want newer versions.
Set up Terraform CI-CD with GitHub Actions. Covers plan on PR, apply on merge, state locking, secrets management, and environment protection.
Master Terraform workspaces for managing dev, staging, and production environments. Covers workspace commands, state isolation, and CI/CD integration.
Master Terraform string functions with practical examples. Covers format, join, split, replace, regex, trim, lower, upper, and template rendering.
Configure Terraform backends for remote state. Complete guide for S3 + DynamoDB, Azure Blob, GCS, Terraform Cloud, and Consul with encryption and locking.