---
name: aws-cicd-and-environments
description: >-
  Terraform CI/CD for AWS: gating apply behind review, scheduled drift
  detection, provisioning every environment from the same code path, secrets
  manager usage, and safe pipeline failure modes. Use when setting up or
  reviewing infrastructure CI/CD.
argument-hint: "[pipeline or environment]"
license: MIT
metadata:
  author: John Felix Lim
  version: "1.0.0"
---
# CI/CD & Environments

Apply when setting up or reviewing an infrastructure CI/CD pipeline. Confirm apply is gated by review and runs against the exact plan that was reviewed, not a plan re-generated at apply time.

## Rules

1. **`plan` and `apply` are different trust levels** — Anyone should be able to run `plan` — it's read-only — but `apply` against a real environment needs a gate: a required review, a protected branch, or a manual approval step. Automating straight through to `apply` on every push removes the last human checkpoint.
2. **Drift detection is not optional once more than one person can change infrastructure** — A manual console change diverges the real world from what Terraform thinks exists. Scheduled `terraform plan` runs that alert on unexpected diffs catch this before it causes a confusing failure later.
3. **Every environment should be provisioned from the same code path** — A staging environment hand-built once and never kept in sync with the Terraform that provisions prod stops being a reliable place to test infrastructure changes before they hit prod.
4. **Secrets belong in a secrets manager, referenced by the pipeline, never committed** — A hardcoded credential in a CI YAML file is in the repo's history forever, even after it's 'removed' in a later commit.
5. **A pipeline's failure mode should be safe, not silent** — A deploy step that fails partway through and leaves the environment in a half-updated state, with no rollback and no alert, turns a bad deploy into an incident discovered by users instead of monitoring.

## Patterns

### apply running unattended on every push

**Avoid** — There's no human checkpoint between 'someone pushed to main' and 'production infrastructure changed' — a typo in a Terraform file ships straight through.

```yaml
# .github/workflows/deploy.yml
on:
  push:
    branches: [main]
jobs:
  deploy:
    steps:
      - run: terraform apply -auto-approve
        # Every push to main applies to production, unreviewed.
```

**Prefer** — `plan` output is visible for review before anything is applied, and `apply` is a deliberate, gated action against a known plan.

```yaml
on:
  pull_request:
    branches: [main]
jobs:
  plan:
    steps:
      - run: terraform plan -out=tfplan
      - uses: actions/upload-artifact@v4
        with: { name: tfplan, path: tfplan }
# apply runs as a separate, manually-approved job against
# the exact plan that was reviewed in the PR.
```

### No drift detection

**Avoid** — The real infrastructure and Terraform's understanding of it have silently diverged, and the divergence surfaces as a confusing failure much later.

```yaml
# Someone opens the AWS console and bumps an instance's
# security group "just to unblock a demo," never updates
# the Terraform. Nobody finds out until the next unrelated
# apply tries to "fix" it and breaks something that was
# quietly relying on the manual change.
```

**Prefer** — Manual changes are caught within a day via a scheduled plan, not discovered months later during an unrelated deploy.

```yaml
# .github/workflows/drift-check.yml
on:
  schedule:
    - cron: "0 6 * * *"
jobs:
  drift:
    steps:
      - run: terraform plan -detailed-exitcode || notify-slack "Drift detected"
```

### Secrets committed to CI config

**Avoid** — The secret is in the repository's git history permanently, readable by anyone with repo access past or present, even after a later commit 'removes' it.

```yaml
# .github/workflows/deploy.yml
env:
  DATABASE_PASSWORD: "hunter2" # committed directly
```

**Prefer** — The actual value never enters the repository at all — only a reference to where the CI platform's secret store holds it.

```yaml
env:
  DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }} # from the CI secrets store
```

## Review checklist

- [ ] `apply` against a real environment requires a review gate (protected branch, required approval), not an unattended run on every push.
- [ ] Scheduled drift detection (`terraform plan` on a cron) alerts on unexpected divergence between real infrastructure and Terraform state.
- [ ] Every environment (dev, staging, prod) is provisioned from the same Terraform code path, not hand-built or manually diverged.
- [ ] No secret value appears in CI configuration files, committed or otherwise — all come from a secrets manager the pipeline references.
- [ ] Failed deploys leave the pipeline in a clearly failed, alerted state — not a silent partial success.
- [ ] The exact `plan` reviewed in a pull request is the one `apply` executes, not a fresh `plan` re-run at apply time.

---

From the John Felix Lim Frontend Engineering Playbook — https://github.com/JohnFelixLim