Scaffold terraform
Cross-platform dotfiles managed by Chezmoi with Homebrew/apt and per-language version managers. One-command bootstrap for macOS and Linux with Neovim, Tmux, Zsh, and AI agent skills.
npx -y skills add urmzd/dotfiles --skill scaffold-terraformAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Scaffold a Terraform infrastructure project with CI/CD (plan on PR, apply on push), AWS OIDC auth, .envrc, and standard files. Uses terraform CLI as the native tool. Loads on top of scaffold-project (run that first for cross-language standard files). Use when creating infrastructure repos, or when the user mentions "new Terraform project", "terraform init", "AWS infra", or "infrastructure scaffold". Do NOT use for Go, Rust, Python, or Node application projects; use the matching scaffold-<lang> skill.
SKILL.md
7.8 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
Scaffold Terraform Project
Generate a production-ready Terraform project following established CI/CD patterns. Read the scaffold-project skill first for standard files (README, AGENTS.md, LICENSE, CONTRIBUTING.md, llms.txt).
When to Use
- Creating a new infrastructure-as-code repository
- Adding CI/CD to an existing Terraform project
- Standardizing a Terraform project to match org conventions
Generated Files
.github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
id-token: write
pull-requests: write
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
jobs:
plan:
name: Terraform Plan
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.plan.outputs.has_changes }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
terraform_wrapper: false
- name: Terraform Init
run: terraform init
- name: Terraform Format Check
run: terraform fmt -check
continue-on-error: true
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
id: plan
run: |
terraform plan -no-color -out=tfplan -detailed-exitcode 2>&1 | tee plan_output.txt || exit_code=$?
if [ "${exit_code:-0}" -eq 2 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
if [ "${exit_code:-0}" -eq 1 ]; then
exit 1
fi
- name: Upload Plan
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: tfplan
path: tfplan
retention-days: 1
- name: Comment PR with Plan
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('plan_output.txt', 'utf8');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' && comment.body.includes('Terraform Plan')
);
const truncatedPlan = plan.length > 60000
? plan.substring(0, 60000) + '\n\n... (truncated)'
: plan;
const body = `## Terraform Plan\n<details>\n<summary>Click to expand plan output</summary>\n\n\`\`\`hcl\n${truncatedPlan}\n\`\`\`\n</details>\n\n**Workflow Run:** [View Details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`;
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner, repo: context.repo.repo,
comment_id: botComment.id, body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: context.issue.number, body
});
}
- name: Plan Summary
run: |
echo "## Terraform Plan" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat plan_output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
apply:
name: Terraform Apply
needs: plan
if: github.ref == 'refs/heads/main' && github.event_name == 'push' && needs.plan.outputs.has_changes == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Init
run: terraform init
- name: Download Plan
uses: actions/download-artifact@v4
with:
name: tfplan
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
- name: Terraform Output Summary
run: |
echo "## Terraform Apply Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Infrastructure has been updated successfully." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Outputs" >> $GITHUB_STEP_SUMMARY
terraform output -no-color >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "No outputs defined" >> $GITHUB_STEP_SUMMARY
No separate ci.yml + release.yml. Terraform uses a single terraform.yml with plan-on-PR and apply-on-push.
Common Commands
No justfile. Terraform CLI is the native tool:
terraform init # initialize
terraform fmt -recursive # format
terraform validate # validate
terraform plan # plan changes
terraform apply # apply changes
terraform output # show outputs
For multi-environment setups (dev/staging/prod workspaces, multiple state files), add a justfile to manage environment switching and orchestration.
.envrc
# terraform/kubectl/helm come from Homebrew/apt. Use this file for AWS/GCP env vars.
dotenv
Project Layout
main.tf # primary resources
variables.tf # input variables
outputs.tf # output values
providers.tf # provider configuration
backend.tf # state backend (S3)
terraform.tfvars # variable values (NOT committed if secrets)
Required GitHub Secrets
| Secret | Purpose |
|---|---|
AWS_ROLE_ARN | IAM role ARN for OIDC assumption |
AWS_REGION | AWS region (e.g., us-east-1) |
Gotchas
cancel-in-progress: falsenever cancel a running Terraform operationterraform_wrapper: falsein plan job to get raw exit codes (exit code 2 = changes detected)- Plan is saved as artifact and downloaded for apply. Ensures apply matches what was planned
- PR comments truncate at 60k chars to stay within GitHub's limits
- OIDC auth (
id-token: write+aws-actions/configure-aws-credentials). No long-lived AWS keys terraform fmt -checkusescontinue-on-error: true. Warns but doesn't block- No sr.yaml or semantic release. Infrastructure changes are applied directly, not versioned/tagged
- Single concurrency group prevents concurrent applies that could corrupt state
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.