Deployments
Deployments are environment-specific definitions that establish how solutions are utilized. A deployment always references a solution and never directly interacts with a module.
Characteristics
- Environment-specific — One folder per region and environment
- Declarative — No business logic, only configuration
- Pinned versions — Always reference a specific solution version
- Terragrunt-based — Uses Terragrunt for DRY configuration and orchestration
Deployment Hierarchy
deployments/
├── region-1/ # Region
│ ├── stage/ # Environment
│ │ ├── root.hcl # Shared env config
│ │ ├── my-app/ # Service deployment
│ │ │ ├── terragrunt.hcl
│ │ │ └── tags.yaml
│ │ └── my-database/
│ │ ├── terragrunt.hcl
│ │ └── tags.yaml
│ └── production/
│ ├── root.hcl
│ └── ...
└── region-2/
├── stage/
└── production/
Environment Types
| Environment | Purpose | Region Examples |
|---|---|---|
| Stage | Pre-production testing and validation | region-1, region-2 |
| Production | Live production workloads | region-1, region-2 |
root.hcl — Environment Configuration
Each environment has a root.hcl that defines shared settings:
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "my-org-region-1-production-terraform-remote-state"
dynamodb_table = "my-org-region-1-production-terraform-remote-state-locks"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-west-2"
encrypt = true
}
}
locals {
env_vars = {
business_region = "region-1"
environment = "production"
vpc_id = "vpc-0example"
db_subnet_group_name = "region-1-production-db-subnet-group"
alarm_sns_topic_arn = "arn:aws:sns:us-west-2:123456789:region-1-production-alerts"
}
}
Tagging Strategy
Tags are enforced at the environment level and merged with deployment-specific tags:
# tags.yaml
company:ops:owner: "platform-team"
company:cost:project: "my-project"
company:cost:center-name: "engineering"
# In root.hcl
locals {
default_tags = {
"ops:region" = local.env_vars.business_region
"ops:environment" = local.env_vars.environment
}
overwrite_tags = try(yamldecode(file("${get_terragrunt_dir()}/tags.yaml")))
tags = merge(
local.default_tags,
{
"ops:owner" = local.overwrite_tags["ops:owner"]
"cost:project" = local.overwrite_tags["cost:project"]
"cost:center-name" = local.overwrite_tags["cost:center-name"]
},
local.overwrite_tags,
{
"ops:stack" = "${path_relative_to_include()}"
}
)
}
The tag merge strategy ensures mandatory tags are always present while allowing deployments to add custom tags.