Skip to main content

Terraform IaC Support

Terraform is an alternative IaC engine available alongside CloudFormation. When APP_IAC=terraform is set in .env (selected during /ipa-init), /ipa-compose generates Makefile targets that execute terraform init/apply/destroy instead of aws cloudformation deploy/delete-stack.

Both engines produce functionally equivalent infrastructure. The Terraform modules mirror the CloudFormation templates 1:1 in resources, parameters, and outputs.

How It Works​

IaC Mode Selection​

The builder chooses the IaC tool during /ipa-init (5th question). The choice is stored as APP_IAC=terraform in .env and read by /ipa-compose at Step 6.0 to determine which Makefile template set to use.

State Backend Bootstrap​

Terraform requires a state backend before any module can run. IPA solves this chicken-and-egg problem by deploying the state backend via CloudFormation — always, even in full-TF mode:

infra/cfn/tfstate/tfstate.yml → S3 bucket + DynamoDB lock table

There are two paths to bootstrap the state backend; both converge on the same .env content:

Inline bootstrap (recommended, default) — /ipa-init Step 4.5 deploys tfstate.yml immediately after writing .env when APP_IAC=terraform is selected. It captures StateBucketName and LockTableName from CFN outputs and appends TF_STATE_BUCKET + TF_STATE_LOCK_TABLE to .env. The project becomes deploy-ready in a single step.

Fallback bootstrap — when /ipa-init cannot deploy (credentials missing, network failure, CI/CD environment), the same logic runs as make -f scripts/prepare.mk prepare-tfstate followed by make -f scripts/env.mk update-env-tfstate. The CFN deploy uses --no-fail-on-empty-changeset, so re-running either path is safe.

In Terraform mode, prepare-tfstate is the only CloudFormation target in prepare.mk. All other prepare targets use terraform apply.

Module Architecture​

Each Terraform module is a flat, independent root module under infra/tf/{tier}/:

infra/tf/backend/
├── main.tf # Resource definitions
├── variables.tf # Input variables with validation
├── outputs.tf # Exported values
└── versions.tf # Provider + backend configuration

Modules are self-contained — no shared modules, no abstractions, no code generation. A customer engineer can fork and evolve any module without IPA knowledge.

Cross-Module Wiring​

IPA uses two wiring patterns depending on the source module's lifecycle:

Source LifecycleWiring MechanismExample
prepare → deployterraform_remote_state data source in the consuming modulecognito → backend (issuer_url, client_id)
deploy → deployMakefile subshell $$(cd ../queue && terraform output -raw queue_url)queue → backend (queue_url, queue_arn)

The prepare→deploy pattern embeds a terraform_remote_state data source that reads the prepare module's state from S3:

data "terraform_remote_state" "cognito" {
backend = "s3"
config = {
bucket = var.state_bucket
key = "${var.namespace}-${var.environment}/cognito/terraform.tfstate"
region = var.region
}
}

The deploy→deploy pattern uses Makefile-level wiring because both modules deploy in the same make invocation — the consuming module's terraform_remote_state would fail if the source module has no state yet:

deploy-backend: deploy-queue
cd infra/tf/backend && \
terraform init -input=false -reconfigure ... && \
terraform apply -auto-approve -input=false \
... \
-var="sqs_queue_url=$$(cd ../queue && terraform output -raw queue_url)"

env.mk in Terraform Mode​

In Terraform mode, env.mk uses terraform output -raw from each module directory instead of aws cloudformation describe-stacks:

update-env-cognito:
$(eval OIDC_ISSUER_VAL := $(shell cd infra/tf/cognito && terraform output -raw issuer_url 2>/dev/null || echo ""))
# ... writes to .env

The one exception is update-env-tfstate, which queries the CFN stack via describe-stacks (the tfstate stack is always CloudFormation).

Usage​

Selecting Terraform During Init​

/ipa-init

At the 5th question ("Which infrastructure-as-code tool?"), select terraform.

Compose and Deploy​

The workflow is identical to CloudFormation mode — only the underlying commands change:

/ipa-compose backend frontend queue # generates TF-mode Makefiles
/ipa-prepare # deploys tfstate (CFN) + cognito/ecr/logs (TF)
/ipa-deploy # deploys backend/frontend/queue (TF)

Validating Modules​

make -f scripts/test.mk validate

Runs terraform init -backend=false && terraform validate in each infra/tf/*/ directory.

Extending / Maintaining​

Adding a Variable to a Module​

  1. Add the variable to infra/tf/{tier}/variables.tf with type, description, and validation.
  2. Reference it in main.tf.
  3. Update the stack skill's ## Terraform Module → Variables table.
  4. Re-run /ipa-compose to regenerate Makefiles with the new -var= override.

Adding a New Resource to a Module​

  1. Add the resource to infra/tf/{tier}/main.tf.
  2. If it produces values needed downstream, add an output to outputs.tf.
  3. Run terraform validate in the module directory.
  4. Update the stack skill's Terraform Module section if outputs changed.

Feature Flags​

Terraform uses count = var.enable_x ? 1 : 0 as the equivalent of CloudFormation Conditions:

resource "aws_dynamodb_table" "passengers" {
count = var.enable_passengers_table ? 1 : 0
name = "${var.namespace}-${var.environment}-passengers"
# ...
}

Conditional outputs reference the resource with an index:

output "passengers_table_arn" {
value = var.enable_passengers_table ? aws_dynamodb_table.passengers[0].arn : ""
}

State Key Convention​

All modules store state under the pattern:

{namespace}-{environment}/{tier}/terraform.tfstate

For example: myapp-dev/backend/terraform.tfstate in the S3 state bucket.

Known Issues​

  • No terraform import workflow — modules support new deployments only. Migrating existing CFN-deployed infrastructure to Terraform is not automated.
  • No CloudWatch observability — per the 016-remove-cloudwatch-observability feature, TF modules omit dashboards and alarms. Log groups are retained.
  • Security stack always uses CFN — /ipa-security generates infra/cfn/generated/iam.yml regardless of APP_IAC. Security IAM roles are referenced via .env variables.

References​

  • infra/cfn/tfstate/tfstate.yml — state backend CloudFormation template
  • .claude/skills/ipa-stack-tfstate/SKILL.md — tfstate prepare stack skill
  • .claude/skills/ipa-compose/MAKEFILE_TEMPLATES.md → Terraform Mode Templates section
  • Terraform S3 Backend documentation