Decisions

ADR-0004: Infrastructure Layout

Decision to use .infra/ convention for infrastructure-as-code in application repositories.

ApprovedNo MCP

Status: Accepted

Date: 2025-12-16

Deciders: Engineering Leadership


Context

Ontopix manages infrastructure-as-code across multiple repositories. Without standardization:

Discovery Problems:

  • Engineers don't know where to find Terraform code
  • Inconsistent locations (root, terraform/, infra/, infrastructure/, etc.)
  • Hard to navigate new repositories
  • Agents can't reliably locate infrastructure

Operational Problems:

  • No standard way to operate infrastructure
  • Direct terraform invocations (bypassing safety checks)
  • Inconsistent state management
  • No clear owner for infrastructure changes

Safety Problems:

  • Easy to accidentally apply changes
  • Production infrastructure not clearly marked
  • No standard approval workflow
  • Agents could make dangerous changes

We need a standard location and operational interface for infrastructure-as-code.

Decision

We will adopt the .infra/ convention for infrastructure-as-code in application repositories.

Core Rules

  1. Application repositories that provision cloud resources MUST place Terraform in .infra/
  2. The central infra repository is EXEMPT (it IS infrastructure, doesn't follow .infra/ rule)
  3. Infrastructure operations MUST be exposed through Taskfile tasks (infra:*)
  4. Agents MUST request human approval before infrastructure changes

Directory Structure

my-service/
├─ .infra/
│  ├─ main.tf
│  ├─ variables.tf
│  ├─ outputs.tf
│  ├─ backend.tf
│  └─ modules/
├─ src/
├─ Taskfile.yaml
├─ AGENTS.md
└─ README.md

Taskfile Integration

Infrastructure operations exposed as:

infra:plan:
  desc: Show Terraform plan
  dir: .infra
  cmds: [terraform init, terraform plan]

infra:apply:
  desc: Apply infrastructure changes (requires approval)
  dir: .infra
  cmds: [terraform init, terraform apply]

infra:destroy:
  desc: Destroy infrastructure (dangerous)
  dir: .infra
  preconditions:
    - sh: "[ '{{.CONFIRM}}' = 'yes' ]"
  cmds: [terraform destroy]

The Central infra Repository Exception

The infra repository:

  • Provisions foundational platform infrastructure (dns, iam, cost alerts, etc.)
  • Does NOT use .infra/ convention
  • Organizes Terraform at root or in top-level directories
  • Is the ONLY exception to the rule

Why this exception? The infra repository IS infrastructure. It doesn't contain application code that needs infrastructure; it defines the platform.

Rationale

Why .infra/ Instead of Alternatives?

Considered locations:

  • infra/ — Too prominent, conflicts with central infra repo name
  • terraform/ — Tool-specific, doesn't age well
  • infrastructure/ — Too verbose
  • .terraform/ — Conflicts with Terraform's internal directory
  • Root directory — Clutters repository root

Why .infra/ wins:

  • Hidden (leading dot) but not obscure
  • Short and memorable
  • Tool-agnostic (works if we switch from Terraform)
  • Doesn't conflict with central infra repo
  • Clear purpose

Why Separate .infra/ from Application Code?

Separation of Concerns:

  • Infrastructure is different from application code
  • Different lifecycle (infra changes less frequently)
  • Different ownership (platform team often involved)
  • Different review requirements

Safety:

  • Infrastructure changes visible in PRs
  • Clear boundary for sensitive operations
  • Agents know where dangerous operations live
  • Easy to apply additional protections (CODEOWNERS)

Discoverability:

  • Standard location across repositories
  • Engineers immediately know where to find infrastructure
  • Agents can locate infrastructure reliably

Why Taskfile Integration?

Safety:

  • Tasks encapsulate preconditions
  • infra:destroy requires explicit confirmation
  • Agents use tasks, not direct Terraform
  • Production deployments require approval

Consistency:

  • Same operational interface as application code
  • task infra:apply works everywhere
  • No need to remember Terraform flags

Documentation:

  • task --list shows infrastructure operations
  • Self-documenting interface
  • Clear intent (plan vs apply vs destroy)

Why Central infra Exception?

The infra repository is fundamentally different:

  • It IS the platform infrastructure
  • Terraform IS the primary content

Avoiding confusion:

  • .infra/ in infra repo would be redundant
  • Would nest unnecessarily
  • Unclear what "application" means in this context

Consequences

Positive

Consistency:

  • Same location across all application repositories
  • Predictable for engineers and agents
  • Easy onboarding

Safety:

  • Clear boundary for infrastructure
  • Taskfile enforces preconditions
  • Agents request approval
  • CODEOWNERS can require platform team review

Maintainability:

  • Infrastructure isolated from application code
  • Clear ownership
  • Easy to navigate

Operational Clarity:

  • Standard Taskfile interface
  • Self-documenting operations
  • Safe by default

Negative

Hidden Directory:

  • Leading dot hides directory
  • Engineers must know to look for it
  • Not visible in some file explorers

Additional Convention:

  • Another thing to remember
  • Can be forgotten in new repositories
  • Requires discipline to enforce

Migration Effort:

  • Existing repositories need restructuring
  • Terraform paths may need updating
  • State may need migration

Mitigations

Documentation:

  • Principles clearly explain .infra/
  • Templates include .infra/ structure
  • AGENTS.md references infrastructure location

Tooling:

  • CI validates .infra/ exists (if applicable)
  • Templates generate correct structure
  • Automated checks for structure
  • Taskfile provides consistent operational interface

Visibility:

  • Document in README where infrastructure lives
  • AGENTS.md explains infrastructure location
  • task --list shows infra tasks

Migration Support:

  • Migration guide provided
  • Terraform state migration documented
  • Incremental migration acceptable

Alternatives Considered

Alternative 1: Root Directory

Rejected because:

  • Clutters repository root
  • Mixes application and infrastructure
  • Hard to apply different permissions
  • Confusing for newcomers

Alternative 2: infra/ Subdirectory

Rejected because:

  • Conflicts with central infra repository name
  • Too prominent (not hidden)
  • Confusion about which "infra" is which

Alternative 3: terraform/ Subdirectory

Rejected because:

  • Tool-specific (doesn't age well)
  • Vendor lock-in perception
  • May need to support other IaC tools

Alternative 4: Monorepo Infrastructure Directory

Rejected as default because:

  • Doesn't work for distributed repositories
  • Overly centralized
  • Can still be used alongside .infra/ if needed

Alternative 5: Separate Infrastructure Repositories

Rejected as default because:

  • Separates infrastructure from application too much
  • Harder to keep in sync
  • More repositories to manage
  • Can still be used for complex cases

References

Success Criteria

This decision is successful if:

  • Application repositories consistently use .infra/
  • Engineers immediately know where to find infrastructure
  • Agents reliably locate infrastructure
  • Infrastructure operations go through Taskfile
  • Human approval enforced for dangerous operations
  • Migration effort is manageable