
Terrashark
- 119 installs
- 307 repo stars
- Updated August 4, 2026
- lukasniessen/terrashark
terrashark is a Claude Code skill for ai & agent building.
About
terrashark is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- terrashark
- AI & Agent Building
- AI-coding skill
Terrashark by the numbers
- 119 all-time installs (skills.sh)
- +10 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #3,837 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lukasniessen/terrashark --skill terrasharkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 119 |
|---|---|
| repo stars | ★ 307 |
| Last updated | August 4, 2026 |
| Repository | lukasniessen/terrashark ↗ |
How do I helps with ai & agent building tasks during AI-assisted development.?
Helps with ai & agent building tasks during AI-assisted development.
Who is it for?
Best when you're working on ai & agent building and need structured help with terrashark.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks during AI-assisted development., or when terrashark is a claude code skill for ai & agent building.
What you get
Structured output aligned to terrashark: terrashark, AI & Agent Building.
Files
Terrashark: Failure-Mode Workflow for Terraform/OpenTofu
Run this workflow top to bottom.
1) Capture execution context
Record before writing code:
- runtime (
terraformortofu) and exact version - provider(s), target platform, and state backend
- execution path (local CLI, CI, HCP Terraform/TFE, Atlantis)
- environment criticality (dev/shared/prod)
If unknown, state assumptions explicitly.
2) Diagnose likely failure mode(s)
Select one or more based on user intent and risk:
- identity churn: resource addressing instability, refactor breakage
- secret exposure: secrets in state, logs, defaults, artifacts
- blast radius: oversized stacks, weak boundaries, unsafe applies
- CI drift: version mismatch, unreviewed applies, missing artifacts
- compliance gate gaps: missing policies/approvals/audit controls
3) Load only the relevant reference file(s)
Primary references:
references/identity-churn.mdreferences/secret-exposure.mdreferences/blast-radius.mdreferences/ci-drift.mdreferences/compliance-gates.md
Supplemental references (only when needed):
references/testing-matrix.mdreferences/quick-ops.mdreferences/examples-good.mdreferences/examples-bad.mdreferences/examples-neutral.mdreferences/coding-standards.mdreferences/module-architecture.mdreferences/ci-delivery-patterns.mdreferences/security-and-governance.mdreferences/do-dont-patterns.mdreferences/mcp-integration.md
Conditional references (CRR; load only on detected signals):
references/conditional/backend-state-safety.md(backend iss3,azurerm,gcs,remote,cloud,pg,consul, orlocal, or task mentions backend migration, locking, state backup, or restore)references/conditional/trusted-modules.md(provider isaws,azurerm,google,oci, oribm)
Do not load multiple conditional references unless the task spans multiple detected backends, providers, or tools.
4) Propose fix path with explicit risk controls
For each fix, include:
- why this addresses the failure mode
- what could still go wrong
- guardrails (tests, approvals, rollback)
5) Generate implementation artifacts
When applicable, output:
- HCL changes (typed vars, stable keys, bounded versions)
- migration blocks (
moved, import strategy) - CI pipeline updates (plan/apply separation, artifacts, policy checks)
- compliance controls (approvals, policy rules, evidence paths)
When a trusted registry module covers the requested resource and the user has not asked for raw HCL, default to that module with an exact version pin (see references/conditional/trusted-modules.md).
6) Validate before finalize
Always provide command sequence tailored to runtime and risk tier. Never recommend direct production apply without reviewed plan and approval.
7) Output contract
Return:
- assumptions and version floor
- selected failure mode(s)
- chosen remediation and tradeoffs
- validation/test plan
- rollback/recovery notes for destructive-impact changes
{
"name": "terrashark",
"description": "Practical Terraform and OpenTofu skill focused on structure, security, testing, and low-hallucination IaC generation.",
"owner": {
"name": "LukasNiessen"
},
"plugins": [
{
"name": "terrashark",
"source": "./",
"description": "Terraform/OpenTofu Guardrails",
"version": "2.3.0"
}
]
}
{
"permissions": {
"allow": [
"WebSearch"
]
}
}
* @LukasNiessen
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
Summary
- What changed?
- Why is this needed?
- Which failure mode(s) does this address?
Failure-Mode Coverage
Select all that apply:
- [ ]
identity-churn - [ ]
secret-exposure - [ ]
blast-radius - [ ]
ci-drift - [ ]
compliance-gates - [ ] Not applicable
Quality Impact
- Hallucination or error pattern reduced:
- Expected quality gain:
- Token-cost impact (higher/lower/neutral):
Validation Performed
- [ ] Ran markdown/link checks
- [ ] Checked SKILL frontmatter and file structure
- [ ] Verified all referenced files exist
- [ ] (If content change) sanity-checked examples for correctness
Commands / notes:
# Paste commands or rationale used for validationSafety Checklist
- [ ] No secrets or credentials added
- [ ] No contradictory guidance across files
- [ ] Runtime/version statements are internally consistent
- [ ] Examples are newly written and not copied from external repos
Reviewer Notes
Any context reviewers should know before approval.
name: Deploy Documentation to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
working-directory: docs
run: npm ci || npm install
- name: Build documentation with HonKit
working-directory: docs
run: npx honkit build
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/_book
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity for 2 years. It will be closed if no further activity occurs
within 30 days. Thank you for your contributions! 🦊
stale-pr-message: >
This pull request has been automatically marked as stale because it has not had
recent activity for 2 years. It will be closed if no further activity occurs
within 30 days. Thank you for your contributions! 🦊
close-issue-message: >
This issue was closed because it has been stale for 30 days with no further activity.
Feel free to reopen if this is still relevant! 🐲
close-pr-message: >
This pull request was closed because it has been stale for 30 days with no further activity.
Feel free to reopen if this is still relevant! 🐲
days-before-stale: 730
days-before-close: 30
stale-issue-label: 'stale'
stale-pr-label: 'stale'
exempt-issue-labels: 'pinned,security,good first issue'
exempt-pr-labels: 'pinned,security'
name: Validate Terrashark
on:
pull_request:
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Validate required files
run: |
test -f SKILL.md
test -f README.md
test -d references
test -f .claude-plugin/marketplace.json
- name: Validate SKILL frontmatter fields
run: |
python - <<'PY'
from pathlib import Path
p = Path('SKILL.md')
text = p.read_text(encoding='utf-8')
assert text.startswith('---\n'), 'SKILL.md must start with YAML frontmatter'
end = text.find('\n---\n', 4)
assert end != -1, 'SKILL.md frontmatter must close with ---'
fm = text[4:end]
assert 'name:' in fm, 'frontmatter missing name'
assert 'description:' in fm, 'frontmatter missing description'
print('frontmatter OK')
PY
- name: Validate local markdown links
run: |
python - <<'PY'
import re
from pathlib import Path
md_files = list(Path('.').rglob('*.md'))
bad = []
link_re = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
for f in md_files:
txt = f.read_text(encoding='utf-8', errors='ignore')
for m in link_re.finditer(txt):
link = m.group(1).strip()
if link.startswith('http://') or link.startswith('https://') or link.startswith('#'):
continue
if link.startswith('mailto:'):
continue
target = (f.parent / link.split('#',1)[0]).resolve()
if not target.exists():
bad.append((str(f), link))
if bad:
for file, link in bad:
print(f'Broken link in {file}: {link}')
raise SystemExit(1)
print('markdown links OK')
PY
- name: Check failure-mode references are present
run: |
test -f references/identity-churn.md
test -f references/secret-exposure.md
test -f references/blast-radius.md
test -f references/ci-drift.md
test -f references/compliance-gates.md
- name: Basic markdown style checks
run: |
python - <<'PY'
from pathlib import Path
issues = []
for f in Path('.').rglob('*.md'):
lines = f.read_text(encoding='utf-8', errors='ignore').splitlines()
for i, line in enumerate(lines, start=1):
if line.endswith(' '):
issues.append(f'{f}:{i}: trailing double-space')
if issues:
print('\n'.join(issues))
raise SystemExit(1)
print('basic markdown style OK')
PY
.claude/settings.local.jsonChangelog
Init
Code of Conduct
Our Standards
We are committed to providing a welcoming and respectful environment for everyone, regardless of experience level.
Expected behavior:
- Be respectful and constructive in discussions and code reviews
- Accept constructive criticism gracefully
- Focus on what is best for the project and community
- Show empathy towards other contributors
Unacceptable behavior:
- Harassment, insults, or derogatory comments
- Trolling or deliberately inflammatory remarks
- Publishing others' private information without consent
- Any conduct that would be considered inappropriate in a professional setting
Scope
This code of conduct applies to all project spaces: issues, pull requests, discussions, and any other communication channels associated with this repository.
Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1.
Contributing
Thanks for contributing to Terrashark.
Goal of this repo
Improve Terraform/OpenTofu output quality while staying lean on token usage.
Every change should answer:
- which failure mode does this prevent?
- what measurable quality gain does it provide?
- is the token cost justified?
Development flow
1. create a branch 2. make focused changes 3. run local checks 4. open PR using .github/PULL_REQUEST_TEMPLATE.md
Local checks
# quick sanity checks
rg -n "FIXME|placeholder-text" README.md SKILL.md references/*.md
python - <<'PY'
from pathlib import Path
assert Path('SKILL.md').exists()
assert Path('README.md').exists()
for p in [
'references/identity-churn.md',
'references/secret-exposure.md',
'references/blast-radius.md',
'references/ci-drift.md',
'references/compliance-gates.md',
]:
assert Path(p).exists(), f'missing {p}'
print('basic structure OK')
PYContent rules
- Keep examples original and clearly distinct.
- Prefer failure-mode framing over generic "best-practice dump" text.
- Avoid provider-specific deep dives unless they directly reduce a known LLM failure mode.
- Keep claims precise; avoid vague "always" language when tradeoffs exist.
Required for PR approval
- clear mapping to one or more failure modes
- no contradictory guidance across references
- updated links/indexes if files were moved/renamed
- validation workflow passing (
.github/workflows/validate.yml)
Security
- never commit credentials, tokens, or secret values
- do not paste real state snippets containing sensitive data
Community
Open an issue with:
- observed hallucination/failure pattern
- minimal reproducible prompt/context
- expected behavior
_book/
node_modules/
Terraform Skill Comparison: TerraShark vs Alternatives
A detailed comparison between TerraShark and other approaches to LLM-assisted Terraform code generation.
Comparison Overview
| Dimension | TerraShark | terraform-skill | No Skill |
|---|---|---|---|
| SKILL.md activation cost | ~600 tokens | ~4,400 tokens | 0 |
| Reference granularity | 19 focused files | 6 large files | — |
| Token burn per query | Low (load 1-2 small refs) | High (large refs, e.g. 1,126 lines) | 0 |
| Architecture | Failure-mode workflow | Static reference manual | — |
| Diagnoses before generating | Yes (Step 2) | No | No |
| Output contract | Yes — assumptions, tradeoffs, rollback | No | No |
| Migration playbooks | Yes (5 playbooks) | Partial (inline snippets) | No |
| Good/bad/neutral examples | Yes (3 dedicated files) | Inline only | No |
| Do/Don't checklist | Yes (dedicated file) | Inline only | No |
| Compliance framework mapping | ISO 27001, SOC 2, FedRAMP, GDPR, PCI DSS, HIPAA | Partial | No |
| MCP integration guidance | Yes | No | No |
| Hallucination prevention | Core design goal | Not addressed | No |
| Security-first defaults | Built-in | Checklist-style | No |
| CI/CD templates | GitHub Actions, GitLab CI, Atlantis, Infracost | GitHub Actions, GitLab CI, Atlantis | No |
| License | MIT | Apache 2.0 | — |
Architectural Difference
The key difference is architectural.
Static reference approach (terraform-skill and similar): Dumps thousands of tokens into context on every activation, then loads additional reference files that can be over 1,000 lines each. Gives the AI information but never tells it how to think about a problem. No diagnosis step, no risk assessment, no structured output.
Failure-mode workflow (TerraShark): The core SKILL.md is an 86-line operational workflow costing ~600 tokens on activation — over 7x leaner. Forces the AI through a diagnostic sequence: capture context, identify failure modes, load only relevant references, propose fixes with explicit risk controls, validate, and deliver a structured output contract.
Why This Matters
1. Token Efficiency
Static skills burn ~4,400 tokens just to activate, before any reference files. A single reference file like module-patterns.md (1,126 lines, ~7,000 tokens) can double the cost. TerraShark's activation is ~600 tokens, and its 19 granular reference files mean the AI loads only what's needed.
2. Hallucination Prevention
Static skills provide good patterns but never ask the AI to diagnose what could go wrong. TerraShark's Step 2 forces failure-mode identification before any code is generated. Step 4 requires explicit risk controls. Step 7 enforces an output contract.
3. Reference Coverage
TerraShark ships 19 focused reference files covering failure modes, backend-specific state safety, migration playbooks, good/bad/neutral examples, do/don't checklists, compliance framework mappings, and MCP integration. Alternatives typically have fewer, larger files that go deep on some topics but lack migration playbooks, anti-pattern banks, and compliance mappings.
When to Use No Skill
For simple, one-off Terraform questions where the AI's built-in knowledge is sufficient and you don't need:
- Failure mode diagnosis
- Output contracts
- Migration safety
- Compliance evidence
Summary
TerraShark provides 7x leaner activation, a failure-mode-first diagnostic workflow, output contracts, granular references, and LLM-specific hallucination prevention. Its architecture is fundamentally designed for the core use case of LLM-assisted IaC generation.
Terraform Skill Token Efficiency Strategy
This page explains the token efficiency strategy that makes TerraShark the leanest Terraform skill available, and the empirical process used to validate every line of content.
The Problem with Large Skills
Most Terraform skills dump huge walls of text into the AI's context window on every activation. This creates three problems:
1. Wasted tokens — context window space spent on skill content is unavailable for the user's codebase and conversation 2. Signal dilution — when everything is included, the model has difficulty finding what matters 3. Redundancy — LLMs already know basic Terraform syntax; restating it burns tokens with no quality improvement
TerraShark's Approach
Lean Activation (~600 tokens)
The core SKILL.md is 86 lines containing:
- No HCL examples
- No inline code blocks
- No tutorial material
- Pure procedure: a workflow the model follows
Granular References (19 files)
Instead of 6 large files (as in alternatives), TerraShark uses 19 focused files. The model loads only the 1-2 files relevant to the diagnosed failure mode.
Content Inclusion Rules
Content enters TerraShark only when at least one condition is met:
1. It materially lowers the probability of destructive or non-compliant changes 2. It prevents common plan/apply surprises 3. It encodes organizational guardrails that general model knowledge cannot infer
Content is excluded when:
1. It is generic Terraform/OpenTofu knowledge with low failure impact 2. It is provider-specific deep design that belongs in project docs 3. It duplicates an existing rule without adding a new decision signal
Expansion Rule
If repeated failure patterns emerge, targeted lines are added for that specific failure mode instead of broad expansion.
The Token Experiment
The content was empirically tested, not designed by intuition.
Process
1. Started with significantly larger reference content 2. Built automated test suite of practical Terraform/OpenTofu tasks 3. Measured baseline quality (correctness, safety, completeness, hallucination rate) 4. Stripped sections one at a time, re-running the full test suite 5. If quality dropped: content was restored (load-bearing) 6. If quality stayed stable: content was permanently removed (redundant) 7. Continued until every remaining section was load-bearing
What Survived
Content the model demonstrably needs help with:
- Module role boundaries and composition rules
- Migration playbooks (moved blocks, count-to-for_each, imports)
- Native test caveats (set indexing, computed values, mocked providers)
- CI delivery templates (policy checks, artifact integrity, env protection)
- Quick troubleshooting (stuck locks, backend migration, provider auth in CI)
What Was Removed
Content the model already knows well:
- Generic HCL syntax tutorials
- Provider-specific resource deep dives
- Broad "best practice" prose without failure-mode framing
- Duplicate explanations
Core Principle
High signal density. Every line must earn its token cost by preventing a specific failure mode or encoding knowledge the model demonstrably lacks.
Backend State Safety
This guide covers backend-specific state safety for Terraform and OpenTofu. Use it when configuring a backend, migrating state, handling locks, or reviewing access to state storage.
When This Guide Applies
Load this guidance when the backend is s3, azurerm, gcs, remote, cloud, pg, consul, or local, or when work mentions backend migration, state storage, locking, force-unlock, state backup, or restore.
Why This Matters
Terraform/OpenTofu state is the source of truth for live resource identity and often contains sensitive values. Backend mistakes can leak secrets, orphan resources, disable locking, or make a routine refactor look like a destructive replacement.
Backend Baseline
- Use remote state for every shared, CI, or production environment
- Require locking on every apply path
- Encrypt state at rest and in transit
- Enable state versioning or point-in-time recovery where the backend supports it
- Keep backend storage and lock primitives in a bootstrap root with a separate lifecycle
- Never manage the backend bucket/container/table from the same root that uses it as its active backend
- Keep backend credentials out of checked-in backend config; prefer workload identity or CI-provided partial backend config
Backend-Specific Checks
| Backend | Required Checks |
|---|---|
s3 | Bucket versioning, encryption, public access block, narrow IAM, lock mechanism configured, state key split by environment/root |
azurerm | Storage account encryption, blob soft delete/versioning where available, lease-based locking, private/network restrictions, narrow data-plane RBAC |
gcs | Bucket versioning, uniform bucket-level access, encryption policy, narrow IAM, prefix split by environment/root |
remote / cloud | Workspace boundary matches blast radius, state sharing is restricted, sensitive variables are marked, applies use approved execution mode |
pg | TLS, database backups, least-privilege user, lock behavior verified, connection secrets kept out of code |
consul | TLS, ACLs, snapshots/backups, highly available quorum, lock/session behavior verified |
local | Solo prototype only; do not use for shared, CI, or production environments |
Migration Guardrails
- Do not combine backend migration with unrelated resource changes
- Freeze applies for the affected state before migrating
- Pull and securely store a state backup before
init -migrate-state; do not commit it - Record current backend type, address/key, workspace, runtime version, and actor
- Migrate the lowest-risk environment first
- After migration, compare resource addresses before/after and run a no-op plan
- Keep the old backend retained and access-controlled until restore has been tested or the rollback window has passed
Use init -migrate-state when moving state between backends. Use init -reconfigure only when intentionally accepting the configured backend without migrating existing state.
Lock Handling
- Treat a lock as a safety signal, not an inconvenience
- Before
force-unlock, verify the lock holder, CI run, process, and timestamp - Never recommend
force-unlockwhile an apply may still be running - Serialize applies for shared foundation, backend, identity, and network roots
Access and Secret Handling
- Treat state readers as secret readers
- Avoid storing plan/state artifacts in public or broad-access CI logs
- If a secret entered state, rotate the secret and use the secret remediation playbook; masking output is not enough
- Keep backend read/write permissions separate when the platform supports it
LLM Mistake Checklist
- Suggesting
localbackend for a team, CI, or production stack - Creating backend storage inside the same root that uses it
- Omitting a lock strategy for a shared backend
- Treating encryption as protection from anyone who can read state
- Combining backend migration with broad resource refactors
- Recommending
force-unlockwithout proving no apply is active - Deleting old backend data immediately after migration
- Hard-coding backend credentials in HCL or checked-in config
Validation Commands
Use the active runtime (terraform or tofu) consistently:
terraform version
terraform workspace show
terraform state pull > state-backup.json
terraform state list > state-before.txt
terraform init -migrate-state
terraform state list > state-after.txt
diff -u state-before.txt state-after.txt
terraform plan -detailed-exitcodeStore state-backup.json in a secure temporary location outside the repository and delete it only after rollback is no longer needed.
Terraform Coding Standards
This guide covers implementation-level consistency for Terraform and OpenTofu code generation, including naming, typing, iteration, and version discipline.
Naming and Metadata
- Use names that reflect business purpose (
payments_api,audit_kms), not temporary implementation details - Reserve
thisfor real singleton resources only - Centralize tags/labels in
localsand apply consistently - Normalize provider-constrained identifiers before use
Identifier Normalization Pattern
Some resources reject characters valid in domains or service names (e.g., . in RDS identifiers):
locals {
raw_name = "${var.domain}-prod"
normalized_name = regexreplace(lower(local.raw_name), "[^a-z0-9-]", "-")
}Then use local.normalized_name for fields like identifier and final_snapshot_identifier.
Resource Block Ordering
Preferred order inside resource blocks:
1. Identity/core arguments 2. Behavior/config arguments 3. Nested blocks 4. Tags/labels 5. Lifecycle/meta-arguments
This keeps diffs predictable and reviewable.
Variable Contract Style
Variable attribute order:
1. description 2. type 3. default (if used) 4. nullable 5. sensitive 6. validation
Prefer explicit objects with optional() over untyped maps for long-lived module contracts.
Iteration and Identity
| Pattern | Use When |
|---|---|
count | Optional singleton toggles (0 or 1) |
for_each | Any collection with stable logical identities |
Never use list index as long-lived identity key.
Set-Type Handling
Rules to avoid ordering bugs and test failures:
- Never index sets directly; convert with
sort(tolist(...))when order matters - Use
for_eachwithtoset(...)only when identity is the value itself - For stable diff output, transform sets to sorted lists in outputs
- In tests, prefer
contains()or set equality over positional assertions
Outputs
- Expose only stable interfaces needed by consumers
- Mark secret-bearing outputs as
sensitive = true - Avoid dumping entire provider objects
Version and Lock Discipline
- Set runtime floor in
required_version - Bound provider and module versions
- Commit lockfile changes intentionally
- Keep upgrade PRs separate from functional changes where possible
Terraform Feature Guard Table
Use this table when deciding what to emit for a given runtime floor. Maps features to their minimum version and the specific LLM error pattern for each.
| Feature | Min Version | LLM Error Pattern |
|---|---|---|
moved blocks | 1.1+ | Omitted during refactor, causing destroy/create |
for_each over count | 0.12+ | Model defaults to count for every collection |
write_only arguments | 1.11+ | Model uses sensitive and assumes state is safe |
optional() defaults | 1.3+ | Model emits wrapper variables and loose maps |
| Cross-variable validation | 1.9+ | Model pushes checks into postconditions only |
Declarative import blocks | 1.5+ | Model recommends ad-hoc CLI import only |
check blocks | 1.5+ | Model ignores runtime assertions entirely |
removed blocks | 1.7+ | Model deletes resources with no lifecycle transition |
| Provider-defined functions | 1.8+ | Model overuses data sources for transformations |
If target runtime is below a feature floor, emit fallback guidance explicitly.
Review Checklist
- Typed inputs and validations present
- Iteration model chosen for address stability
- No plaintext secret defaults
- Version constraints and lockfile strategy are explicit
- Migration-sensitive changes include
moved/importplan
Terraform Module Architecture
This guide covers designing reusable Terraform modules, composition layers, and the deep hierarchy model for platform engineering at scale.
Module Roles
| Role | Responsibility |
|---|---|
| Primitive module | Wraps one resource family with strict interface |
| Composite module | Assembles multiple primitives for a deployable capability |
| Root composition | Injects environment values and wiring only |
Keep business policy out of primitives when it is environment-specific.
Contract Design
A good module contract has:
- Strongly typed inputs
- Defaults only for safe/common behavior
- Explicit outputs for consumers
- Preconditions for invariants
Bad contract smells:
- Many loosely typed maps
- Opaque passthrough variables
- Outputs that mirror entire provider objects
Suggested File Layout
| File | Purpose |
|---|---|
main.tf | Resources and module calls |
variables.tf | Typed input contract and validation |
outputs.tf | Explicit consumer interface |
versions.tf | Runtime and provider constraints |
locals.tf | Computed values, naming, shared labels |
Composition Rules
- Pass only required values into child modules
- Avoid circular dependencies and hidden ordering
- Prefer data flow via input/output over broad
depends_on - Keep module count manageable; over-fragmentation hurts maintainability
Deep Hierarchy Model
For platform engineering at scale, use a 5-level module hierarchy:
L4: Org Orchestration
└── L3: Environment Roots
└── L2: Domain Stacks
└── L1: Composites
└── L0: Primitives| Level | Role | Examples |
|---|---|---|
| L0 Primitives | One resource family, strict contract | VPC, IAM role, S3 bucket |
| L1 Composites | Capability units built from primitives | Networking stack, compute cluster |
| L2 Domain stacks | Bounded business domains | Payments, identity, observability |
| L3 Environment roots | Env-specific wiring and configuration | dev, staging, production |
| L4 Org orchestration | Account/project vending and shared policy | Organization policies, account factory |
Composition Rules
- Dependencies flow downward only (L4 -> L3 -> L2 -> L1 -> L0)
- No lateral imports across the same level without an explicit interface contract
- Cross-state data flow is via explicit outputs or approved remote state access
- Each level owns its state boundary and apply lifecycle
- Environment roots should not embed business logic; keep it in L2/L1
Decision Aid
Add a new level only if ownership, lifecycle, or blast radius requires it.
Module Release Discipline
- Tag module versions
- Use bounded version constraints in consumers
- Run compatibility tests before raising lower bounds
When to Create a New Module
Create a new module only when at least one is true:
- Reused across 2+ stacks
- Ownership differs from current module
- Lifecycle differs significantly
- Change blast radius needs isolation
Terraform Structure and State Management
This guide covers how to choose repository shape, environment segmentation, and state boundaries for Terraform projects.
Boundary Model
Define boundaries by three factors:
1. Change cadence — what changes together 2. Blast radius — what can fail together 3. Ownership — who approves and supports it
If those differ between components, split stacks.
Root Module Patterns
| Pattern | Scope |
|---|---|
| service-root | One business service and its direct dependencies |
| platform-root | Shared platform primitives (network, identity, observability) |
| bootstrap-root | Backend/state prerequisites and foundational security controls |
Avoid monolithic roots that mix all three.
Environment Isolation Options
| Option | Best For |
|---|---|
| Separate root directories per environment | Default choice, clearest isolation |
| Workspace-per-environment with strict policy | When governance is strong and access controls exist |
| Separate repositories | Hard regulatory segregation requirements |
Pick one and document why.
State Backend Baseline
- Remote backend only for collaborative environments
- Lock protection for every apply path
- Encryption at rest and in transit
- Narrow IAM on state and lock stores
- Versioned backup with restore tested at least once
- Load Backend State Safety for backend-specific locking, access, and migration guardrails
Cross-Stack Dependencies
Preferred order:
1. Explicit module outputs passed in the same root 2. Published interface artifacts (preferred at scale) 3. `terraform_remote_state` as last-resort coupling
If terraform_remote_state is used, version and ownership of the producer stack must be explicit.
Apply Safety Gates
Minimum gates for any apply:
fmtandvalidate- Lint and security scan
- Policy checks
- Reviewed plan
- Approved apply
Change Safety for State Evolution
- Use
movedblocks for renames and address changes - For imports, document source of truth and verify idempotency before apply
- For manual state operations, require peer review and rollback notes
{
"title": "Terraform Skill for Claude Code — TerraShark Documentation",
"description": "Comprehensive documentation for TerraShark, the Terraform and OpenTofu skill for Claude Code and Codex. Prevent hallucinations, enforce best practices, and generate safe infrastructure-as-code.",
"author": "Lukas Niessen",
"language": "en",
"gitbook": ">= 3.2.0",
"plugins": [
"-sharing",
"search-pro",
"-lunr",
"-search"
],
"pluginsConfig": {},
"structure": {
"readme": "README.md",
"summary": "SUMMARY.md"
},
"links": {
"sidebar": {
"GitHub Repository": "https://github.com/LukasNiessen/terrashark"
}
}
}
Changelog
Current Version: v2.3.0
TerraShark is actively maintained. For the latest changes, see the GitHub repository.
What's Included in v2.3.0
- Focused
SKILL.mdexecution flow (7-step workflow) - Five primary failure-mode references (identity churn, secret exposure, blast radius, CI drift, compliance gates)
- Expanded architecture guidance (structure/state, module architecture, coding standards)
- Five migration playbooks for safe evolution
- CI/CD delivery patterns (GitHub Actions, GitLab CI, Atlantis, Infracost)
- Risk-based testing matrix with native test caveats and Terratest coverage
- Good/bad/neutral example banks
- Do/Don't pattern checklist
- MCP integration guidance
- Token balance rationale
- Automated validation workflow
- Plugin marketplace support
Contributing to the Terraform Skill
We appreciate contributions to TerraShark. Every change should improve Terraform/OpenTofu output quality while staying lean on token usage.
Contribution Principles
Every change should answer three questions:
1. Which failure mode does this prevent? — must map to at least one of the five failure modes 2. What measurable quality gain does it provide? — demonstrate improvement 3. Is the token cost justified? — every line must earn its place
Development Flow
1. Create a branch 2. Make focused changes 3. Run local checks 4. Open PR using .github/PULL_REQUEST_TEMPLATE.md
Local Checks
# Quick sanity checks
rg -n "FIXME|placeholder-text" README.md SKILL.md references/*.md
python - <<'PY'
from pathlib import Path
assert Path('SKILL.md').exists()
assert Path('README.md').exists()
for p in [
'references/identity-churn.md',
'references/secret-exposure.md',
'references/blast-radius.md',
'references/ci-drift.md',
'references/compliance-gates.md',
]:
assert Path(p).exists(), f'missing {p}'
print('basic structure OK')
PYContent Rules
- Keep examples original and clearly distinct
- Prefer failure-mode framing over generic "best-practice dump" text
- Avoid provider-specific deep dives unless they directly reduce a known LLM failure mode
- Keep claims precise; avoid vague "always" language when tradeoffs exist
Required for PR Approval
- Clear mapping to one or more failure modes
- No contradictory guidance across references
- Updated links/indexes if files were moved/renamed
- Validation workflow passing (
.github/workflows/validate.yml)
Security
- Never commit credentials, tokens, or secret values
- Do not paste real state snippets containing sensitive data
Reporting Issues
Open an issue with:
- Observed hallucination/failure pattern
- Minimal reproducible prompt/context
- Expected behavior
Community
- Submit an issue on GitHub
- Join GitHub Discussions
- Star the repository if TerraShark helps your project
Five Terraform Failure Modes
TerraShark organizes all its guidance around five explicit failure modes. These are not arbitrary categories — they represent the five most common ways LLM-generated Terraform causes real operational damage.
Every piece of content in the Terraform skill maps to at least one failure mode. Content that does not reduce the probability of any failure mode is excluded.
1. Identity Churn
What it is: Resource addressing instability that causes unexpected destroy/create cycles during refactors or collection changes.
Common symptoms:
- Plan shows broad replace actions after small list edits
- Renaming resources or modules triggers destroy/create
- Refactor from
counttofor_eachcauses churn - Imported resources keep drifting because addressing is unstable
Root causes:
- Index-based identity (
count) used for long-lived objects - Keys derived from unstable data (sorted lists, transient IDs)
- Missing
movedblocks during refactors for_eachkeys derived from values unknown at plan time
LLM-specific risks: Models default to count for every collection, omit moved blocks during refactors, and build for_each keys from computed IDs not known until apply.
Full reference: Identity Churn
2. Secret Exposure
What it is: Secrets leaking into state files, CI logs, plan output, variable defaults, or artifact storage.
Common symptoms:
- Secret values appear in plan output or logs
- Credentials defined in variable defaults
- Sensitive outputs printed in CI
- Generated passwords stored in state unintentionally
Root causes:
- Hardcoded defaults in
variables.tf - Secret-bearing resources whose values persist in state
- Logging
terraform showoutputs without redaction - Artifact retention policies keeping plan/state exports too long
LLM-specific risks: Models assume sensitive alone means "not in state", propose plaintext defaults for demo convenience, and use outputs that expose connection strings in PR comments.
Full reference: Secret Exposure
3. Blast Radius
What it is: Oversized stacks where a small change can cause widespread, unintended impact across unrelated services.
Common symptoms:
- Tiny change triggers a very large plan
- Unrelated services share one state and fail together
- Production and non-production are entangled
- Review/approval ownership is unclear
Root causes:
- No ownership boundaries between services
- Monolithic root modules mixing all concerns
- Weak state isolation between environments
- Missing apply governance for shared foundations
LLM-specific risks: Models propose one monolithic root for convenience, recommend workspace-only isolation without access controls, and omit rollback paths for shared foundation changes.
Full reference: Blast Radius
4. CI Drift
What it is: Pipeline behavior diverging from local behavior or from reviewed intent, causing unreviewed or inconsistent applies.
Common symptoms:
- CI plan differs from local plan unexpectedly
- Apply occurs without using the reviewed plan artifact
- Provider/runtime drift between runs
- Scanner/policy stages skipped on some code paths
Root causes:
- Unpinned runtime/provider versions
- Missing or stale lockfile
- Apply job re-running
planinstead of consuming the reviewed artifact - Inconsistent credentials/auth between plan and apply
LLM-specific risks: Models produce CI pipelines with missing lockfile strategies, apply without saved plan artifacts, skip policy stages despite claiming compliance, and omit branch/environment protection.
Full reference: CI Drift
5. Compliance Gate Gaps
What it is: Missing enforceable controls and evidence artifacts despite referencing compliance frameworks by name.
Common symptoms:
- Frameworks mentioned but no enforceable gates exist
- Security best practices confused with compliance evidence
- Missing approval workflows for different risk classes
- No evidence retention for production applies
Root causes:
- No preventative controls (policy/validation)
- No detective controls (logging/monitoring)
- No evidence artifacts (plans, approvals, audit records)
LLM-specific risks: Models mention framework names without providing enforceable gates, confuse security best practices with compliance evidence, omit risk-class approvals, and ignore data-residency obligations.
Full reference: Compliance Gate Gaps
How Failure Modes Drive the Terraform Skill
The 7-step workflow uses these failure modes as the diagnostic lens in Step 2. Every reference file, every example, and every checklist in TerraShark traces back to preventing one or more of these five failure modes. This ensures the skill is focused, measurable, and directly actionable.
Terraform Skill Design Philosophy
This page describes the architectural decisions and empirical process behind TerraShark's design.
Failure-Mode-First Architecture
TerraShark is built around a single insight: telling an LLM what good Terraform looks like is less effective than telling it how to think about Terraform problems.
The core SKILL.md is not a reference manual. It is a 7-step operational workflow that forces the model to diagnose before it generates. This prevents the most common failure pattern in LLM-assisted IaC: producing syntactically valid but operationally dangerous code.
Token Efficiency as a Design Constraint
Context window space is a finite resource. Every token spent on skill content is a token unavailable for the user's actual codebase, conversation history, and tool results.
TerraShark is designed for minimal activation cost:
| Metric | TerraShark | Typical Alternative |
|---|---|---|
| Activation cost | ~600 tokens | ~4,400 tokens |
| Reference files | 19 focused files | 6 large files |
| Loaded per query | 1-2 small files | Large reference dumps |
The core SKILL.md is 86 lines containing no HCL examples, no inline code blocks, and no tutorial material. It is purely procedural. Depth lives in 19 granular reference files loaded on demand.
LLM-Aware Guardrails
Every reference file that covers a risk domain includes an LLM mistake checklist — a list of specific errors that language models make when generating Terraform code:
- Defaulting to
countinstead offor_eachfor collections - Omitting
movedblocks during refactors, causing destroy/create cycles - Using
sensitiveand assuming the value is safe from state - Proposing plaintext credential defaults "for demo purposes"
- Recommending CLI-only
terraform importinstead of declarative import blocks
These checklists exist because the model needs to know what it gets wrong, not just what is correct. A reference that only shows the right pattern still allows the model to hallucinate the wrong one. A reference that explicitly names the hallucination pattern reduces it.
The Feature Guard Table in coding-standards.md maps Terraform features to their minimum version and the specific LLM error pattern associated with each, letting the model check feature availability before emitting code.
Output Contracts
Every TerraShark response includes a structured output contract:
- Assumptions and version floor — what the model assumed
- Selected failure modes — which risks were diagnosed
- Chosen remediation and tradeoffs — what was recommended and why
- Validation/test plan — how to verify the output
- Rollback/recovery notes — how to undo if something goes wrong
This makes outputs auditable. A reader can check assumptions, verify failure mode coverage, and validate the rollback path before applying anything.
Reference Granularity
The 19 reference files are organized by concern, not by Terraform concept:
| Category | Files | When Loaded |
|---|---|---|
| Primary failure modes | Identity churn, secret exposure, blast radius, CI drift, compliance gates | When that failure mode is diagnosed |
| Structural guidance | Structure/state, backend state safety, module architecture, coding standards | When designing, refactoring, or changing backends |
| Operational references | Migration playbooks, testing matrix, CI delivery, security/governance, quick ops | For specific operational tasks |
| Pattern banks | Good examples, bad examples, neutral examples, do/don't patterns | For review or teaching |
| Integration and meta | MCP integration, token balance rationale | When relevant |
Each file is self-contained. No file depends on another file being loaded simultaneously.
Deep Hierarchy Model
For platform engineering at scale, TerraShark defines a 5-level module hierarchy:
| Level | Role | Scope |
|---|---|---|
| L0 | Primitives | One resource family, strict contract |
| L1 | Composites | Capability units built from primitives |
| L2 | Domain stacks | Bounded business domains |
| L3 | Environment roots | Env-specific wiring and configuration |
| L4 | Org orchestration | Account/project vending and shared policy |
Dependencies flow downward only. Each level owns its state boundary and apply lifecycle.
Content Inclusion Rules
Content enters TerraShark only when at least one condition is met:
1. It materially lowers the probability of destructive or non-compliant changes 2. It prevents common plan/apply surprises 3. It encodes organizational guardrails that general model knowledge cannot infer
Content is excluded when:
1. It is generic Terraform/OpenTofu knowledge with low failure impact 2. It is provider-specific deep design that belongs in project docs 3. It duplicates an existing rule without adding a new decision signal
The Token Experiment
The content in TerraShark was empirically tested, not designed by intuition.
Process
1. Started large — broader coverage, more examples, more tutorial material 2. Built automated test suite — practical Terraform/OpenTofu task patterns 3. Measured baseline quality — correctness, safety, completeness, hallucination rate 4. Stripped iteratively — removed sections one at a time, re-running the full test suite 5. Measured quality impact — if quality dropped, content was restored; if stable, content was permanently removed 6. Converged — continued until every remaining section was load-bearing
What Survived (Models Need Help With)
- Module role boundaries and composition rules
- Migration playbooks (moved blocks, count-to-for_each, imports)
- Native test caveats (set indexing, computed values, mocked providers)
- CI delivery templates (policy checks, artifact integrity, env protection)
- Quick troubleshooting (stuck locks, backend migration, provider auth in CI)
What Was Removed (Models Already Know)
- Generic HCL syntax tutorials
- Provider-specific resource deep dives
- Broad "best practice" prose without failure-mode framing
- Duplicate explanations of concepts covered by multiple rules
Core Design Principle
High signal density. Every line must earn its token cost by preventing a specific failure mode or encoding knowledge the model demonstrably lacks. Content that merely restates what the model already knows is actively harmful — it burns context window space without improving output quality.
The 7-Step Terraform Skill Workflow
The core of TerraShark is a 7-step operational workflow defined in SKILL.md. Unlike traditional reference manuals that dump information and hope the AI uses it correctly, this workflow forces the AI to diagnose before generating — the single most important pattern for preventing Terraform hallucinations.
Overview
Capture Context → Diagnose Failure Modes → Load References → Propose Fix → Generate Artifacts → Validate → Output ContractStep 1: Capture Execution Context
Before writing any code, the Terraform skill records:
- Runtime:
terraformortofuand exact version - Providers: which cloud providers and their versions
- Target platform: AWS, Azure, GCP, etc.
- State backend: S3, GCS, Azure Blob, HCP Terraform, etc.
- Execution path: local CLI, CI, HCP Terraform/TFE, Atlantis
- Environment criticality: dev, shared, or production
If any of these are unknown, the skill states assumptions explicitly in the output contract.
Why this matters: A module targeting Terraform 1.1 cannot use import blocks (requires 1.5+). A CI pipeline needs different patterns than local CLI. Production requires approval gates that dev does not.
Step 2: Diagnose Likely Failure Modes
The skill selects one or more failure modes based on the user's intent and risk level:
| Failure Mode | When Diagnosed |
|---|---|
| Identity churn | Refactors, collection changes, count/for_each decisions |
| Secret exposure | Credential handling, state access, CI artifact management |
| Blast radius | Stack design, environment isolation, state boundaries |
| CI drift | Pipeline setup, version management, plan/apply separation |
| Compliance gate gaps | Policy setup, framework requirements, approval workflows |
This step is what makes TerraShark fundamentally different from static reference skills. The AI must identify what could go wrong before it starts generating code.
Step 3: Load Only Relevant References
Based on the diagnosed failure modes, the skill loads targeted reference files:
Primary references (one per failure mode):
references/identity-churn.mdreferences/secret-exposure.mdreferences/blast-radius.mdreferences/ci-drift.mdreferences/compliance-gates.md
Supplemental references (loaded only when needed):
- Testing, CI delivery, module architecture, coding standards, migration playbooks, security governance, quick ops, examples, and more
This granularity means a query about secret handling never loads CI delivery patterns, and a query about module architecture never loads compliance gates. Only 1-2 small, focused files are loaded per query instead of one massive dump.
Step 4: Propose Fix Path with Explicit Risk Controls
For each proposed fix, the skill includes:
- Why this addresses the failure mode — direct mapping from diagnosis to solution
- What could still go wrong — honest risk assessment
- Guardrails — tests, approvals, and rollback steps to mitigate remaining risk
This forces transparency. The AI cannot silently generate code that might cause damage without disclosing the risks.
Step 5: Generate Implementation Artifacts
When applicable, the output includes:
- HCL changes: typed variables, stable keys, bounded version constraints
- Migration blocks:
movedblocks,importstrategy - CI pipeline updates: plan/apply separation, artifact management, policy checks
- Compliance controls: approval gates, policy rules, evidence paths
Step 6: Validate Before Finalize
The skill provides a command sequence tailored to the runtime and risk tier:
terraform fmt -check
terraform validate
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.jsonThe skill never recommends direct production apply without a reviewed plan and approval gate.
Step 7: Output Contract
Every response includes a structured contract:
| Section | Content |
|---|---|
| Assumptions and version floor | What was assumed about the environment |
| Selected failure modes | Which risks were diagnosed |
| Chosen remediation and tradeoffs | What was recommended and what was traded off |
| Validation/test plan | How to verify the output |
| Rollback/recovery notes | How to undo if something goes wrong |
This makes every output auditable. A reviewer can check assumptions, verify failure mode coverage, and validate the rollback path — all before applying anything.
Why This Architecture Works
The 7-step workflow prevents the most common failure pattern in LLM-assisted infrastructure-as-code: producing syntactically valid but operationally dangerous code.
A static reference manual tells the AI what good Terraform looks like. The 7-step workflow tells the AI how to think about Terraform problems. This is the difference between giving someone a cookbook and giving them a diagnostic checklist.
Terraform Anti-Patterns (Bad Patterns)
Seven common anti-patterns that the Terraform skill explicitly prevents. Each pattern maps to a specific failure mode and is a known LLM hallucination risk.
1. List-Driven count for Mutable Identities
variable "queue_names" {
type = list(string)
}
resource "aws_sqs_queue" "worker" {
count = length(var.queue_names)
name = var.queue_names[count.index]
}Why this fails: Reordering list entries forces unexpected replacements. Object identity is tied to index, not business key. Failure mode: Identity churn.
2. No Type Constraints on Critical Input
variable "network" {
default = {}
}Why this fails: Consumer mistakes surface late and noisily. Module contract is ambiguous. Failure mode: Blast radius from silent misconfiguration.
3. Sensitive Defaults Committed in Code
variable "api_token" {
type = string
default = "token-please-change"
}Why this fails: Secret can leak via VCS and logs. Violates basic secret hygiene. Failure mode: Secret exposure.
4. Floating Provider Versions
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
}
}
}Why this fails: Pulls latest provider implicitly. Increases non-deterministic CI behavior. Failure mode: CI drift.
5. Blanket ignore_changes
resource "aws_db_instance" "main" {
identifier = "core-db"
engine = "postgres"
lifecycle {
ignore_changes = all
}
}Why this fails: Masks drift and important config regressions. Erodes trust in plan output. Failure mode: CI drift and blast radius.
6. Dynamic Block with Wrong Iterator Reference
variable "ports" {
type = list(number)
}
resource "aws_security_group" "app" {
name = "app-sg"
dynamic "ingress" {
for_each = var.ports
content {
from_port = ports.value # WRONG: should be ingress.value
to_port = ports.value # WRONG: should be ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}Why this fails: The iterator name defaults to the dynamic block label (ingress), not the variable name. Using ports.value causes an unknown reference error. This is a common LLM hallucination pattern.
7. Hidden Ordering via Unrelated depends_on
resource "aws_iam_role" "app" {
name = "app-role"
}
resource "aws_cloudwatch_log_group" "app" {
name = "/app/runtime"
depends_on = [aws_iam_role.app]
}Why this fails: Artificial dependency reduces parallelism. Hides poor interface boundaries. Failure mode: Blast radius from hidden coupling.
Terraform Do and Don't Checklist
A fast reference checklist for safe Terraform and OpenTofu code generation. Use this for quick reviews.
Identity and Iteration
| Do | Don't |
|---|---|
Use for_each with stable, business-meaningful keys | Use list index as long-lived identity |
| Keep identity keys separate from mutable attributes | Derive identity from a computed attribute |
Add moved blocks when renaming resources or modules | Delete/rename addresses without an explicit migration plan |
Secrets and Sensitive Data
| Do | Don't |
|---|---|
Mark secret outputs as sensitive = true | Put secrets in default values or .tfvars committed to VCS |
| Use secret managers and data sources for runtime injection | Echo secrets in provisioner commands |
Avoid logging sensitive values in locals or output | Rely on sensitive alone to protect state contents |
State Boundaries and Blast Radius
| Do | Don't |
|---|---|
| Keep production in isolated state backends or workspaces | Mix unrelated systems in a single root state |
| Split large stacks by lifecycle and ownership | Apply directly to production from unreviewed branches |
| Use environment protection and approvals for apply | Use one monolithic stack for all environments |
Module Contracts
| Do | Don't |
|---|---|
| Expose typed inputs and explicit outputs | Accept untyped map(any) for core interfaces |
Use optional() for evolution-friendly contracts | Expose entire provider objects as outputs |
Validate invariants with validation and precondition | Push environment-specific policy into primitive modules |
Providers and Versions
| Do | Don't |
|---|---|
| Pin runtime and providers with bounded constraints | Float provider versions |
Commit .terraform.lock.hcl intentionally | Rely on implicit provider inheritance in multi-region setups |
| Pass provider aliases explicitly to child modules | Mix upgrades with functional changes in the same PR |
Data Sources and Dependencies
| Do | Don't |
|---|---|
| Use data sources for read-only integration | Use depends_on to paper over missing interfaces |
| Model dependencies via input/output wiring | Use data sources for identity fields that can change |
Keep depends_on for real ordering requirements only | Create hidden ordering between unrelated resources |
CI/CD and Policy
| Do | Don't |
|---|---|
| Separate plan and apply | Allow direct apply from arbitrary branches |
| Keep an auditable reviewed plan artifact | Skip policy checks for production changes |
| Run policy and cost checks on every plan | Delete plan artifacts before approval |
Testing
| Do | Don't |
|---|---|
Run terraform test / tofu test for module-level checks | Rely on plan-only validation for runtime-only attributes |
| Use Terratest for workflow or integration validation | Run destructive tests without isolation and cleanup |
| Tier tests by risk and cost | Treat mocked provider tests as full integration coverage |
Migration and Refactors
| Do | Don't |
|---|---|
Include moved or import strategy in the same change | Rename resources without preserving state identity |
| Run a reviewed plan before any apply | Apply refactors without plan review |
| Document rollback steps for destructive changes | Remove resources without lifecycle transition |
Terraform Good Patterns
Eight strong implementation patterns that the Terraform skill uses as positive examples. Each pattern directly prevents one or more failure modes.
1. Stable Identity Map for Service Accounts
variable "service_accounts" {
type = map(object({
display_name = string
roles = set(string)
}))
}
resource "google_service_account" "app" {
for_each = var.service_accounts
account_id = each.key
display_name = each.value.display_name
}Why this works: Key-based identity survives insertion/removal changes. Contract is strict and predictable. Prevents identity churn.
2. Cross-Variable Validation for Safe Combinations
variable "public_endpoint" {
type = bool
default = false
}
variable "allowed_cidrs" {
type = list(string)
default = []
validation {
condition = var.public_endpoint || length(var.allowed_cidrs) == 0
error_message = "allowed_cidrs must be empty unless public_endpoint is true."
}
}Why this works: Invalid combinations fail early. Intent is encoded directly in the module interface. Prevents blast radius from misconfigured access.
3. Strong Object Typing with Optional Fields
variable "node_pool" {
type = object({
size = string
min_replicas = number
max_replicas = number
spot_instances = optional(bool, false)
})
}Why this works: Flexible without sacrificing schema clarity. Avoids ad-hoc maps and runtime surprises.
4. Narrow, Useful Outputs
output "app_subnet_ids" {
description = "Subnet ids for application workloads"
value = values(aws_subnet.app)[*].id
}Why this works: Downstream modules get exactly what they need. Avoids leaking full provider objects that could expose sensitive data.
5. Controlled Provider Pinning
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.40.0, < 6.0.0"
}
}
}Why this works: Controlled upgrade window prevents accidental major-version breakage. Prevents CI drift from floating versions.
6. Dynamic Block with Typed Variable
variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
}))
}
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}Why this works: Iterator uses ingress.value (named after the block label, not the variable). Typed input prevents runtime shape errors.
7. Provider Alias for Multi-Region
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "eu"
region = "eu-west-1"
}
resource "aws_s3_bucket" "replica" {
provider = aws.eu
bucket = "my-replica-bucket"
}
module "eu_network" {
source = "./modules/network"
providers = {
aws = aws.eu
}
}Why this works: Explicit alias keeps region intent clear. Modules receive providers via providers map, not implicit inheritance.
8. moved Block for Safe Rename
moved {
from = aws_kms_key.logs
to = aws_kms_key.audit
}Why this works: Keeps state continuity during naming refactors. Prevents unnecessary replacement that could destroy encryption keys.
Terraform Neutral Patterns (Context-Dependent)
Six patterns that are neither universally good nor bad — they depend on organizational context, team size, and governance maturity. The Terraform skill presents these as explicit tradeoffs rather than recommendations.
1. Workspace-Centric Environment Split
locals {
env = terraform.workspace
}
resource "aws_cloudwatch_log_group" "audit" {
name = "/org/${local.env}/audit"
}Tradeoff: Clean for workspace-managed workflows. Harder to reason about in ad-hoc CLI usage across many environments.
2. Single Repo with Many Modules
iac-repo/
modules/
network/
identity/
observability/
environments/
dev/
prod/Tradeoff: Easy discovery and shared standards. Larger blast radius for repo-level process changes.
3. Remote-State Bridge Across Stacks
data "terraform_remote_state" "platform" {
backend = "gcs"
config = {
bucket = "infra-state-org"
prefix = "platform/prod"
}
}Tradeoff: Quick integration path. Introduces coupling to producer stack internals.
4. Composite Module Owning Many Primitives
module "payments_platform" {
source = "./modules/payments-platform"
}Tradeoff: Simplifies root composition. Can become hard to evolve if internal boundaries are unclear.
5. Apply-Mode Native Tests in CI
run "database_contract" {
command = apply
}Tradeoff: Catches real runtime behavior. Increases cost and pipeline duration significantly.
6. Aggressive Precondition Usage
resource "aws_s3_bucket" "artifact" {
bucket = var.bucket_name
lifecycle {
precondition {
condition = startswith(var.bucket_name, "org-")
error_message = "Bucket names must start with org-."
}
}
}Tradeoff: Protects conventions early and enforces naming standards. Too many strict checks can reduce module reuse across different org units.
Terraform Blast Radius: Limiting Change Impact Scope
Blast radius problems occur when infrastructure stacks are too large or poorly isolated, causing small changes to have widespread, unintended impact across unrelated services.
Symptoms of Blast Radius Problems
- Tiny change triggers a very large plan
- Unrelated services share one state and fail together
- Production and non-production are entangled
- Review/approval ownership is unclear
Boundary Model
Split stacks along three axes:
| Axis | Question |
|---|---|
| Ownership | Who approves and supports this? |
| Change cadence | What changes together? |
| Recovery | What can fail together? |
If these differ between components, split the stack/state.
Architecture Patterns
| Pattern | Scope |
|---|---|
| Platform foundation stack | Network, identity, shared controls |
| Service stack(s) | One per business workload |
| Bootstrap stack | Backend/state prerequisites |
Do not combine all of them in one root.
State Isolation Rules
- One backend key per isolated stack/environment
- Dedicated lock scope per stack
- Backup/versioning required for production states
- No shared prod/non-prod state files
Environment Separation Options
1. Separate directories + separate backend keys (recommended default) 2. Workspace per environment (only with strict governance and access controls) 3. Separate repositories for hard regulatory segregation
Apply Governance
- Serialize applies to shared foundations
- Require explicit approval for production and destructive plans
- Block auto-apply for high-impact stacks
Example Directory Structure
infra/
bootstrap/
platform/
dev/
prod/
services/
billing/
dev/
prod/
catalog/
dev/
prod/LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Proposes one monolithic root for convenience
- Recommends workspace-only isolation without access controls
- Mixes blast radius discussion with purely stylistic concerns
- Omits rollback path for shared foundation changes
Verification Checks
Before applying, verify:
- Does the plan only touch the intended stack?
- Does the state key scope match the ownership boundary?
- Is the rollback path documented for this apply?
Terraform CI Drift: Preventing Pipeline Divergence
CI drift occurs when pipeline behavior diverges from local behavior or from reviewed intent. This leads to unreviewed applies, inconsistent infrastructure state, and broken trust in the delivery process.
Symptoms of CI Drift
- CI plan differs from local plan unexpectedly
- Apply occurs without using the reviewed plan artifact
- Provider/runtime drift appears between runs
- Scanner/policy stages are skipped on some paths
Root Causes
- Unpinned runtime/provider versions — different versions produce different plans
- Missing or stale lockfile — providers resolve differently across environments
- Apply re-runs plan — apply job runs
planagain instead of consuming the reviewed artifact - Inconsistent auth — different credentials between plan and apply stages
Drift Prevention Baseline
- Pin runtime and provider version ranges
- Commit lockfile and review lockfile changes
- Generate one reviewed plan artifact and apply exactly that artifact
- Run policy/security checks on every path to apply
- Enforce branch protections and environment approvals
Production-Ready GitHub Actions Template
name: terraform-delivery
on:
pull_request:
paths:
- '**/*.tf'
- '**/*.tfvars'
push:
branches: [main]
paths:
- '**/*.tf'
- '**/*.tfvars'
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
id-token: write
pull-requests: write
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check
- run: terraform init -backend=false
- run: terraform validate
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform show -json plan.bin > plan.json
- run: conftest test plan.json --policy policy/
- uses: actions/upload-artifact@v4
with:
name: reviewed-plan
path: plan.bin
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [validate]
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- uses: actions/download-artifact@v4
with:
name: reviewed-plan
- run: terraform init
- run: terraform apply -auto-approve plan.binNotes:
- Replace auth steps with OIDC/provider-specific login actions
- In real repos, split plan/apply workflows if artifact lifetime across events is an issue
Production-Ready GitLab CI Template
stages:
- validate
- plan
- policy
- apply
variables:
TF_IN_AUTOMATION: "true"
validate:
stage: validate
image: hashicorp/terraform:1.7
script:
- terraform fmt -check
- terraform init -backend=false
- terraform validate
plan:
stage: plan
image: hashicorp/terraform:1.7
script:
- terraform init
- terraform plan -out=plan.bin
- terraform show -json plan.bin > plan.json
artifacts:
paths:
- plan.bin
- plan.json
expire_in: 24h
policy:
stage: policy
image: openpolicyagent/conftest:latest
script:
- conftest test plan.json --policy policy/
dependencies:
- plan
apply:
stage: apply
image: hashicorp/terraform:1.7
when: manual
allow_failure: false
script:
- terraform init
- terraform apply -auto-approve plan.bin
dependencies:
- planLLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Missing lockfile strategy
- Apply without saved plan artifact
- No policy stage despite claiming compliance
- No branch/environment protection discussion
Quick Diagnostics
- Compare runtime versions local vs CI
- Diff lockfile in PR
- Ensure apply consumes
plan.binfrom the reviewed plan stage - Verify policy scanner runs on every apply path
Terraform Compliance Gates: Enforceable Controls and Evidence
Compliance gate gaps occur when frameworks are referenced by name but no enforceable controls or evidence artifacts actually exist. The Terraform skill treats compliance as delivery gates, not static documentation.
Core Principle
Every compliance framework mapping should translate into:
- Preventative controls — policy/validation that blocks non-compliant changes
- Detective controls — logging/monitoring that catches issues post-deploy
- Evidence artifacts — plans, approvals, audit records that prove compliance
Framework Starter Mappings
ISO 27001
Focus: Formal ISMS governance, access control and change management, incident response and evidence retention.
IaC gate examples:
- Mandatory change approval records
- Encryption and logging policy checks
- Periodic access review evidence from CI/CD systems
SOC 2
Focus: Security, availability, confidentiality controls.
IaC gate examples:
- Least-privilege IAM enforcement
- Transport/at-rest encryption checks
- Audit logging enabled on critical services
FedRAMP
Focus: Strict baseline controls, boundary protection, continuous monitoring (when US federal workloads apply).
IaC gate examples:
- Region/service allowlists for authorized environments
- Hardened network segmentation policies
- Continuous scan artifacts attached to each release
GDPR
Focus: Data protection by design, minimization, lawful processing support (when processing EU personal data).
IaC gate examples:
- Data residency constraints via policy
- Retention/lifecycle enforcement for personal data stores
- Access logging for data systems with evidence retention
PCI DSS
Focus: Segmentation, key management, hardening, monitoring (when cardholder data environment exists).
IaC gate examples:
- Deny public exposure of CDE components
- No default credentials
- Strong encryption and key rotation controls
HIPAA
Focus: Confidentiality/integrity of ePHI, auditability, access controls (when handling protected health information).
IaC gate examples:
- Private network boundaries for ePHI systems
- Immutable audit trails for infra changes
- Backup/retention and recovery controls
Policy-as-Code Gate Pattern
| Stage | Tool/Action | Purpose |
|---|---|---|
| Stage 1 | Static scanning (tfsec, checkov) | Catch common misconfigurations |
| Stage 2 | Plan policy checks (Sentinel/OPA/Conftest) | Enforce organizational policies |
| Stage 3 | Approval workflow by risk class | Human oversight for high-impact changes |
| Stage 4 | Evidence archival | Retain plan, policy result, approver identity |
Risk-Classed Approval Model
| Risk Level | Required Approval |
|---|---|
| Low | One maintainer approval |
| Medium | Platform owner + service owner approval |
| High (identity/network/encryption/state) | Security or compliance sign-off required |
Minimal Evidence Checklist
For each production apply, retain:
- Reviewed plan artifact and hash
- Policy scan output
- Approver identity and timestamp
- Runtime/provider versions
- Post-apply verification logs
LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Mentions framework names but gives no enforceable gates
- Confuses security best practices with compliance evidence
- Omits who approves what risk class
- Ignores data-residency obligations for GDPR/FedRAMP-like contexts
Terraform Identity Churn: Preventing Resource Address Instability
Identity churn is one of the most common and dangerous Terraform failure modes. It occurs when resource addresses or object identity shift unexpectedly, causing destroy/create cycles that can take down production infrastructure.
Symptoms of Identity Churn
- Plan shows broad replace actions after small list edits
- Renaming resources/modules triggers destroy/create instead of in-place updates
- Refactor from
counttofor_eachcauses churn - Imported resources keep drifting because addressing is unstable
Primary Causes
- Index-based identity (
count) used for long-lived logical objects - Unstable keys derived from sorted lists or transient IDs
- Missing `moved` blocks during refactors
- Hidden dependencies forcing replacement chains
- `for_each` keys derived from values unknown at plan time
Prevention Rules
- Use
for_eachfor long-lived identities - Choose stable keys from business identity (e.g.,
zone-a,payments-api) - Keep identity attributes separate from mutable attributes
- Add
movedblocks before first apply after rename/restructure
Decision Matrix: count vs for_each
Use count only when:
- Resource is truly optional singleton (
0or1) - No downstream references depend on stable per-item addresses
Use for_each when:
- Multiple logical instances are expected
- Insertion/removal/reordering happens over time
- Downstream references need stable keys
- Keys are fully known during planning
When keys are unknown at plan time:
- Drive
for_eachfrom known input keys - Use
countfor conditional/singleton creation when key-stablefor_eachis not possible
Safe Migration: count to for_each
1. Define stable key map 2. Refactor resource to for_each 3. Add one moved block per old index 4. Verify plan reports move operations, not replace 5. Apply in lower environment first
Example
locals {
app_subnets = {
a = { cidr = "10.40.1.0/24", az = "us-east-1a" }
b = { cidr = "10.40.2.0/24", az = "us-east-1b" }
}
}
resource "aws_subnet" "app" {
for_each = local.app_subnets
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
tags = {
Name = "app-${each.key}"
}
}
moved {
from = aws_subnet.app[0]
to = aws_subnet.app["a"]
}
moved {
from = aws_subnet.app[1]
to = aws_subnet.app["b"]
}Rename Playbook
When renaming resource/module labels, always add moved first:
moved {
from = module.network_core
to = module.network_foundation
}Known-at-Plan Failure Pattern
Bad — key depends on apply-time value:
resource "aws_security_group_rule" "egress" {
for_each = toset([aws_security_group.ecs.id])
type = "egress"
from_port = 443
to_port = 443
protocol = "tcp"
security_group_id = each.value
cidr_blocks = ["0.0.0.0/0"]
}Safer fallback for optional singleton behavior:
resource "aws_security_group_rule" "egress" {
count = var.enable_egress_rule ? 1 : 0
type = "egress"
from_port = 443
to_port = 443
protocol = "tcp"
security_group_id = aws_security_group.ecs.id
cidr_blocks = ["0.0.0.0/0"]
}LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Defaults to
countfor every collection - Omits
movedblocks in refactors - Uses list index as identity key
- Suggests
terraform state mvin automation wheremovedis safer and reviewable - Builds
for_eachkeys from computed IDs not known until apply
Verification Commands
terraform fmt -check
terraform validate
terraform plan -out=plan.bin
terraform show plan.bin | grep -i movedOpenTofu equivalent:
tofu fmt -check
tofu validate
tofu plan -out=plan.bin
tofu show plan.bin | grep -i movedTerraform Secret Exposure: Preventing Credential Leaks
Secret exposure occurs when credentials, tokens, or sensitive data leak into Terraform state files, CI logs, plan output, variable defaults, or artifact storage. This is a critical security failure mode.
Symptoms of Secret Exposure
- Secret values appear in plan output or logs
- Credentials are defined in variable defaults
- Sensitive outputs are printed in CI
- Generated passwords are stored in state unintentionally
Exposure Paths
- Hardcoded defaults in
variables.tf - State persistence — secret-bearing resources whose values are persisted in state
- Logging —
terraform showoutputs without redaction - Artifact retention — policies that keep plan/state exports too long
Prevention Baseline
- Never set secret defaults in code
- Source secrets from managed secret stores at runtime
- Mark secret variables and outputs as
sensitive = true - Restrict state backend access aggressively
- Avoid publishing raw plan JSON as broadly accessible artifact
Runtime Patterns (Preferred Order)
1. External secret manager lookup (AWS Secrets Manager, HashiCorp Vault, etc.) 2. Workload identity federation for providers 3. Short-lived credentials from trusted broker
Avoid long-lived static credentials in repository or runner config.
Understanding sensitive and write_only
sensitive = truemasks display but the value can still exist in state depending on provider behaviorwrite_onlyarguments (where supported) reduce state persistence risk- Always verify provider docs before assuming secret material is excluded from state
Good Example: Secret Manager Integration
variable "db_admin_username" {
description = "Database admin user"
type = string
}
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/admin"
}
resource "aws_db_instance" "core" {
identifier = "core-db-prod"
username = var.db_admin_username
password = jsondecode(
data.aws_secretsmanager_secret_version.db_password.secret_string
)["password"]
}Bad Example: Plaintext Default
variable "db_password" {
type = string
default = "ChangeMe123!" # NEVER do this
}Rotation Playbook
1. Create new secret version in manager 2. Update application to consume new version 3. Roll infrastructure safely 4. Revoke old credential 5. Verify no leaked copies remain in logs/artifacts
LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Assumes
sensitivealone means "not in state" - Proposes plaintext defaults for demo convenience
- Uses outputs that expose full connection strings in PR comments
- Forgets artifact retention and access controls in CI
Verification Commands
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
# Ensure secret fields are not emitted to shared artifacts/logsInstalling the Terraform Skill
TerraShark can be installed in three ways depending on your environment: direct clone, marketplace, or per-project for Codex.
Option 1: Clone to Skills Directory (Recommended)
The simplest method. Clone the repository into Claude Code's skills directory.
macOS / Linux
git clone https://github.com/LukasNiessen/terrashark.git ~/.claude/skills/terrasharkWindows (PowerShell)
git clone https://github.com/LukasNiessen/terrashark.git "$env:USERPROFILE\.claude\skills\terrashark"Windows (Command Prompt)
git clone https://github.com/LukasNiessen/terrashark.git "%USERPROFILE%\.claude\skills\terrashark"Claude Code auto-discovers skills in ~/.claude/skills/ — no restart needed.
Option 2: Marketplace Install
Claude Code has a built-in plugin system with marketplace support. Add TerraShark directly from the CLI:
/plugin marketplace add LukasNiessen/terrashark
/plugin install terrasharkOr use the interactive plugin manager: 1. Run /plugin 2. Switch to the Discover tab 3. Install TerraShark from there
The marketplace reads the .claude-plugin/marketplace.json in the repository to register TerraShark as an installable plugin.
Option 3: Codex (Per-Project Setup)
Codex has no global skill system — setup is per-project. Clone TerraShark into your repository and reference it from your AGENTS.md:
# Clone into your project root
git clone https://github.com/LukasNiessen/terrashark.git .terrasharkThen add to your AGENTS.md (or create one in the repo root):
## Terraform
When working with Terraform or OpenTofu, follow the workflow in `.terrashark/SKILL.md`.
Load references from `.terrashark/references/` as needed.Updating the Terraform Skill
To update to the latest version, pull the latest changes:
cd ~/.claude/skills/terrashark
git pull origin mainVerifying the Installation
After installing, test it by asking Claude Code any Terraform question:
/terrashark Create a multi-region S3 module with replicationOr ask naturally — the Terraform skill activates automatically for any Terraform/OpenTofu task:
Review my main.tf for security issuesThe response should follow the 7-step failure-mode workflow and include an output contract with assumptions, tradeoffs, and rollback notes.
System Requirements
- Claude Code or Codex with skill support
- Git for cloning the repository
- No additional dependencies required — the skill is pure Markdown
Terraform Skill Quick Start Guide
Get productive with the Terraform skill in under 2 minutes.
Step 1: Install
git clone https://github.com/LukasNiessen/terrashark.git ~/.claude/skills/terrasharkStep 2: Use It
Explicit Invocation
Use the /terrashark command to explicitly invoke the Terraform skill:
/terrashark Create a multi-region S3 module with replication/terrashark Refactor our EKS stack into separate state files per environment, add moved blocks to avoid recreation, set up a GitHub Actions pipeline with plan on PR and gated apply on merge, and wire in Checkov for compliance scanningAutomatic Activation
The Terraform skill activates automatically for any Terraform/OpenTofu task. Just ask naturally:
Review my main.tf for security issuesMigrate this module from count to for_eachSet up a CI pipeline for our Terraform modulesWhat to Expect
Every TerraShark response follows the 7-step workflow and includes a structured output:
1. Assumptions — what the skill assumed about your environment and versions 2. Failure modes — which risks were identified (identity churn, secret exposure, etc.) 3. Remediation — what was recommended and what tradeoffs were made 4. Validation plan — how to verify the output before applying 5. Rollback notes — how to undo changes if something goes wrong
Example Tasks
Here are some common tasks the Terraform skill excels at:
| Task | Example Prompt |
|---|---|
| Module creation | "Create an AWS VPC module with public and private subnets" |
| Security review | "Review my Terraform for secret exposure risks" |
| Migration | "Migrate from count to for_each for my subnet resources" |
| CI/CD setup | "Set up GitHub Actions for Terraform with plan on PR and apply on merge" |
| Compliance | "Add SOC 2 compliance gates to our Terraform pipeline" |
| Refactoring | "Split this monolithic root into service stacks with blast radius isolation" |
| Troubleshooting | "Why does my plan show replacements after renaming a module?" |
Next Steps
- Read about the 7-Step Workflow to understand how the skill processes tasks
- Explore the Five Failure Modes that drive every diagnostic
- Check the Good Patterns and Bad Patterns for reference
Terraform CI/CD Delivery Patterns
This guide covers implementing auditable Terraform and OpenTofu delivery pipelines with GitHub Actions, GitLab CI, Atlantis, and Infracost.
Delivery Principles
- Plan and apply are separate concerns
- Apply must consume the reviewed plan artifact when architecture permits
- Policy and security checks run on every apply path
- Production applies require environment protection and approvals
Baseline Stages
Every production Terraform pipeline should include:
1. fmt + validate 2. Lint + security scan 3. Plan creation 4. Policy checks against plan JSON 5. Approval gate 6. Apply from trusted branch/runner 7. Post-apply drift and evidence capture
GitHub Actions Template
name: terraform-delivery
on:
pull_request:
paths:
- '**/*.tf'
- '**/*.tfvars'
workflow_dispatch:
push:
branches: [main]
paths:
- '**/*.tf'
- '**/*.tfvars'
permissions:
contents: read
id-token: write
pull-requests: write
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false
env:
TF_IN_AUTOMATION: "true"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check
- run: terraform init -backend=false
- run: terraform validate
plan:
if: github.event_name == 'pull_request'
needs: [validate]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform show -json plan.bin > plan.json
- run: conftest test plan.json --policy policy/
- uses: actions/upload-artifact@v4
with:
name: reviewed-plan
path: |
plan.bin
plan.json
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
needs: [validate]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform apply -auto-approve plan.binNotes:
- Configure provider auth with OIDC (avoid static cloud keys)
- For strict "apply reviewed PR plan" semantics, keep plan/apply in same workflow run or externalize signed plan storage
GitLab CI Template
stages:
- validate
- plan
- policy
- apply
- verify
variables:
TF_IN_AUTOMATION: "true"
validate:
stage: validate
image: hashicorp/terraform:1.7
script:
- terraform fmt -check
- terraform init -backend=false
- terraform validate
plan:
stage: plan
image: hashicorp/terraform:1.7
script:
- terraform init
- terraform plan -out=plan.bin
- terraform show -json plan.bin > plan.json
artifacts:
paths: [plan.bin, plan.json]
expire_in: 24h
policy:
stage: policy
image: openpolicyagent/conftest:latest
dependencies: [plan]
script:
- conftest test plan.json --policy policy/
apply:
stage: apply
image: hashicorp/terraform:1.7
dependencies: [plan]
when: manual
allow_failure: false
script:
- terraform init
- terraform apply -auto-approve plan.bin
verify:
stage: verify
image: hashicorp/terraform:1.7
script:
- terraform plan -detailed-exitcodeAtlantis (PR-Driven Delivery)
Use Atlantis for chat-driven, PR-scoped plan/apply with locking.
version: 3
projects:
- name: platform
dir: .
workspace: default
autoplan:
enabled: true
when_modified: ["**/*.tf", "**/*.tfvars"]
workflow: default
workflows:
default:
plan:
steps:
- init
- plan
apply:
steps:
- applyHardening notes:
- Restrict apply to approved PRs and protected branches
- Enable Atlantis server-side locking
- Use custom workflows to add policy checks and cost steps
- Keep CI auth in OIDC where supported; avoid static secrets
Infracost (Cost Visibility)
Surface cost deltas from plan JSON in PRs:
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
infracost breakdown --path plan.json --format json --out-file infracost.jsonStore plan.json and infracost.json as artifacts for auditability. Treat cost checks like policy checks for high-risk environments.
Pipeline Hardening Checklist
- Enforce branch protection on default branch
- Require CODEOWNERS review for prod-impacting paths
- Restrict apply jobs to protected runners
- Set artifact retention + access policies
- Preserve audit trail (approver, actor, commit, runtime version)
Cost and Speed Controls
- Run expensive integration suites only for IaC path changes
- Serialize shared-foundation applies
- Use provider plugin cache where supported
- Schedule cleanup for ephemeral test environments
Terraform Migration Playbooks
Five dedicated playbooks for safely migrating Terraform resources without causing identity churn, data loss, or downtime.
Playbook 1: count to for_each Migration
Goal: Keep object identity stable during refactor.
Steps
1. Define stable keys (not list indexes) 2. Add for_each implementation 3. Add moved mappings from old index addresses to new keyed addresses 4. Run plan and confirm move operations (not destroy/create) 5. Apply in low-risk environment first
Example Mapping
moved {
from = aws_subnet.app[0]
to = aws_subnet.app["a"]
}
moved {
from = aws_subnet.app[1]
to = aws_subnet.app["b"]
}Playbook 2: Resource/Module Rename
Use moved for address renames before any apply.
moved {
from = module.edge_cache
to = module.cdn_edge
}Playbook 3: Import-First Adoption
When taking over manually created resources:
1. Confirm remote object exactly matches intended config shape 2. Import into correct address 3. Run plan and ensure no surprise replacements
Declarative import Block (TF 1.5+ / OpenTofu 1.5+)
Prefer declarative import blocks over CLI terraform import:
import {
to = aws_s3_bucket.logs
id = "my-existing-bucket-name"
}
resource "aws_s3_bucket" "logs" {
bucket = "my-existing-bucket-name"
}Bulk Import with for_each
locals {
existing_buckets = {
logs = "prod-logs-bucket"
archive = "prod-archive-bucket"
}
}
import {
for_each = local.existing_buckets
to = aws_s3_bucket.managed[each.key]
id = each.value
}
resource "aws_s3_bucket" "managed" {
for_each = local.existing_buckets
bucket = each.value
}Post-Import Checklist
- Run
terraform planand verify zero changes (no-diff) - If plan shows changes, align config with actual state before applying
- Remove
importblocks after successful apply (they are one-time directives)
Playbook 4: Secrets Remediation
If secrets are currently in state:
1. Create new secret path in managed secret store 2. Switch resources to reference external secret material 3. Rotate credentials after cutover 4. Remove old secret-generating Terraform resources where possible
Playbook 5: Runtime/Provider Upgrade Flow
1. Bump constraints intentionally 2. Regenerate lockfile 3. Run full test tier for target risk 4. Inspect deprecations and behavior shifts 5. Ship upgrade independently from functional changes when possible
Migration Red Flags
Watch for these warning signs during any migration:
- Plan shows broad replace for unrelated resources
- Key changes derived from unstable list order
- Unknown ownership of imported resources
- No rollback narrative for production apply
Terraform Quick Ops and Troubleshooting
Fast command recall and common failure handling for Terraform and OpenTofu operations.
Core Command Sequence
Terraform
terraform fmt -check
terraform init
terraform validate
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.jsonOpenTofu
tofu fmt -check
tofu init
tofu validate
tofu plan -out=plan.bin
tofu show -json plan.bin > plan.jsonCommon Failures and Fixes
CI Passes Locally but Fails in Runner
Causes:
- Mismatch in runtime/provider versions
- Missing lockfile updates
- Environment variables present locally but missing in CI
Fix:
- Pin runtime and providers
- Commit lockfile
- Make required env vars explicit in pipeline
Large Unexpected Replacements in Plan
Causes:
- Unstable iteration keys
- Hidden rename without
movedmapping - Data source drift feeding identity fields
Fix:
- Stabilize keys
- Add
movedblocks - Separate identity from mutable attributes
AWS RDS Identifier Validation Errors
Symptoms: InvalidParameterValue for identifier or final_snapshot_identifier, names include dots.
Fix: Normalize to lowercase letters, numbers, and hyphens:
locals {
rds_base = regexreplace(lower("${var.project}-prod"), "[^a-z0-9-]", "-")
}
resource "aws_db_instance" "main" {
identifier = local.rds_base
final_snapshot_identifier = "${local.rds_base}-final"
}Apply Contention on Shared State
Cause: Concurrent pipelines targeting same backend key.
Fix:
- Serialize applies for that stack
- Use lock timeout and per-stack concurrency guard
Tests Are Too Costly
Fix:
- Tag tests by risk (
fast,integration,destructive) - Run full suite nightly, risk-tier suite on PRs
- Auto-clean ephemeral infra with TTL tags
State Lock Stuck
Symptom: Error: Error acquiring the state lock
Fix:
# Identify the lock holder from the error message (lock ID shown)
terraform force-unlock LOCK_ID
# OpenTofu equivalent:
tofu force-unlock LOCK_IDOnly force-unlock when you are certain no other apply is running. Check CI pipelines and team activity first.
State Corruption or Lost State
Fix:
- Restore from versioned state backend (S3 versioning, GCS versioning)
- If no backup: re-import resources using
importblocks - Never manually edit state JSON unless absolutely no alternative and with peer review
# Pull current state for inspection
terraform state pull > state-backup.json
# List all tracked resources
terraform state listBackend Migration
When changing state backends (e.g., local to S3, or S3 to different bucket):
# Update backend config in code, then:
terraform init -migrate-state- Always backup state before migration
- Verify resource count matches after migration
- Test plan shows no changes after migration
Provider Authentication Failures in CI
Symptom: Error: No valid credential sources found
Fix:
- Verify environment variables are set in CI runner
- Prefer workload identity federation over static keys
- Check credential expiry for short-lived tokens
- Ensure CI runner IAM role/service account has required permissions
null_resource vs terraform_data
Use terraform_data (TF 1.4+) instead of null_resource + null provider:
# Prefer this (no extra provider needed):
resource "terraform_data" "bootstrap" {
triggers_replace = [var.config_hash]
provisioner "local-exec" {
command = "bootstrap.sh"
}
}Terraform Security and Governance
This guide covers security controls for Terraform and OpenTofu delivery pipelines. For framework-specific compliance mappings and evidence gates, see Compliance Gates.
Identity Controls
- Least privilege for CI identities
- Separate
planandapplyroles where possible - Short-lived credentials via workload identity federation
- Deny direct human write access to production backends
Secret Controls
- Prohibit plaintext secret defaults in code
- Source sensitive values from managed secret stores
- Mark secret variables and outputs as sensitive
- Sanitize logs/artifacts and restrict access
Supply-Chain Controls
- Pin provider/module versions with bounded constraints
- Commit lockfile and review lockfile diffs
- Verify action/container versions in CI workflows
Policy Layers
Use layered controls, not single-tool reliance:
| Layer | Tool/Approach | Purpose |
|---|---|---|
| Static scanning | tfsec, checkov, equivalent | Catch common misconfigurations early |
| Plan-policy checks | Sentinel, OPA, Conftest | Enforce organizational policies on plan output |
| Approval gates | By risk class | Human oversight for high-impact changes |
High-Impact Change Controls
Require elevated approval for:
- IAM privilege expansion
- Network exposure/public ingress changes
- Encryption disablement/key-policy weakening
- Backend/state changes
- Production replacement/destruction actions
Minimal OPA Example
package main
deny[msg] {
r := input.resource_changes[_]
r.type == "aws_security_group_rule"
r.change.after.cidr_blocks[_] == "0.0.0.0/0"
r.change.after.from_port == 22
msg := sprintf("Public SSH is not allowed: %s", [r.address])
}Operational Governance
- Serialize applies for shared foundations
- Require explicit opt-in for destroy
- Keep break-glass runbook and test it periodically
- Retain run metadata and policy outputs for auditability
Terraform Testing Matrix
This guide covers choosing testing depth proportional to risk and cost for Terraform and OpenTofu modules.
Testing Layers
1. Static checks: format, validate, lint, security scan 2. Plan checks: reviewed execution intent 3. Native tests: module-level assertions (terraform test / tofu test) 4. Integration tests: ephemeral apply + live assertions 5. Terratest: workflow/system validation in Go for complex scenarios
Tier A: All Changes (Minimum)
Required for every change:
fmt -checkinit -backend=false+validate- Lint + security scan
- Reviewed plan artifact
Use for low-risk isolated updates.
Tier B: Shared Modules and Medium-Risk Changes
Add on top of Tier A:
- Native test runs for module behavior
- Targeted integration apply tests in ephemeral environment
- Policy checks on plan JSON
Typical triggers:
- Shared module changes
- IAM/network updates
- Encryption/data-boundary updates
Tier C: High-Risk Production Changes
Add on top of Tier A + B:
- Staged rollout (dev -> stage -> prod)
- Rollback rehearsal or documented rollback proof
- Manual owner approvals + security/compliance sign-off
- Post-apply drift detection
Typical triggers:
- State backend migration
- Major refactor with address changes
- Foundational platform stack changes
Native Test Guidance
When to Use command = plan
- Input validation
- Static contract checks
- Argument shape assertions not relying on computed runtime values
When to Use command = apply
- Computed attributes known only after creation
- Assertions over provider-populated fields
- Set/list semantics unresolved in plan stage
Frequent Pitfalls
- Asserting unknown values in plan mode
- Indexing set-type blocks directly
- Assuming mocked providers equal integration confidence
Terratest Guidance
Use Terratest when native tests are insufficient:
- Cross-module workflows
- External API verification (health checks, connectivity)
- Failover/disaster-recovery scenarios
- Multi-step lifecycle tests (apply-change-destroy)
Terratest Test Pyramid
- Fast: contract tests (mocked or plan-level)
- Medium: environment integration tests (ephemeral)
- Slow: limited end-to-end smoke tests for critical paths
Cost Controls
- Tag tests by class (
unit,integration,destructive) - Parallelize only isolated stacks
- Auto-clean resources with TTL tags
- Run expensive tests nightly and on protected branches
Test Framework Scaffolding
.
test/
terratest/
go.mod
helpers/
examples/
network_test.go
native/
main.tftest.hcl
MakefileMinimal Makefile targets:
test-native:
terraform test
test-terratest:
go test ./test/terratest -timeout 45mExample Command Flow
terraform fmt -check
terraform init -backend=false
terraform validate
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
conftest test plan.json --policy policy/
terraform testTerratest stage:
go test ./test -run TestCriticalPath -timeout 45mQuick Selection Rules
| Change Type | Testing Tier |
|---|---|
| Tiny tag change in isolated stack | Tier A |
| Module contract change | Tier A + Tier B |
Refactor with moved and shared impact | Tier A + B + targeted Terratest |
| Production identity/network/encryption/state changes | Tier A + B + C + Terratest smoke |
Done Criteria
Not done until:
- Required tier passes
- Reviewed plan is approved
- Apply path is trusted and auditable
- Evidence artifacts are retained
MCP Integration with the Terraform Skill
This guide covers how to safely use MCP (Model Context Protocol) servers to supply trusted context during Terraform and OpenTofu work.
When to Use MCP
- Fetch authoritative provider or platform facts for the current environment
- Read organization-specific standards, naming rules, or guardrails
- Pull inventory or baseline state summaries when local context is missing
What MCP Should Not Do
- Do not retrieve or transmit plaintext secrets
- Do not treat MCP responses as change authorization
- Do not use MCP to bypass review or approval controls
Safe Integration Pattern
1. Query MCP for environment facts and constraints 2. Compare with local inputs and repo defaults 3. Emit assumptions explicitly if MCP data is partial 4. Preserve least-privilege access and log sources used
Output Hygiene
- Quote MCP-derived values as inputs, not hard-coded defaults
- Keep environment-specific data out of reusable primitives
- Record MCP-provided versions or IDs in notes for traceability
Example Uses
- Resolve account or project IDs for the target environment
- Confirm region allow-lists and data residency boundaries
- Retrieve approved module registry versions or constraints
Failure Handling
- If MCP is unavailable, proceed with explicit assumptions
- Avoid speculative values for IDs, names, or policy constraints
- Request confirmation before emitting high-impact changes
{
"name": "terrashark-docs",
"version": "2.3.0",
"description": "TerraShark documentation - Terraform Skill for Claude Code",
"private": true,
"scripts": {
"build": "honkit build",
"serve": "honkit serve"
},
"devDependencies": {
"honkit": "^6.1.7",
"gitbook-plugin-search-pro": "^2.0.2"
}
}
Terraform Skill for Claude Code and Codex: TerraShark
TerraShark is a lean, failure-mode-first Terraform skill for Claude Code and Codex. It prevents Terraform and OpenTofu hallucinations by forcing the AI to diagnose risks before generating code.
Why Use a Terraform Skill?
LLMs hallucinate frequently when generating Terraform code. They produce configurations that are syntactically valid but operationally dangerous — unstable resource identities, leaked secrets, oversized blast radii, CI drift, and missing compliance gates. TerraShark fixes this by embedding a 7-step diagnostic workflow and 19 granular reference files directly into the AI's context.
Key Features
- Failure-mode-first architecture — diagnoses risks before generating code
- ~600 token activation cost — over 7x leaner than alternatives
- 19 granular reference files — loads only what's relevant per query
- LLM-specific guardrails — explicitly names and prevents common AI hallucination patterns
- Output contracts — every response includes assumptions, tradeoffs, and rollback notes
- 5 migration playbooks — safe count-to-for_each, rename, import, secrets, and upgrade flows
- Compliance framework mappings — ISO 27001, SOC 2, FedRAMP, GDPR, PCI DSS, HIPAA
- Production CI/CD templates — GitHub Actions, GitLab CI, Atlantis, Infracost
- Based on HashiCorp's official best practices — prioritizes HashiCorp recommended practices when guidance conflicts
How It Works
When Claude Code encounters a Terraform or OpenTofu task, the Terraform skill activates and runs a 7-step workflow:
1. Capture execution context — runtime, version, providers, backend, risk level 2. Diagnose failure modes — identity churn, secret exposure, blast radius, CI drift, compliance gaps 3. Load relevant references — pull only the targeted guidance needed 4. Propose fix path — include risk notes, approvals, tests, and rollback expectations 5. Generate artifacts — HCL changes, migration blocks, CI/policy updates 6. Validate — runtime-appropriate command sequence and risk-tier checks 7. Deliver output contract — assumptions, remediation, tradeoffs, validation plan, recovery notes
Quick Install
# macOS / Linux
git clone https://github.com/LukasNiessen/terrashark.git ~/.claude/skills/terrashark
# Windows (PowerShell)
git clone https://github.com/LukasNiessen/terrashark.git "$env:USERPROFILE\.claude\skills\terrashark"That's it. Claude Code auto-discovers skills in ~/.claude/skills/ — no restart needed.
Supported Runtimes
- Terraform (all versions, with feature guards for version-specific capabilities)
- OpenTofu (all versions, with equivalent command mappings)
License
MIT License — see GitHub repository for details.
Summary
- Introduction
Getting Started
- Installation
- Quick Start
Core Concepts
- The 7-Step Terraform Skill Workflow
- Five Failure Modes
- Design Philosophy
Terraform Failure Mode References
- Identity Churn
- Secret Exposure
- Blast Radius
- CI Drift
- Compliance Gate Gaps
Terraform Architecture Guidance
- Structure and State
- Backend State Safety
- Module Architecture
- Coding Standards
Terraform Operational Guides
- Migration Playbooks
- Testing Matrix
- CI/CD Delivery Patterns
- Security and Governance
- Quick Ops and Troubleshooting
Terraform Code Examples
- Good Patterns
- Bad Patterns (Anti-Patterns)
- Neutral Patterns (Context-Dependent)
- Do and Don't Checklist
Integrations
- MCP Integration
Advanced
- Token Efficiency Strategy
- Comparison with Other Terraform Skills
Community
- Contributing
- Changelog
Copyright 2025 Lukas Niessen <lks.niessen@gmail.com> https://lukasniessen.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Philosophy
This document describes TerraShark's design strategy, architectural decisions, and the empirical process behind its content.
Failure-Mode-First Architecture
TerraShark is built around a single insight: telling an LLM what good Terraform looks like is less effective than telling it how to think about Terraform problems.
The core SKILL.md is not a reference manual. It is a 7-step operational workflow:
1. Capture execution context -- runtime, version, providers, backend, execution path, criticality 2. Diagnose likely failure modes -- identity churn, secret exposure, blast radius, CI drift, compliance gate gaps 3. Load only relevant references -- pull targeted guidance, not everything 4. Propose fix path with risk controls -- why this works, what could go wrong, guardrails 5. Generate implementation artifacts -- HCL, migration blocks, CI updates, compliance controls 6. Validate before finalize -- command sequence tailored to runtime and risk tier 7. Output contract -- assumptions, tradeoffs, validation plan, rollback notes
The model diagnoses before it generates. This prevents the most common failure pattern in LLM-assisted IaC: producing syntactically valid but operationally dangerous code.
Token Efficiency
Context window space is a finite resource. Every token spent on skill content is a token unavailable for the user's actual codebase, conversation history, and tool results.
TerraShark is designed for minimal activation cost. The core SKILL.md is 86 lines (~600 tokens). It contains no HCL examples, no inline code blocks, no tutorial material. It is purely procedural: a workflow the model follows.
Depth lives in 19 granular reference files. The model loads only the 1-2 files relevant to the diagnosed failure mode. A query about secret handling never loads the CI delivery patterns. A query about module architecture never loads the compliance gates.
This granularity matters. A single large reference file forces the model to process thousands of irrelevant tokens. 19 small files let it load precisely what it needs.
LLM-Aware Guardrails
Every reference file that covers a risk domain includes an LLM mistake checklist -- a list of specific errors that language models make when generating Terraform code. Examples:
- Defaulting to
countinstead offor_eachfor collections - Omitting
movedblocks during refactors, causing destroy/create cycles - Using
sensitiveand assuming the value is safe from state - Proposing plaintext credential defaults "for demo purposes"
- Recommending CLI-only
terraform importinstead of declarative import blocks
These checklists exist because the model needs to know what it gets wrong, not just what is correct. A reference that only shows the right pattern still allows the model to hallucinate the wrong one. A reference that explicitly names the hallucination pattern reduces it.
The Feature guard table in coding-standards.md takes this further: it maps Terraform features to their minimum version and the specific LLM error pattern associated with each. This lets the model check whether a feature is available for the target runtime before emitting it.
Output Contracts
Every TerraShark response includes a structured output contract:
- Assumptions and version floor -- what the model assumed about the environment
- Selected failure modes -- which risks were diagnosed
- Chosen remediation and tradeoffs -- what was recommended and what was traded off
- Validation/test plan -- how to verify the output
- Rollback/recovery notes -- how to undo if something goes wrong
This makes outputs auditable. A reader can check whether the model's assumptions were correct, whether the right failure modes were identified, and whether the rollback path is viable -- before applying anything.
Reference Granularity
The 19 reference files are organized by concern, not by Terraform concept:
Primary failure modes (loaded when the failure mode is diagnosed):
- Identity churn, secret exposure, blast radius, CI drift, compliance gates
Structural guidance (loaded when designing, refactoring, or changing backends):
- Structure and state, backend state safety, module architecture, coding standards
Operational references (loaded for specific tasks):
- Migration playbooks, testing matrix, CI delivery patterns, security and governance, quick ops
Pattern banks (loaded for review or teaching):
- Good examples, bad examples, neutral examples, do/don't patterns
Integration and meta:
- MCP integration, token balance rationale
Each file is self-contained. No file depends on another file being loaded simultaneously.
Failure Modes as First-Class Concepts
TerraShark names five failure modes explicitly:
1. Identity churn -- resource addressing instability, refactor breakage, index-based identity 2. Secret exposure -- secrets leaking through state, logs, defaults, or artifacts 3. Blast radius -- oversized stacks, weak boundaries, unsafe production applies 4. CI drift -- version mismatches, unreviewed applies, missing plan artifacts 5. Compliance gate gaps -- missing policies, approvals, audit controls, evidence
These are not arbitrary categories. They represent the five most common ways LLM-generated Terraform causes real damage. Every piece of content in the skill maps to at least one of these failure modes. Content that does not reduce the probability of any failure mode is excluded.
Deep Hierarchy Model
For platform engineering at scale, TerraShark defines a 5-level module hierarchy:
- L0 primitives -- one resource family, strict contract
- L1 composites -- capability units built from primitives
- L2 domain stacks -- bounded business domains (payments, identity, observability)
- L3 environment roots -- env-specific wiring and configuration
- L4 org orchestration -- account/project vending and shared policy baselines
Dependencies flow downward only. No lateral imports across the same level without an explicit interface contract. Each level owns its state boundary and apply lifecycle.
Content Inclusion Rules
Content enters TerraShark only when at least one condition is met:
1. It materially lowers the probability of destructive or non-compliant changes 2. It prevents common plan/apply surprises (identity churn, drift, unsafe upgrades) 3. It encodes organizational guardrails that general model knowledge cannot infer
Content is excluded when:
1. It is generic Terraform/OpenTofu knowledge with low failure impact 2. It is provider-specific deep design that belongs in project docs 3. It duplicates an existing rule without adding a new decision signal
If repeated failure patterns emerge, targeted lines are added for that specific failure mode instead of broad expansion.
---
Token Experiment
The content in TerraShark was not designed by intuition. It was empirically tested.
Process
1. Started large. The initial reference content was significantly larger -- broader coverage, more examples, more inline explanations, more tutorial-style material.
2. Built an automated test suite. A large set of practical Terraform/OpenTofu task patterns was assembled: module creation, refactoring, CI pipeline setup, migration, security review, compliance checks, and others.
3. Measured baseline quality. The full-size skill was run against the test suite. Output quality was scored across correctness, safety, completeness, and hallucination rate.
4. Stripped content iteratively. Sections were removed one at a time. After each removal, the full test suite was re-run.
5. Measured quality impact. For each removal:
- If quality dropped: the content was restored. It carried signal the model needed.
- If quality stayed stable: the content was permanently removed. It was redundant with the model's existing knowledge.
6. Converged on current size. The process continued until every remaining section was load-bearing -- removing any further content caused measurable quality degradation.
What Survived
The content that survived the stripping process reveals what models actually need help with versus what they already know:
- Module role boundaries and composition rules -- models struggle with when to split modules, how deep hierarchies should work, and where business logic belongs
- Migration playbooks -- models frequently omit
movedblocks, mishandlecount-to-for_eachtransitions, and skip import strategies - Native test caveats -- models incorrectly index set-type blocks, assert computed values in plan mode, and treat mocked providers as integration coverage
- CI delivery templates -- models produce CI pipelines that skip policy checks, re-run plan during apply instead of using artifacts, and lack environment protection
- Quick troubleshooting -- models struggle with operational failures (stuck locks, backend migration, provider auth in CI) that are not well-covered in training data
What Was Removed
Content that did not affect output quality when removed:
- Generic HCL syntax tutorials (models know this)
- Provider-specific resource deep dives (better served by MCP or docs)
- Broad "best practice" prose without failure-mode framing (low signal density)
- Duplicate explanations of concepts covered by multiple rules
Design Principle
The token experiment established a core design principle: high signal density. Every line in TerraShark must earn its token cost by preventing a specific failure mode or encoding knowledge the model demonstrably lacks. Content that merely restates what the model already knows is actively harmful -- it burns context window space without improving output quality.
Blast Radius
Use this guide to limit impact scope for changes, failures, and rollbacks.
Symptoms
- tiny change triggers very large plan
- unrelated services share one state and fail together
- production and non-production are entangled
- review/approval ownership is unclear
Boundary model
Split along:
- ownership boundaries
- change cadence boundaries
- recovery boundaries
If these differ, split stack/state.
Architecture patterns
- platform foundation stack (network, identity, shared controls)
- service stack(s) per business workload
- bootstrap stack for backend/state prerequisites
Do not combine all of them in one root.
State isolation rules
- one backend key per isolated stack/environment
- dedicated lock scope per stack
- backup/versioning required for production states
- no shared prod/non-prod state files
Environment separation options
1. separate directories + separate backend keys 2. workspace per environment (only with strict governance) 3. separate repositories for hard regulatory segregation
Apply governance
- serialize applies to shared foundations
- require explicit approval for production and destructive plans
- block auto-apply for high-impact stacks
Example structure
infra/
bootstrap/
platform/
dev/
prod/
services/
billing/
dev/
prod/
catalog/
dev/
prod/LLM mistake checklist
Common model mistakes to correct:
- proposes one monolithic root for convenience
- recommends workspace-only isolation without access controls
- mixes blast radius discussion with purely stylistic concerns
- omits rollback path for shared foundation changes
Verification checks
- does plan only touch intended stack?
- does state key scope match ownership boundary?
- is rollback path documented for this apply?
CI Delivery Patterns
Use this guide to implement auditable Terraform/OpenTofu delivery pipelines.
Delivery principles
- plan and apply are separate concerns
- apply must consume reviewed plan artifact when architecture permits
- policy and security checks run on every apply path
- production applies require environment protection and approvals
Baseline stages
1. fmt + validate 2. lint + security scan 3. plan creation 4. policy checks against plan JSON 5. approval gate 6. apply from trusted branch/runner 7. post-apply drift and evidence capture
GitHub Actions (production-oriented template)
name: terraform-delivery
on:
pull_request:
paths:
- '**/*.tf'
- '**/*.tfvars'
workflow_dispatch:
push:
branches: [main]
paths:
- '**/*.tf'
- '**/*.tfvars'
permissions:
contents: read
id-token: write
pull-requests: write
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false
env:
TF_IN_AUTOMATION: "true"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check
- run: terraform init -backend=false
- run: terraform validate
plan:
if: github.event_name == 'pull_request'
needs: [validate]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform show -json plan.bin > plan.json
- run: conftest test plan.json --policy policy/
- uses: actions/upload-artifact@v4
with:
name: reviewed-plan
path: |
plan.bin
plan.json
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
needs: [validate]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform apply -auto-approve plan.binNotes:
- configure provider auth with OIDC (avoid static cloud keys)
- if you require strict “apply reviewed PR plan” semantics, keep plan/apply in same workflow run or externalize signed plan storage
GitLab CI (production-oriented template)
stages:
- validate
- plan
- policy
- apply
- verify
variables:
TF_IN_AUTOMATION: "true"
validate:
stage: validate
image: hashicorp/terraform:1.7
script:
- terraform fmt -check
- terraform init -backend=false
- terraform validate
plan:
stage: plan
image: hashicorp/terraform:1.7
script:
- terraform init
- terraform plan -out=plan.bin
- terraform show -json plan.bin > plan.json
artifacts:
paths: [plan.bin, plan.json]
expire_in: 24h
policy:
stage: policy
image: openpolicyagent/conftest:latest
dependencies: [plan]
script:
- conftest test plan.json --policy policy/
apply:
stage: apply
image: hashicorp/terraform:1.7
dependencies: [plan]
when: manual
allow_failure: false
script:
- terraform init
- terraform apply -auto-approve plan.bin
verify:
stage: verify
image: hashicorp/terraform:1.7
script:
- terraform plan -detailed-exitcodePipeline hardening checklist
- enforce branch protection on default branch
- require CODEOWNERS review for prod-impacting paths
- restrict apply jobs to protected runners
- set artifact retention + access policies
- preserve audit trail (approver, actor, commit, runtime version)
Cost and speed controls
- run expensive integration suites only for IaC path changes
- serialize shared-foundation applies
- use provider plugin cache where supported
- schedule cleanup for ephemeral test environments
Atlantis (PR-driven delivery template)
Use Atlantis when you want chat-driven, PR-scoped plan/apply with locking.
Example atlantis.yaml:
version: 3
projects:
- name: platform
dir: .
workspace: default
autoplan:
enabled: true
when_modified: ["**/*.tf", "**/*.tfvars"]
workflow: default
workflows:
default:
plan:
steps:
- init
- plan
apply:
steps:
- applyHardening notes:
- restrict apply to approved PRs and protected branches
- enable Atlantis server-side locking
- use custom workflows to add policy checks and cost steps
- keep CI auth in OIDC where supported; avoid static secrets
Infracost (cost visibility template)
Use Infracost to surface cost deltas from plan JSON in PRs.
Pattern: 1. run plan and export plan JSON 2. generate Infracost breakdown 3. publish result as PR comment or artifact
Example commands:
terraform plan -out=plan.bin
terraform show -json plan.bin > plan.json
infracost breakdown --path plan.json --format json --out-file infracost.jsonNotes:
- store
plan.jsonandinfracost.jsonas artifacts for auditability - treat cost checks like policy checks for high-risk environments
Neutral Examples (Context-Dependent)
1) Workspace-centric environment split
locals {
env = terraform.workspace
}
resource "aws_cloudwatch_log_group" "audit" {
name = "/org/${local.env}/audit"
}Tradeoff:
- clean for workspace-managed workflows
- harder to reason about in ad-hoc CLI usage across many environments
2) Single repo with many modules
iac-repo/
modules/
network/
identity/
observability/
environments/
dev/
prod/Tradeoff:
- easy discovery and shared standards
- larger blast radius for repo-level process changes
3) Remote-state bridge across stacks
data "terraform_remote_state" "platform" {
backend = "gcs"
config = {
bucket = "infra-state-org"
prefix = "platform/prod"
}
}Tradeoff:
- quick integration path
- introduces coupling to producer stack internals
4) Composite module owning many primitives
module "payments_platform" {
source = "./modules/payments-platform"
}Tradeoff:
- simplifies root composition
- can become hard to evolve if boundaries inside module are unclear
5) Apply-mode native tests in CI
run "database_contract" {
command = apply
}Tradeoff:
- catches real runtime behavior
- increases cost and pipeline duration
6) Aggressive precondition usage
resource "aws_s3_bucket" "artifact" {
bucket = var.bucket_name
lifecycle {
precondition {
condition = startswith(var.bucket_name, "org-")
error_message = "Bucket names must start with org-."
}
}
}Tradeoff:
- protects conventions early
- too many strict checks can reduce module reuse across org units
Related skills
FAQ
What does terrashark do?
terrashark is a Claude Code skill for ai & agent building.
When should I use terrashark?
When you need to helps with ai & agent building tasks during AI-assisted development., or when terrashark is a claude code skill for ai & agent building.
What are the main capabilities?
terrashark; AI & Agent Building; AI-coding skill.