Skip to content
Back to skills

Terraform Ci

CSecurity

Terraform in CI/CD — plan on PR, apply on merge, OIDC auth, drift detection, importing existing resources, common CLI commands, anti-patterns, and Terraform vs Pulumi vs CDK decision guide.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 12, 2026
devopstypescriptpythongojavabashkubernetesawsgcpazureterraform

Works with

  • cli
  • api

Security analysis

C71/100
  • criticalPipes output to a shell interpreter
  • mediumUses curl or wget to download content
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill terraform-ci --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Terraform Ci?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Terraform Ci
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-terraform-ci/badge)](https://www.skillsdirectory.com/skills/aibot88-terraform-ci)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: terraform-ci
description: Terraform in CI/CD — plan on PR, apply on merge, OIDC auth, drift detection, importing existing resources, common CLI commands, anti-patterns, and Terraform vs Pulumi vs CDK decision guide.
---

# Terraform CI/CD & Operations

## When to Activate

- Setting up GitHub Actions (or other CI) to run `terraform plan` on pull requests
- Configuring OIDC-based AWS authentication for CI (no stored credentials)
- Importing existing cloud resources under Terraform control
- Deciding between Terraform, Pulumi, and AWS CDK for a new project
- Looking up common Terraform CLI commands
- Reviewing anti-patterns (local state, secrets in tfvars, count=0 to disable)

> For project structure, remote state setup, module design, workspace strategy, ECS/IAM patterns — see skill `terraform-patterns`.

## CI/CD Integration (GitHub Actions)

```yaml
# .github/workflows/terraform.yml
name: Terraform

on:
  pull_request:
    paths: ['infrastructure/**']
  push:
    branches: [main]
    paths: ['infrastructure/**']

jobs:
  plan:
    runs-on: ubuntu-latest
    permissions:
      id-token: write    # OIDC for AWS auth (no stored credentials)
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-terraform
          aws-region: eu-west-1

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: '~1.14'

      - name: Terraform Init
        run: terraform init
        working-directory: infrastructure/environments/prod

      - name: Terraform Validate
        run: terraform validate
        working-directory: infrastructure/environments/prod

      - name: Terraform Plan
        id: plan
        run: |
          terraform plan -var="db_password=${{ secrets.DB_PASSWORD }}" \
            -out=plan.tfplan -no-color 2>&1 | tee plan.txt
        working-directory: infrastructure/environments/prod

      - name: Comment Plan on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const plan = require('fs').readFileSync('infrastructure/environments/prod/plan.txt', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `\`\`\`hcl\n${plan.slice(0, 65000)}\n\`\`\``
            });

  apply:
    needs: plan
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production   # requires manual approval in GitHub
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-terraform
          aws-region: eu-west-1
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
        working-directory: infrastructure/environments/prod
      - run: terraform apply -auto-approve -var="db_password=${{ secrets.DB_PASSWORD }}"
        working-directory: infrastructure/environments/prod
```

---

## Importing Existing Resources

```bash
# Bring existing AWS resource under Terraform control
terraform import aws_s3_bucket.uploads my-existing-bucket-name
terraform import aws_security_group.rds sg-0abc123def456

