Module Structure#
terraform/
âââ main.tf # Module composition and orchestration
âââ variables.tf # Map-based variables (per environment)
âââ config.auto.tfvars # Actual value configuration
âââ outputs.tf # Output variables
âââ backend.tf # S3 + DynamoDB remote state
âââ moved.tf # State migration
âââ modules/
âââ shared/ # Created once (VPC, ALB, IAM, SG)
âââ environment/ # Per environment (EC2, RDS, Redis, S3, DNS)
âââ message/ # Push notifications (SQS, SNS, Lambda)
âââ pdf_processing/ # PDF processing (Step Functions, Lambda)
âââ batch_processing/ # Batch processing (SQS, Lambda)
Variable Design Patterns#
Map-Based Variables (Per-Environment Configuration)#
# CORRECT: Map for per-environment settings
variable " instance_types " {
type = map(string)
default = {
" production " = " t4g.medium "
" staging " = " t4g.small "
" development " = " t4g.small "
}
}
# WRONG: Separate variables per environment
variable " production_instance_type " { ... }
variable " staging_instance_type " { ... }
Passing Variables to Modules (lookup pattern)#
# CORRECT: Safe access with lookup
deeplink_subdomain = lookup(var.deeplink_config, " production " , null) != null ? var.deeplink_config[ " production " ][ " subdomain " ] : " "
# WRONG: Direct access (errors if key is missing)
deeplink_subdomain = var.deeplink_config[ " production " ][ " subdomain " ]
Feature Toggle Pattern#
# Environment activation
locals {
is_production_enabled = var.enable_production_server
is_staging_enabled = var.enable_staging_server
is_development_enabled = var.enable_development_server
}
# Module count condition
module " production_env " {
count = local.is_production_enabled ? 1 : 0
# ...
}
# Service + environment combination condition
module " message_production " {
count = var.enable_push_notifications & & local.is_production_enabled ? 1 : 0
# ...
}
Route53 Resource Patterns#
count Condition Rules#
| Record Type | count Condition |
|---|---|
| Primary CNAME | enable_route53 && enable_primary_dns && subdomain != "" |
| Secondary CNAME | enable_route53 && secondary_domain != null && subdomain != "" |
| Top domain A |
enable_route53 && enable_primary_dns && enable_top_domain_alias && length(ips) > 0
|
| Production-only | Additional local.is_production condition |
Subdomain Naming#
# CORRECT: Production has no prefix, others have suffix
name = local.is_production ? " console " : " console-${local.env_short_suffix} "
# env_short_suffix: " stg " or " dev "
Lambda ZIP Path Rules#
Relative Paths Based on path.module#
# Reference Lambda ZIP within module
locals {
lambda_base_path = " ${path.module}/../../../lambda/python "
router_zip_path = " ${local.lambda_base_path}/pdf_router/build/pdf-router.zip "
}
Caution: path.module is relative to the module .tf file location:
modules/pdf_processing/../../../=deploy/aws/- NOT the
terraform/execution directory
fileexists() Guard Pattern#
# CORRECT: Guard with fileexists()
locals {
zip_exists = fileexists(local.zip_path)
}
resource " aws_lambda_function " " example " {
count = local.zip_exists ? 1 : 0
source_code_hash = local.zip_exists ? filebase64sha256(local.zip_path) : null
}
# WRONG: Direct call without guard (plan fails if file missing)
resource " aws_lambda_function " " example " {
source_code_hash = filebase64sha256(local.zip_path)
}
State Management#
Remote Backend#
backend " s3 " {
bucket = " kobic-tfstate-bucket "
key = " terraform.tfstate "
region = " ap-northeast-2 "
dynamodb_table = " kobic-tfstate-lock "
encrypt = true
}
Workspace Usage#
# List workspaces
terraform workspace list
# Switch workspace
terraform workspace select production
This project manages environments with a workspace + enable_*_server flag combination. Within each workspace, feature flags conditionally create per-environment resources.
Tag Rules#
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = " terraform "
}