Reusable GitHub Actions and callable workflows for Terraform — plan, apply, validate, security scan, and multi-environment deployments.
A single source of truth for your Terraform CI/CD platform. Drop one uses: line into any repository and get a production-grade pipeline instantly.
Atomic building blocks. Use them individually to compose custom pipelines.
| Action | Description |
|---|---|
terraform-install |
Installs a pinned Terraform version via the official HashiCorp action |
terraform-init |
terraform init with optional S3 backend config via inputs |
terraform-validate |
fmt -check + validate — reports both results before failing |
terraform-plan |
Plan with -detailed-exitcode, saves plan file, posts formatted PR comment |
terraform-apply |
Applies a saved plan file — deterministic, no drift from what was reviewed |
terraform-destroy |
Destroy with a confirm='destroy' safety guard |
tflint |
TFLint with configurable rules or a .tflint.hcl config file |
terraform-docs |
Generates docs via terraform-docs; optionally commits updated README |
Full pipelines. Call with a single uses: line from any repository.
| Workflow | Description |
|---|---|
terraform-qa |
Quality gate: fmt → validate → tflint → Checkov. No AWS credentials required. |
terraform-deploy |
Full plan + apply with OIDC auth, PR comment, artifact handoff, and apply gate |
terraform-multi-env |
Matrix plan across N environments in parallel; sequential apply dev → staging → prod |
# .github/workflows/terraform-ci.yml
on:
pull_request:
paths: ["terraform/**"]
jobs:
qa:
uses: MujahidAbdul/github-actions-terraform-platform/.github/workflows/terraform-qa.yml@main
with:
working_directory: terraform
terraform_version: "1.8.0"
secrets:
github_token: ${{ secrets.GITHUB_TOKEN }}# .github/workflows/deploy.yml
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
qa:
uses: MujahidAbdul/github-actions-terraform-platform/.github/workflows/terraform-qa.yml@main
with:
working_directory: terraform
deploy:
needs: qa
uses: MujahidAbdul/github-actions-terraform-platform/.github/workflows/terraform-deploy.yml@main
with:
environment: prod
working_directory: terraform
aws_region: eu-west-2
backend_config: |
bucket=my-tfstate-bucket
key=prod/terraform.tfstate
region=eu-west-2
dynamodb_table=terraform-state-lock
secrets:
aws_role_arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
github_token: ${{ secrets.GITHUB_TOKEN }}# .github/workflows/multi-env.yml
on:
push:
branches: [main]
jobs:
deploy:
uses: MujahidAbdul/github-actions-terraform-platform/.github/workflows/terraform-multi-env.yml@main
with:
environments: '["dev","staging","prod"]'
working_directory_prefix: environments
terraform_version: "1.8.0"
backend_config_template: |
bucket=my-tfstate-bucket
key={env}/terraform.tfstate
region=eu-west-2
secrets:
aws_role_arn_dev: ${{ secrets.AWS_ROLE_DEV }}
aws_role_arn_staging: ${{ secrets.AWS_ROLE_STAGING }}
aws_role_arn_prod: ${{ secrets.AWS_ROLE_PROD }}
github_token: ${{ secrets.GITHUB_TOKEN }}See examples/ for complete copy-paste pipelines with directory structure guidance.
All deploy workflows use OIDC (OpenID Connect) — no long-lived AWS access keys stored as secrets.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<account-id>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<org>/<repo>:environment:<env>"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket",
"dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"
],
"Resource": [
"arn:aws:s3:::my-tfstate-bucket/*",
"arn:aws:s3:::my-tfstate-bucket",
"arn:aws:dynamodb:eu-west-2:<account-id>:table/terraform-state-lock"
]
}
]
}Add your resource-specific permissions on top of this backend minimum.
Pin to a tag in production — never use @main in a real pipeline:
uses: MujahidAbdul/github-actions-terraform-platform/.github/workflows/terraform-deploy.yml@v1.0.0Tags follow Semantic Versioning. Breaking input changes increment the major version.
github-actions-terraform-platform/
├── actions/
│ ├── terraform-install/ action.yml
│ ├── terraform-init/ action.yml
│ ├── terraform-validate/ action.yml
│ ├── terraform-plan/ action.yml
│ ├── terraform-apply/ action.yml
│ ├── terraform-destroy/ action.yml
│ ├── tflint/ action.yml
│ └── terraform-docs/ action.yml
├── workflows/
│ ├── terraform-qa.yml
│ ├── terraform-deploy.yml
│ └── terraform-multi-env.yml
├── examples/
│ ├── simple-pipeline/
│ └── multi-env-pipeline/
├── .github/workflows/ci.yml
├── .pre-commit-config.yaml
├── CONTRIBUTING.md
└── LICENSE
Composable over monolithic. Every composite action does one thing. The callable workflows compose them — but you can compose them differently if your pipeline needs it.
No secrets in files. Backend config and AWS role ARNs are always passed as inputs or secrets, never committed. OIDC eliminates stored credentials entirely.
Deterministic apply. The plan file is always saved as an artifact and passed explicitly to apply. There is no "plan again on apply" pattern — what you reviewed is exactly what gets applied.
Environment isolation. Each environment maps to a separate IAM role and a separate GitHub environment. A compromised dev pipeline cannot touch prod state.
DRY across repos. One update to this repo propagates to every consumer on the next run. No copy-paste drift across dozens of pipeline files.
| Tool | Minimum version |
|---|---|
| GitHub Actions runner | ubuntu-latest (ubuntu-22.04+) |
| Terraform | >= 1.5.0 |
| TFLint | >= 0.50.0 |
| terraform-docs | >= 0.18.0 |
See CONTRIBUTING.md.
MIT © 2026 Mujahid Abdul