CloudFormation Mappings let you define region-specific or environment-specific configurations that your templates can use, abstracting away the need to hardcode values.
Let’s say you’re deploying an EC2 instance and need to use a specific AMI ID. These IDs are different for each AWS region. Instead of maintaining a massive if/else structure or separate templates for each region, you can use a mapping.
AWSTemplateFormatVersion: '2010-09-09'
Description: Example of using CloudFormation Mappings
Mappings:
# This mapping holds AMI IDs for Amazon Linux 2, varying by region.
AMIRegionMap:
us-east-1:
AMI: ami-0fc5d77e42a21170c # Amazon Linux 2 AMI in us-east-1
us-west-2:
AMI: ami-08b4173575791903b # Amazon Linux 2 AMI in us-west-2
eu-west-1:
AMI: ami-0e83a2282641974f7 # Amazon Linux 2 AMI in eu-west-1
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [AMIRegionMap, !Ref "AWS::Region", AMI] # Dynamically find the AMI ID
InstanceType: t2.micro
Tags:
- Key: Name
Value: MyMappedInstance
Outputs:
InstanceId:
Description: The Instance ID of the created EC2 instance
Value: !Ref MyEC2Instance
AMISold:
Description: The AMI ID used for this instance
Value: !FindInMap [AMIRegionMap, !Ref "AWS::Region", AMI]
When you deploy this template, CloudFormation automatically determines which region you’re deploying into using the AWS::Region pseudo-parameter. It then looks up the correct AMI ID from the AMIRegionMap based on that region and the key AMI.
This same pattern applies to environment-specific configurations. Imagine you have different database instance sizes or security group names for your development, staging, and production environments.
AWSTemplateFormatVersion: '2010-09-09'
Description: Environment-specific configurations with Mappings
Mappings:
# This mapping holds database instance sizes for different environments.
EnvironmentDBConfig:
dev:
DBInstanceType: db.t3.small
DBAllocatedStorage: 20
staging:
DBInstanceType: db.t3.medium
DBAllocatedStorage: 50
prod:
DBInstanceType: db.r5.large
DBAllocatedStorage: 100
Parameters:
Environment:
Type: String
AllowedValues:
- dev
- staging
- prod
Description: The deployment environment (dev, staging, or prod).
Resources:
MyRDSInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: !FindInMap [EnvironmentDBConfig, !Ref Environment, DBInstanceType]
AllocatedStorage: !FindInMap [EnvironmentDBConfig, !Ref Environment, DBAllocatedStorage]
Engine: "mysql"
MasterUsername: "admin"
MasterUserPassword: "password123" # In production, use Secrets Manager!
DBName: "mydatabase"
Outputs:
DBInstanceEndpoint:
Description: The endpoint of the RDS instance
Value: !GetAtt MyRDSInstance.Endpoint.Address
Here, the Environment parameter allows the user to select dev, staging, or prod. The !FindInMap function then uses this selected environment to pull the correct DBInstanceType and DBAllocatedStorage from the EnvironmentDBConfig mapping.
The primary benefit is maintainability. If AWS introduces a new AMI in a region, you only update the mapping in one place, not across multiple templates or within complex conditional logic. Similarly, if your environment configurations change, you update the mapping. This significantly reduces the chances of errors and makes your templates more readable and reusable.
You can also nest mappings. For example, you might have a top-level mapping for regions, and within each region, a nested mapping for environments. This allows for even more granular control.
Mappings:
RegionEnvConfig:
us-east-1:
dev:
InstanceType: t3.micro
AMI: ami-0fc5d77e42a21170c
prod:
InstanceType: m5.large
AMI: ami-0fc5d77e42a21170c # Same AMI, but could be different
us-west-2:
dev:
InstanceType: t3.small
AMI: ami-08b4173575791903b
prod:
InstanceType: m5.xlarge
AMI: ami-08b4173575791903b
Parameters:
Environment:
Type: String
AllowedValues: [dev, prod]
Default: dev
Resources:
MyNestedMappedInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionEnvConfig, !Ref "AWS::Region", !Ref Environment, AMI]
InstanceType: !FindInMap [RegionEnvConfig, !Ref "AWS::Region", !Ref Environment, InstanceType]
# ... other properties
In this nested example, !FindInMap [RegionEnvConfig, !Ref "AWS::Region", !Ref Environment, AMI] first looks up the region, then the environment within that region, and finally the AMI key. This provides a powerful way to organize and access complex, multi-dimensional configuration data directly within your CloudFormation templates.
The !Ref "AWS::Region" pseudo-parameter is a key enabler here; without it, you’d have to pass the region as a parameter, which is less automatic and more prone to user error. The !FindInMap intrinsic function is what actually performs the lookup, taking the mapping name, the first-level key (like the region), and the second-level key (like the environment or AMI) as arguments.
What trips people up is thinking of mappings as just for region-specific data. They are equally, if not more, powerful for environment-specific configurations, allowing you to deploy identical infrastructure code across different stages of your lifecycle without code duplication.
The next logical step after mastering mappings for static lookups is to integrate them with dynamic data sources or to manage more complex configurations using AWS Systems Manager Parameter Store, which can be referenced within CloudFormation.