Skip to main content
🎤 Luca Berton is speaking at Red Hat Summit & KubeCon EU 2026!Learn more →
Back to Blog

GitHub Actions CI/CD for Terraform

Automate Terraform with GitHub Actions. Plan on PR, apply on merge, remote state locking, and secure secrets for IaC pipelines.

Luca BertonApril 13, 20261 min read

The Workflow

Plan on every pull request. Apply only when merged to main. This gives you code review for infrastructure changes.

yaml
name: Terraform
on:
  pull_request:
    paths: ['infra/**']
  push:
    branches: [main]
    paths: ['infra/**']

permissions:
  contents: read
  pull-requests: write

jobs:
  terraform:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: infra

    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.8

      - name: Terraform Init
        run: terraform init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Terraform Format Check
        run: terraform fmt -check

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color -out=tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Comment Plan on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const output = `#### Terraform Plan
            \`\`\`
            ${{ steps.plan.outputs.stdout }}
            \`\`\``;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            });

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Required Secrets

Add these in GitHub → Settings → Secrets:

  • AWS_ACCESS_KEY_ID — IAM user with deployment permissions
  • AWS_SECRET_ACCESS_KEY — corresponding secret key

For other clouds, use the equivalent credentials (GCP service account JSON, Azure service principal).

Related Course

Master this topic with hands-on labs

Go beyond reading — build real projects in sandboxed environments with expert video guidance.

Browse Courses →

Remote State Backend

Store state in S3 with DynamoDB locking:

hcl
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

The CI runner reads and writes state through the same backend. DynamoDB prevents concurrent applies.

Multi-Environment

Use workspaces or matrix strategy:

yaml
jobs:
  terraform:
    strategy:
      matrix:
        environment: [dev, staging, prod]
    steps:
      - name: Terraform Plan
        run: |
          terraform workspace select ${{ matrix.environment }} || \
          terraform workspace new ${{ matrix.environment }}
          terraform plan -var-file="envs/${{ matrix.environment }}.tfvars"
Stay Updated

Get weekly IT automation tips

Docker, Ansible, Terraform, MLOps — curated insights delivered to your inbox. No spam.

Subscribe Free →

Drift Detection

Schedule a daily plan to detect manual changes:

yaml
on:
  schedule:
    - cron: '0 8 * * *'

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - name: Check for drift
        run: |
          terraform plan -detailed-exitcode -no-color || echo "DRIFT DETECTED"

Exit code 2 means changes detected — pipe this to Slack or email.

Security Tips

  • Never store state locally in CI — always use a remote backend
  • Use OIDC instead of static credentials when possible
  • Limit IAM permissions to only what Terraform needs
  • Pin provider versions to avoid surprise breaking changes
  • Review plans before merging — automated apply trusts your review process

---

Ready to go deeper? Check out our hands-on course: Terraform for Beginners — practical exercises you can follow along on your own machine.

Ready to learn by doing?

Stop reading tutorials — start building. Expert video courses with hands-on labs in real sandboxed environments.

Share this article
LB
Luca Berton

Docker Captain, IT automation expert, Red Hat Summit & KubeCon speaker. Building hands-on education for DevOps engineers at CopyPasteLearn.

Related Articles

Explore topics

Browse more articles on the topics covered here.