Automating Terraform with GitHub Actions

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:
Apply changes if something was modified in
infrastructure/
.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!
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!