Using Terraform with CI/CD
1. Integrating with GitHub Actions
| Step | Description | Action |
|---|---|---|
| 1 | Set up a GitHub repository with Terraform configuration files | terraform init |
| 2 | Configure GitHub Secrets for credentials (e.g., AWS access key) | Repository Settings > Secrets and Variables |
| 3 | Create a GitHub Actions workflow file (e.g., .github/workflows/terraform.yml) |
YAML configuration in .github/workflows |
| 4 | Use the Terraform GitHub Action in your workflow | hashicorp/setup-terraform |
Example: GitHub Actions Workflow
name: "Terraform"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
run: terraform apply tfplan
Note: Ensure that sensitive variables like AWS credentials are stored as GitHub Secrets and not hardcoded in workflows.
Warning: Always review the plan output before applying changes in a production environment.
Best Practices for CI/CD Integration
- Use
terraform planbefore applying changes to prevent unintended modifications. - Store state files securely (e.g., in S3 with versioning and encryption).
- Use
terraform validatein workflows to catch configuration errors early. - Implement proper branching strategies (e.g., feature branches, main branch protection).
2. Integrating with GitLab CI
| Feature | Description | Usage |
|---|---|---|
| Terraform Cloud Integration | Use Terraform Cloud for remote operations and state management. | terraform cloud |
| GitLab CI Variables | Securely store and manage sensitive data using GitLab CI variables. | TF_VAR_<variable> |
| Terraform State Storage | Store state in remote backends like S3 or GCS for CI/CD. | backend "s3" |
Example: GitLab CI Pipeline Configuration
stages:
- validate
- plan
- apply
validate:
stage: validate
script:
- terraform init
- terraform validate
plan:
stage: plan
script:
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- tfplan
apply:
stage: apply
script:
- terraform init
- terraform apply tfplan
only:
- master
Note: Ensure that GitLab CI runners have appropriate permissions to access Terraform state and execute commands.
3. Integrating with Jenkins
| Property | Description | Example |
|---|---|---|
| Jenkins Plugin | Plugin to manage Terraform in Jenkins pipelines | terraform-plugin |
| Terraform Version | Version of Terraform to use in Jenkins | 1.9.5 |
| Workspace | Workspace to use for Terraform operations | production |
Example: Jenkins Pipeline Configuration
pipeline {
agent any
stages {
stage('Setup') {
steps {
sh 'terraform init'
}
}
stage('Plan') {
steps {
sh 'terraform plan -out=tfplan'
}
}
stage('Apply') {
steps {
sh 'terraform apply tfplan'
}
}
}
}
Note: Ensure Jenkins has appropriate permissions to access Terraform state and secrets.
Comparison: Jenkins vs Other CI/CD Tools
- Jenkins: Extensible with plugins, supports complex workflows
- GitHub Actions: Integrated with GitHub, simpler for basic pipelines
- GitLab CI: Built-in with GitLab, easy to configure
4. Integrating with Azure DevOps
| Feature | Description | Notes |
|---|---|---|
| Azure Pipeline YAML | Define CI/CD pipelines using YAML files in Azure Repos | Supports Terraform lifecycle operations |
| Terraform Cloud Integration | Use Terraform Cloud for remote operations and state management | Requires Azure DevOps integration setup |
| Service Connections | Securely connect to Azure resources from pipelines | Use service principals or managed identities |
Example: Azure Pipeline YAML for Terraform
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: TerraformTaskV2@2
inputs:
provider: 'azurerm'
command: 'init'
backendType: 'azurerm'
backendServiceArm: 'my-azure-service-connection'
backendSettings: |
resource_group_name = "tfstate-rg"
storage_account_name = "tfstatestorage"
container_name = "tfstatecontainer"
key = "terraform.tfstate"
- task: TerraformTaskV2@2
inputs:
provider: 'azurerm'
command: 'apply'
environmentServiceNameARM: 'my-azure-service-connection'
Note: Ensure the Azure DevOps service connection has appropriate permissions to manage resources and access the Terraform backend storage.
Key Steps for Integration
- Create an Azure DevOps project and repository
- Set up a service connection to Azure with necessary permissions
- Configure Terraform backend to use Azure Storage Account
- Define pipeline YAML with Terraform tasks for init, plan, and apply
5. Automating Terraform Init
| Option | Values | Notes |
|---|---|---|
| terraform init | terraform init |
Initialize a Terraform working directory |
| -reconfigure | -reconfigure |
Reconfigure the backend and providers |
| -upgrade | -upgrade |
Upgrade to latest provider versions |
Example: Automating terraform init in a CI pipeline
#!/bin/bash
set -e
echo "Initializing Terraform..."
terraform init \
-reconfigure \
-upgrade \
-backend-config="bucket=terraform-state-bucket" \
-backend-config="key=prod/terraform.tfstate"
Note: Ensure that the CI environment has appropriate credentials and permissions to access the backend storage (e.g., AWS S3, GCS).
6. Automating Plan and Apply
Example: CI/CD Pipeline with Terraform Plan and Apply
name: Terraform CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan
env:
TF_VAR_environment: ${{ github.ref_name }}
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
env:
TF_VAR_environment: ${{ github.ref_name }}
| Step | Description | Action |
|---|---|---|
| Checkout | Fetches the code from the repository | actions/checkout@v4 |
| Setup Terraform | Installs the specified version of Terraform | hashicorp/setup-terraform@v3 |
| Terraform Init | Initializes the Terraform working directory | terraform init |
| Terraform Plan | Generates an execution plan and saves it to a file | terraform plan -out=tfplan |
| Terraform Apply | Applies the changes from the plan file | terraform apply -auto-approve tfplan |
Note: Using
-auto-approve in CI/CD pipelines should be done carefully and only in trusted environments.Warning: Ensure that sensitive variables (like API keys) are not exposed in logs or outputs.
Best Practices for Automating Terraform Plan and Apply
- Use
-out=tfplanto store the plan as a file for review and approval before applying. - Implement
-auto-approvecautiously, preferably in environments with strict access control. - Validate changes using
terraform planbefore applying them in production. - Use environment-specific variables to manage different configurations (e.g., dev, staging, prod).
- Ensure that CI/CD pipelines are configured to run only on specific branches or tags.
7. Storing State Remotely
Storing Terraform state remotely is essential for collaboration, security, and consistency in production environments. Remote state allows multiple team members to work with the same infrastructure while maintaining a single source of truth.
| Backend | Description | Notes |
|---|---|---|
| local | Stores state in a local file (terraform.tfstate) |
Not recommended for team collaboration or production use |
| remote | Stores state in a remote backend (e.g., AWS S3, Azure Blob Storage) | Enables shared state and locking for concurrent operations |
| s3 | Uses AWS S3 for state storage and DynamoDB for locking | Highly recommended for production environments |
| azurerm | Uses Azure Blob Storage for state and Table Storage for locking | Recommended for Azure-based infrastructures |
| gcs | Uses Google Cloud Storage for state and Firestore for locking | Recommended for GCP-based infrastructures |
Example: Configuring S3 Backend
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
}
}
Note: Ensure that the IAM role or user used for Terraform has appropriate permissions to read and write to the S3 bucket and DynamoDB table.
Warning: Never store sensitive backend configuration in version control. Use environment variables or encrypted secrets for sensitive values.
8. Managing Secrets
| Method | Description | Notes |
|---|---|---|
| Terraform Variables | Use variable blocks with sensitive = true |
Secrets are not logged in plan/apply output |
| Environment Variables | Use TF_VAR_ prefix in CI/CD |
Securely pass secrets to Terraform |
| Vault Integration | Use vault_generic_secret data source |
Store secrets in HashiCorp Vault |
| AWS Secrets Manager | Use aws_secretsmanager_secret resource |
Store secrets in AWS Secrets Manager |
Example: Using Sensitive Variables
variable "api_key" {
description = "API key for external service"
sensitive = true
}
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
tags = {
Name = "web-server"
}
}
Note: Never store secrets in plain text Terraform files or version control.
Warning: Ensure CI/CD systems properly handle secret exposure in logs or output.
9. Using Terraform Cloud Runs
| Field | Description | Values |
|---|---|---|
| terraform_cloud.runs | Enables Terraform Cloud runs integration | true |
| terraform_cloud.workspace | Name of the Terraform Cloud workspace to use | "my-workspace" |
| terraform_cloud.token | API token for Terraform Cloud access | "tfp.xxxxxxxxxx" |
Example: Configuring Terraform Cloud Runs
terraform {
cloud {
organization = "myorg"
workspaces {
name = "my-workspace"
}
}
}
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
Note: Ensure your Terraform Cloud token has appropriate permissions for the workspace.
Warning: Terraform Cloud runs require a valid
organization and workspace name.
10. Implementing GitOps Workflows
| Component | Description | Notes |
|---|---|---|
| Git Repository | Source of truth for infrastructure as code (IaC) | Must be version-controlled with history |
| Terraform State | Tracks current infrastructure state and configuration | Should be stored remotely (e.g., S3, Azure Blob) |
| CI/CD Pipeline | Automates deployment and validation of infrastructure | Can be implemented using GitHub Actions, GitLab CI, Jenkins |
| Branch Protection Rules | Prevents direct pushes to main/master branches | Enforces pull request reviews before merging |
Example: GitHub Actions Workflow
name: "Terraform CI/CD"
on:
push:
branches: [ "main" ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
run: terraform apply tfplan
Note: Ensure that sensitive variables (e.g., AWS credentials) are stored securely in GitHub Secrets or similar.