Reusable GitHub Actions workflows and composite actions for Terraform CI/CD pipelines. This repository contains the automation logic — it does not contain any Terraform modules or infrastructure configuration.
infra-github-actions/
├── .github/workflows/
│ └── cd.yml # Reusable workflow (workflow_call)
└── actions/
├── plan/
│ └── action.yml # Composite action: init → plan → upload artifact
└── apply/
└── action.yml # Composite action: download artifact → init → apply
infra-live (issue opened)
└─ .github/workflows/cd.yml (triggers on cd-terraform-standalone label)
└─ calls infra-github-actions/.github/workflows/cd.yml (reusable)
├─ parse-inputs — extracts issue form fields
├─ plan — terraform init + plan, uploads plan artifact
├─ apply — downloads artifact, terraform apply
└─ finalize — comments on issue, closes on success
Plan and Apply run as separate jobs on separate runners. The plan output is passed via a GitHub Actions Artifact, ensuring the exact same plan is applied that was reviewed.
Called via workflow_call from any infrastructure repository:
# In your infra repo: .github/workflows/cd.yml
name: CD Terraform Standalone
on:
issues:
types: [opened, labeled]
jobs:
deploy:
if: contains(github.event.issue.labels.*.name, 'cd-terraform-standalone')
uses: imran2191/infra-github-actions/.github/workflows/cd.yml@main
secrets: inherit| Job | Purpose | Runs On |
|---|---|---|
| parse-inputs | Parses GitHub Issue body into structured outputs (environment, branch, module, command, mode) | ubuntu-latest |
| plan | Checks out code, runs actions/plan composite action |
ubuntu-latest |
| apply | Checks out code, downloads plan artifact, runs actions/apply composite action. Skipped when plan-only=true |
ubuntu-latest |
| finalize | Comments deployment status on the issue. Closes issue on success | ubuntu-latest |
The apply job targets a GitHub Environment based on the wait-for-approval input:
| wait-for-approval | Environment Used | Behavior |
|---|---|---|
false |
{env}-gate-auto-approved |
No manual approval needed |
true |
{env}-gate |
Requires configured reviewers to approve |
Runs terraform init and plan, then uploads the plan as a GitHub Actions Artifact.
| Input | Required | Description |
|---|---|---|
module-path |
yes | Path to the module directory |
terraform-mode |
yes | createUpdate or destroy |
environment |
yes | Target environment name |
| Output | Description |
|---|---|
artifact-name |
Name of the uploaded plan artifact |
plan-exit-code |
Exit code from the plan command |
Steps: Setup Terraform → Init → Plan → Generate JSON → Upload Artifact → Summary
Downloads the plan artifact and runs terraform apply.
| Input | Required | Description |
|---|---|---|
module-path |
yes | Path to the module directory |
terraform-mode |
yes | createUpdate or destroy |
artifact-name |
yes | Artifact name from the plan job |
environment |
yes | Target environment name |
Steps: Setup Terraform → Init → Download Artifact → Move Plan → Apply → Summary
Plan files are stored as GitHub Actions Artifacts (not in the Git repo or Artifactory):
| Property | Value |
|---|---|
| Naming | {env}-{module-path}-tf.plan-{run_id}-{run_number} (slashes replaced with dashes) |
| Contents | tf.plan (binary) + tfplan.json (JSON for scanning) |
| Retention | 5 days |
| Storage | GitHub-managed Azure Blob Storage |
For any infrastructure repository that calls this workflow:
- GitHub Environments — create
{env},{env}-gate,{env}-gate-auto-approvedper target - AWS Credentials — configure via OIDC (
id-token: write) or environment secrets - S3 State Bucket — for Terraform remote state
- DynamoDB Lock Table — for state locking
This repository is fully self-contained. It uses only:
actions/checkout@v4actions/upload-artifact@v4actions/download-artifact@v4actions/github-script@v7hashicorp/setup-terraform@v3