# After import: run plan to check drift
terraform plan
# Fix .tf files until plan shows "No changes"
```

---

## Common Commands

```bash
terraform init          # download providers + configure backend
terraform validate      # check syntax and references
terraform fmt -recursive # format all .tf files
terraform plan          # preview changes (never modifies infra)
terraform apply         # apply the plan (prompts for confirmation)
terraform apply -auto-approve  # skip confirmation (CI only)
terraform destroy       # destroy everything (use with extreme care)
terraform state list    # list all managed resources
terraform state show aws_db_instance.main  # inspect state of one resource
terraform output        # print outputs
terraform graph | dot -Tpng > graph.png  # visualize dependency graph
```

---

## Anti-Patterns

| Anti-Pattern | Problem | Fix |
|---|---|---|
| Local state (no backend) | Lost on disk failure, no team sharing | S3 + DynamoDB locking from day 1 |
| Secrets in `.tfvars` committed to git | Credential exposure | Pass via CI env vars or Secrets Manager |
| `count = 0` to "disable" resources | Leaves dangling state | Remove the resource block + `terraform state rm` |
| Manual changes in console | State drift — next `plan` tries to revert | All changes through Terraform |
| No `deletion_protection` on prod DB | Accidental `destroy` deletes production data | Always set on prod databases |
| One giant `main.tf` | Impossible to navigate | Split by concern: `vpc.tf`, `rds.tf`, `ecs.tf` |
| `depends_on` overuse | Hides missing implicit references | Fix the reference chain instead |
| `ignore_changes` for everything | Terraform loses awareness of drift | Only ignore what autoscaling legitimately changes |

---

## Terraform vs. Pulumi vs. AWS CDK — When to Use What

Terraform is the right choice in many situations, but modern IaC alternatives (Pulumi, AWS CDK) offer advantages worth evaluating for new projects.

### Decision Matrix

| Criterion | Terraform HCL | Pulumi | AWS CDK |
|-----------|:-------------:|:------:|:-------:|
| Multi-cloud support | ✅ Best (3000+ providers) | ✅ Good | ❌ AWS only |
| Real programming language | ❌ HCL DSL | ✅ TS/Python/Go | ✅ TS/Python/Java |
| Unit testing infrastructure | ❌ | ✅ | ✅ |
| Type safety + IDE autocomplete | ❌ | ✅ | ✅ |
| Native loops & conditionals | ⚠️ `count`/`for_each` | ✅ | ✅ |
| Existing Terraform state migration | ✅ N/A | ⚠️ Via cdktf | ❌ |
| Provider ecosystem maturity | ✅ Largest | ✅ Uses TF providers | ⚠️ AWS-specific |
| Reusable abstractions | Modules | ComponentResource | L3 Constructs |
| Governance / policy | Sentinel / OPA | CrossGuard | CDK Aspects + OPA |
| Team HCL familiarity | ✅ | ❌ | ❌ |

### When to Stay with Terraform

- Existing large Terraform codebase — migration cost outweighs benefits
- Multi-cloud with providers not available in Pulumi (niche SaaS providers)
- Team is HCL-fluent and finds DSL simpler than TypeScript for infra
- Terraform Cloud governance (Sentinel policies, team VCS integration) is a requirement
- Working with providers only available via Terraform Registry

### When to Choose Pulumi

- New project and team writes TypeScript/Python/Go daily — same language for infra and app
- Need real unit tests for infrastructure code (`pulumi.runtime.setMocks`)
- Multi-cloud: deploy to AWS + GCP + Azure + Kubernetes in one program
- Need complex dynamic resource generation (lists of services, conditional resource graphs)
- CrossGuard policy enforcement in CI is a requirement

### When to Choose AWS CDK

- AWS-only infrastructure (no multi-cloud requirement)
- Team values strong defaults (L2 Constructs include encryption, proper IAM by default)
- Publishing reusable constructs to Construct Hub for other teams to consume
- Need CDK Pipelines for self-mutating CI/CD
- tighter AWS service integration and early access to new AWS services

### Hybrid Strategy: Terraform + Pulumi/CDK

For large organizations, a hybrid works well:
- **Terraform** manages foundational infrastructure: VPCs, accounts, DNS, IAM org-wide roles
- **Pulumi/CDK** manages application-specific resources: ECS services, databases, queues
- Pulumi/CDK reads Terraform outputs via remote state (`terraform_remote_state` or `pulumi.StackReference`)

```typescript
// Pulumi: read VPC from existing Terraform state
const tfState = new pulumi.StackReference("tf-networking/prod");
const vpcId = tfState.getOutput("vpc_id");

// Use VPC created by Terraform in a Pulumi resource
const service = new aws.ecs.Service("api", {
  networkConfiguration: {
    subnets: tfState.getOutput("private_subnet_ids").apply(ids => ids as string[]),
  },
});
```

### Migration Path: Terraform → Pulumi

If migrating an existing Terraform codebase:

1. Use `pulumi convert --from terraform` to auto-convert HCL to TypeScript/Python
2. Import existing state with `pulumi import` (matches existing resources without recreating)
3. Migrate module by module — keep Terraform for unmigrated modules, use Pulumi for new ones
4. Run `pulumi refresh` after import to align state with actual cloud resources

```bash
# Convert existing Terraform module to Pulumi TypeScript
pulumi convert --from terraform --language typescript ./infra/modules/vpc
```

---

## Infracost Cost Gate — Runnable CI Step

Add this step to your `plan` job (after `Terraform Plan`, before `Comment Plan on PR`) to block PRs that increase monthly cloud costs by more than 15%:

```yaml
      - name: Install Infracost
        run: |
          curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh
        env:
          INFRACOST_API_KEY: ${{ secrets.INFRACOST_API_KEY }}

      - name: Run Infracost diff
        id: infracost
        run: |
          infracost diff \
            --path infrastructure/environments/prod \
            --compare-to main \
            --format json \
            --out-file /tmp/infracost.json
          # Extract percentage change (positive = cost increase)
          PCT=$(jq '.diffTotalMonthlyCost | tonumber' /tmp/infracost.json 2>/dev/null || echo "0")
          echo "cost_delta_pct=$PCT" >> "$GITHUB_OUTPUT"
        working-directory: .

      - name: Enforce cost threshold
        run: |
          DELTA="${{ steps.infracost.outputs.cost_delta_pct }}"
          echo "Monthly cost delta: $DELTA USD"
          # Block if increase > $500/month; tag PR for cost-review if > $50
          if (( $(echo "$DELTA > 500" | bc -l) )); then
            echo "::error::Monthly cost increase \$$DELTA exceeds \$500 threshold — add 'cost-approved' label to override"
            exit 1
          elif (( $(echo "$DELTA > 50" | bc -l) )); then
            gh pr edit "${{ github.event.pull_request.number }}" --add-label "cost-review"
            echo "::warning::Monthly cost increase \$$DELTA — tagged for cost-review"
          fi
```

**Prerequisites:** `INFRACOST_API_KEY` secret set in GitHub repo settings (free at infracost.io).

---

## Related Skills

- `iac-modern-patterns` — Pulumi TypeScript, AWS CDK L1/L2/L3, Bicep, cdktf — full reference for non-HCL IaC
- `kubernetes-patterns` — Kubernetes resource management (Terraform can provision clusters, Helm provider manages workloads)
- `devsecops-patterns` — Checkov, OPA/Conftest for IaC compliance scanning in CI
- `ci-cd-patterns` — GitHub Actions integration for Terraform plan/apply pipelines

Files in this skill

  • SKILL.md10.7 KB
  • source.json693 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.