Automating Terraform with GitHub Actions

Vasudha JhaVasudha Jha
3 min read

When working with Infrastructure as Code (IaC), the goal is to automate everything to reduce manual intervention, ensure consistency, and eliminate human errors. Terraform already gives us declarative infrastructure management, but without a CI/CD pipeline, we still rely on manually running terraform plan and terraform apply, which leaves room for mistakes.

So, I decided to integrate Terraform into a GitHub Actions CI/CD pipeline to make it truly automated. While doing this, I also wanted an easy way to destroy the environment when I no longer needed it without manually running terraform destroy.

I know, I know. Terraform destroy in a CI/CD pipeline sounds dangerous. If misconfigured, it could wipe out production infrastructure in one bad push. Not ideal. But in my case, I was experimenting with hosting a static website on AWS using S3 and CloudFront, and I needed an automated way to clean up environments when I was done.

So, here's how I automated Terraform deployments AND controlled when to destroy my environment safely.


Step 1: Detecting Infrastructure Changes

All my infrastructure code lived in an infrastructure/ folder, so I needed my pipeline to run Terraform only when changes were made to this folder. Instead of running Terraform on every push, I used the dorny/path-filter GitHub Action. This action checks which files changed in a push and sets an output variable indicating whether Terraform should run:

- name: Detect changed files
  id: changes
  uses: dorny/paths-filter@v3
  with:
    filters: |
      infrastructure:
        - "infrastructure/**"

Now, Terraform only runs when something changes inside the infrastructure/ folder.


Step 2: Deciding Whether to Apply or Destroy Infrastructure

I wanted two ways to trigger Terraform:

  1. Apply changes if something was modified in infrastructure/.

  2. Destroy infrastructure if I explicitly requested it (without running Terraform manually).

To do this, I added a step that sets an environment variable (TF_RUN) if Terraform should execute:

- name: Set Terraform Execution Flag
  id: set-flag
  run: |
    if [[ "${{ steps.changes.outputs.infrastructure }}" == "true" || "${{ contains(github.event.head_commit.message, 'destroy infra') }}" == "true" ]]; then
      echo "TF_RUN=true" >> $GITHUB_ENV
    else
      echo "TF_RUN=false" >> $GITHUB_ENV
    fi

This way Terraform will only run if something changed in infrastructure/ or I pushed a commit with the message destroy infra. If neither condition is met, Terraform does nothing, preventing unnecessary runs.


Step 3: Running Terraform Based on the Flag

I then used TF_RUN in every Terraform-related step to ensure that it only runs when necessary:

- name: Setup Terraform
  if: env.TF_RUN == 'true'
  uses: hashicorp/setup-terraform@v3.1.2
  with:
    terraform_version: "1.10.5"

- name: Initialize Terraform
  if: env.TF_RUN == 'true'
  working-directory: infrastructure
  run: terraform init -input=false

This way, terraform won’t initialize unless needed and no random Terraform runs will happen if infra wasn’t changed.


Step 4: Handling terraform apply and terraform destroy Safely

For Terraform apply, I added an extra condition to ensure it doesn’t run if the commit message requests a destroy:

- name: Terraform Apply
  if: env.TF_RUN == 'true' && !contains(github.event.head_commit.message, 'destroy infra')
  working-directory: infrastructure
  run: terraform apply tfplan

And for destroy, I made sure it only runs if explicitly triggered:

- name: Terraform Destroy
  if: contains(github.event.head_commit.message, 'destroy infra')
  working-directory: infrastructure
  run: terraform destroy --auto-approve

Wrapping up

This setup fully automates Terraform while keeping control over when infrastructure is applied or destroyed. I no longer have to manually run Terraform, just commit my changes, push, and let GitHub Actions handle it.

Here’s the complete GitHub Actions YAML if you’re interested: [terraform-ci-cd.yml]

What do you think? This setup worked well for me, but I know there are many ways to automate Terraform, specifically terraform destroy as evident through this reddit post. How do you handle Terraform CI/CD? Do you automate Terraform destroy, or do you prefer a different approach? Let me know!


0
Subscribe to my newsletter

Read articles from Vasudha Jha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vasudha Jha
Vasudha Jha

I love solving problems at the intersection of software development and cloud infrastructure. My journey started as a full-stack developer, building web and mobile applications, but I found myself drawn to automation, cloud scalability, and making deployments smoother. That led me to DevOps and Cloud Engineering, where I now focus on building reliable infrastructure, optimizing workflows, and automating deployments. Right now, I’m hands-on with AWS, Terraform, CI/CD pipelines, Docker and Ansible, working on projects that deepen my understanding of cloud automation and scalable infrastructure. I have found that the best way to learn is by building real things, debugging, and iterating along the way. 🔧 What I’m Currently Working On AWS Cloud Resume Challenge: Setting up a fully automated, serverless personal website on AWS. The core AWS services I'm using include S3, IAM, CloudFront, API Gateway, Lambda, and DynamoDB. I’m writing the infrastructure using Terraform and deploying code through GitHub Actions to ensure an automated, infrastructure-as-code approach. Here's the link to it if you'd like to give it a go: https://cloudresumechallenge.dev/docs/the-challenge/aws/ If you're still here, we probably have similar interests! Let’s connect and geek out over DevOps, cloud, and automation or anything tech-related!