TL;DR
Learn how to automate Terraform deployments using CI/CD pipelines
Terraform Automation with CI/CD Pipelines
Learn how to automate your infrastructure deployments by integrating Terraform with popular CI/CD platforms.
$1
1. Consistent deployments
2. Reduced human error
3. Automated testing
4. Version control integration
5. Audit trail
$1
1. Version Control
2. Testing
3. Plan Generation
4. Approval Gates
5. Deployment
6. Validation
$1
`` name: Terraform CI/CD on:
push:
branches: [ main ]
pull_request:
branches: [ main ] jobs:
terraform:
runs-on: ubuntu-latest
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
TF_VAR_environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.0.0
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Upload Plan
uses: actions/upload-artifact@v3
with:
name: tfplan
path: tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan
yaml
`.github/workflows/terraform.yml
$1
` image: hashicorp/terraform:1.0.0 variables:
TF_VAR_environment: ${CI_COMMIT_REF_NAME}
cache:
paths:
- .terraform stages:
- validate
- plan
- apply before_script:
- terraform init validate:
stage: validate
script:
- terraform fmt -check
- terraform validate
plan:
stage: plan
script:
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
stage: apply
script:
- terraform apply -auto-approve tfplan
only:
- main
when: manual
yaml
`.gitlab-ci.yml
$1
` trigger:
- main pool:
vmImage: 'ubuntu-latest' variables:
- group: terraform-secrets stages:
jobs:
- job: ValidateAndPlan
steps:
- task: TerraformInstaller@0
inputs:
terraformVersion: '1.0.0'
- task: TerraformTaskV3@3
inputs:
provider: 'aws'
command: 'init'
- task: TerraformTaskV3@3
inputs:
provider: 'aws'
command: 'plan'
environmentServiceNameAWS: 'AWS-Connection'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: ApplyTerraform
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: TerraformTaskV3@3
inputs:
provider: 'aws'
command: 'apply'
environmentServiceNameAWS: 'AWS-Connection'
yaml
`azure-pipelines.yml
$1
` // Jenkinsfile
pipeline {
agent any
environment {
TF_VAR_environment = "${BRANCH_NAME}"
AWS_CREDENTIALS = credentials('aws-credentials')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Terraform Init') {
steps {
sh 'terraform init'
}
}
stage('Terraform Plan') {
steps {
sh 'terraform plan -out=tfplan'
}
}
stage('Approval') {
when {
branch 'main'
}
steps {
input message: 'Apply Terraform changes?'
}
}
stage('Terraform Apply') {
when {
branch 'main'
}
steps {
sh 'terraform apply -auto-approve tfplan'
}
}
}
post {
always {
cleanWs()
}
}
}
groovy
`
$1
` version: 2.1 orbs:
terraform: circleci/terraform@3.0.0 workflows:
version: 2
terraform:
jobs:
- terraform/fmt:
checkout: true
context: terraform
- terraform/validate:
checkout: true
context: terraform
requires:
- terraform/fmt
- terraform/plan:
checkout: true
context: terraform
requires:
- terraform/validate
- terraform/apply:
checkout: true
context: terraform
filters:
branches:
only: main
requires:
- terraform/plan
yaml
`.circleci/config.yml
$1
$1
` terraform {
backend "s3" {
bucket = "terraform-state"
key = "env/${var.environment}/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
hcl
`backend.tf
$1
` variable "environment" {
type = string
description = "Environment name (dev/staging/prod)"
} environment = "dev"
region = "us-west-2"
hcl
`variables.tf
terraform.tfvars.example
$1
` terraform workspace new dev
terraform workspace new prod
terraform workspace select dev
bash
`Use workspaces for environment isolation
$1
$1
` env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
yaml
`GitHub Actions secrets
$1
` resource "aws_iam_policy" "terraform_ci" {
name = "terraform-ci-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "${aws_s3_bucket.terraform_state.arn}/*"
}
]
})
}
hcl
`IAM policy for CI/CD
$1
$1
` package test import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
) func TestTerraformBasicExample(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/basic",
Vars: map[string]interface{}{
"environment": "test",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
hcl
`test/main_test.go
$1
` package terraform deny[msg] {
resource := input.planned_values.root_module.resources[_]
not startswith(resource.values.tags.Name, "company-")
msg = sprintf("Resource %v must have company prefix in Name tag", [resource.address])
}
hcl
`policy/naming.rego
$1
$1
` uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
yaml
`GitHub Actions
$1
` // Jenkins
post {
success {
emailext subject: "Pipeline Successful",
body: "Terraform changes applied successfully",
to: "team@company.com"
}
failure {
emailext subject: "Pipeline Failed",
body: "Terraform pipeline failed. Please check logs.",
to: "team@company.com"
}
}
groovy
`
$1
` name: Terraform Drift Detection on:
schedule:
- cron: '0 0 * ' jobs:
detect-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Plan
run: |
terraform init
terraform plan -detailed-exitcode
continue-on-error: true
id: plan
- name: Notify on Drift
if: steps.plan.outcome == 'failure'
run: |
echo "Infrastructure drift detected!"
# Add notification logic
yaml
``Scheduled drift detection
$1
Automating Terraform with CI/CD:
Remember to:
1. Use version control
2. Implement proper testing
3. Secure sensitive data
4. Monitor deployments
5. Handle state properly
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.