This document provides a technical reference for CatStack's directory structure and conventions, designed for both human engineers and AI assistants.
CatStack is a loosely coupled Terraform/OpenTofu framework that organizes infrastructure as code using domains (autonomous components with separate state) within stacks (container directories). This separation enables teams to work independently on different infrastructure components while maintaining clear dependency relationships.
project-root/
├── infra_environments/ # Environment-specific configuration
│ ├── nonprod/
│ │ ├── nonprod.tfvars.json # Shared variables for non-production
│ │ └── nonprod.tfbackend # Backend config for non-production
│ └── prod/
│ ├── prod.tfvars.json # Shared variables for production
│ └── prod.tfbackend # Backend config for production
├── stack/ # Container for all domains
│ ├── 01_shared_kms/ # Low-level domain (foundational)
│ ├── 02_backend_config/ # Depends on 01_shared_kms
│ └── 03_sqs_dlq/ # Higher-level domain
├── modules/ # (Optional) Project-specific shared modules
└── .gitignore
| Directory | Purpose |
|---|---|
infra_environments/ |
Contains environment-specific configuration files (.tfvars.json, .tfbackend) |
stack/ |
Contains all domain directories, each with its own Terraform state |
modules/ |
(Optional) Shared modules used across multiple domains within the project |
Each domain within stack/ follows a standard file structure:
stack/01_shared_kms/
├── main.tf # Resource definitions and module calls
├── providers.tf # AWS provider configuration
├── tf_backend.tf # Partial S3 backend configuration
├── tf_settings.tf # Terraform version and provider requirements
├── variables.tf # Input variable definitions
└── output.tf # Exported values for cross-domain consumption
Contains the primary resource definitions and module calls. This is where the domain's infrastructure is defined.
module "kms" {
source = "git@github.com:wearetechnative/terraform-aws-kms.git?ref=v1.0.0"
name = "shared-key"
# ... configuration
}Configures the AWS provider with role assumption and account restrictions.
provider "aws" {
region = "eu-west-1"
assume_role {
role_arn = "arn:aws:iam::${var.aws_account_id}:role/TerraformAdmin"
}
allowed_account_ids = [var.aws_account_id]
default_tags {
tags = {
Project = "project-name"
Stack = "01_shared_kms"
}
}
}Partial S3 backend configuration with the domain-specific state key. The remaining backend settings (bucket, role_arn, dynamodb_table) come from the .tfbackend file.
terraform {
backend "s3" {
key = "01_shared_kms/terraform.tf"
}
}Specifies Terraform version constraints and required providers.
terraform {
required_version = "~> 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}Defines input variables. Every domain includes common variables (aws_account_id, infra_environment) plus domain-specific ones.
# Common variables (present in all domains)
variable "aws_account_id" {
type = string
description = "AWS account ID for this environment"
}
variable "infra_environment" {
type = string
description = "Infrastructure environment name (e.g., nonprod, prod)"
}
# Domain-specific variables
variable "kms_arn" {
type = string
description = "ARN of the shared KMS key"
}Exports values that other domains may need. These can be consumed via terraform_remote_state data sources or shared through .tfvars.json.
output "kms_key_arn" {
value = module.kms.key_arn
description = "ARN of the shared KMS key"
}JSON files containing variables shared across all domains in an environment. These are passed to Terraform with -var-file.
{
"aws_account_id": "123456789012",
"infra_environment": "nonprod",
"kms_arn": "arn:aws:kms:eu-west-1:123456789012:key/abc-123"
}Usage pattern:
aws_account_idandinfra_environmentare always present- Cross-domain values (like
kms_arn) are added after the source domain is deployed - Update the file when domain outputs need to be shared
Partial backend configuration merged with tf_backend.tf at init time.
role_arn = "arn:aws:iam::123456789012:role/TerraformAdmin"
bucket = "project-terraform-state"
dynamodb_table = "project-terraform-locks"
region = "eu-west-1"
encrypt = trueUsage: terraform init -backend-config=../../infra_environments/nonprod/nonprod.tfbackend
Domains use numeric prefixes (01_, 02_, etc.) to indicate deployment order and dependency hierarchy:
| Prefix Range | Category | Description |
|---|---|---|
01_ - 09_ |
Foundational | Core infrastructure (KMS, networking). Many dependants. |
10_ - 49_ |
Platform | Shared services (databases, queues). Moderate dependencies. |
50_ - 99_ |
Application | Application-specific resources. Few or no dependants. |
Key principles:
- Lower numbers = more dependants (deployed first, destroyed last)
- Higher numbers = more dependencies (deployed after prerequisites)
- Domains with the same prefix can be deployed in parallel
- Use
snake_casefor all Terraform identifiers - Prefix resources with the domain purpose when clarity is needed
- Variable names should be descriptive and self-documenting
cd stack/01_shared_kms
# Initialize with environment-specific backend
terraform init -backend-config=../../infra_environments/nonprod/nonprod.tfbackend
# Plan with shared variables
terraform plan -var-file=../../infra_environments/nonprod/nonprod.tfvars.json
# Apply changes
terraform apply -var-file=../../infra_environments/nonprod/nonprod.tfvars.json- Determine the appropriate numeric prefix based on dependencies
- Create the domain directory:
stack/XX_domain_name/ - Add all standard files (main.tf, providers.tf, tf_backend.tf, tf_settings.tf, variables.tf, output.tf)
- Update
tf_backend.tfwith the domain-specific key - Add any domain-specific variables to
variables.tf - Deploy in dependency order (lower numbers first)
Option 1: Via .tfvars.json (recommended)
- Deploy source domain and note output values
- Add output values to
.tfvars.json - Reference via variables in dependent domains
Option 2: Via terraform_remote_state
data "terraform_remote_state" "kms" {
backend = "s3"
config = {
bucket = "project-terraform-state"
key = "01_shared_kms/terraform.tf"
region = "eu-west-1"
}
}
# Use: data.terraform_remote_state.kms.outputs.kms_key_arn# Terraform
.terraform/
*.tfstate
*.tfstate.*
crash.log
# Lock file (versions pinned in tf_settings.tf)
.terraform.lock.hcl
# Secrets
secrets.auto.tfvars
*.auto.tfvars
# Local tools
.tool-versions- RACE - Terraform tooling for CatStack
- Bootstrap-AWS-CatStack - Project bootstrapping