(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisbon

How to create an Automated Testing environment with Terraform and GitHub Actions

João Barros 23 de August de 2026 4 min read

This tutorial shows how to create a pipeline that uses Terraform and GitHub Actions to spin up an ephemeral test environment and automatically tear it down. Useful to test infrastructure changes without polluting the Azure/AWS account and to reduce costs.

Prerequisites

  • Azure or AWS account with permissions to create resources (or a local environment like Docker for a simplified example).
  • GitHub repository with permissions for GitHub Actions.
  • Terraform installed locally to test and validate the files.
  • Basic knowledge of Terraform and GitHub Actions.

Step 1: Concept and file organization

Explains the why: an ephemeral environment is created per branch/pull request, used for testing and removed on merge/close. We will separate the Terraform configuration and the GitHub Actions workflow.

# Estrutura mínima do repositório
/
  ├─ terraform/
  │    ├─ main.tf
  │    ├─ variables.tf
  │    └─ outputs.tf
  └─ .github/workflows/terraform-testing.yml

Step 2: Minimal Terraform for example environment

Here is a simple example that creates a VM/instance or, to avoid cloud complexity, a local resource (e.g.: null_resource). Use this to validate create/destroy logic.

# terraform/main.tf
terraform {
  required_providers {
    null = {
      source  = "hashicorp/null"
      version = "~> 3.0"
    }
  }
}
provider "null" {}

resource "null_resource" "test_env" {
  triggers = {
    branch = var.branch_name
  }
}

output "env_id" {
  value = null_resource.test_env.id
}
# terraform/variables.tf
variable "branch_name" {
  type    = string
  default = "local"
}

Step 3: GitHub Actions workflow to create and destroy

The workflow runs terraform init/plan/apply when opening a pull request and runs destroy when the PR is closed or when the branch is removed. It uses a unique naming per branch.

# .github/workflows/terraform-testing.yml
name: Terraform Testing Env

on:
  pull_request:
    types: [opened, reopened, synchronize]
  pull_request_target:
    types: [closed]

jobs:
  apply:
    if: github.event_name == 'pull_request' && github.event.action != 'closed'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: '1.5.0'

      - name: Terraform Init
        working-directory: terraform
        run: terraform init -input=false

      - name: Terraform Apply
        working-directory: terraform
        env:
          TF_VAR_branch_name: ${{ github.head_ref }}
        run: terraform apply -auto-approve -input=false

  destroy:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: '1.5.0'

      - name: Terraform Init
        working-directory: terraform
        run: terraform init -input=false

      - name: Terraform Destroy
        working-directory: terraform
        env:
          TF_VAR_branch_name: ${{ github.head_ref }}
        run: terraform destroy -auto-approve -input=false

Step 4: State and secrets management

If using real cloud resources, never store state locally on the runner. Configure a remote backend (e.g.: Azure Storage, S3) and use GitHub secrets for credentials. Quick example for an S3 backend:

# Adicionar ao terraform/main.tf
terraform {
  backend "s3" {
    bucket = "my-terraform-state-bucket"
    key    = "envs/${var.branch_name}.tfstate"
    region = "eu-west-1"
  }
}

Step 5: Common errors and how to avoid them

Main issues: state conflicts when multiple runs use the same key, lack of permissions for the runner account, forgetting to destroy abandoned branches. Solutions: backend per branch, roles/minimal responsibility associations, periodic cleanup with a cron script.

Verify the result

When you open a pull request you will see the 'Terraform Testing Env' job run and apply the resources. Confirm the env_id in the job outputs. When closing the PR, verify that the destroy job ran and that the resource was removed (or that the corresponding key does not exist in the state backend).

Conclusion

You now have a basic flow to create ephemeral environments with Terraform and GitHub Actions: create on PR, test and destroy on close. Next steps: replace null_resource with real resources (VM, RDS, Storage), add automated tests that run against the environment and support state locks. Tip: start by testing locally with terraform plan and then simulate the runner using act to validate the workflow.