Question: How can I tag a root volume from an instance created by CloudFormation?

I want to tag the root volume of my Amazon Elastic Compute Cloud (Amazon EC2) instances that are created through AWS CloudFormation.

Short descriptionThe tag property of the EC2 instance resource doesn't extend to the volumes that are created through CloudFormation. Tagging can restrict the control that you have over your instances. Tagging helps you manage the costs of specific resources and restrict AWS Identity and Access Management (IAM) policies. Tagging also helps you exert similar control over other resources.Bootstrapping with CloudFormation allows you to tag the Amazon Elastic Block Store (Amazon EBS) root volume of your instance. The bootstrapping method is done through the UserData property of the AWS::EC2::Instance resource. To perform bootstrapping, use AWS Command Line Interface (AWS CLI) commands or standard Windows PowerShell commands after creating your instance.Note: If you receive errors when running AWS CLI commands, make sure that you’re using the most recent AWS CLI version.

ResolutionCreate an instance with a CloudFormation template1.    Open the CloudFormation console.2.    Choose Create Stack, and then choose Design template.3.    In the code editor, on the Parameters tab, choose Template.4.    For Choose template language, choose YAML.5.    Copy either of the following JSON or YAML templates, and then paste that copied template into your code editor.JSON template:{  "AWSTemplateFormatVersion": "2010-09-09",  "Description": "AWS CloudFormation Sample Template Tagging Root Volumes of EC2 Instances: This template shows you how to automatically tag the root volume of the EC2 instances that are created through the AWS CloudFormation template. This is done through the UserData property of the AWS::EC2::Instance resource. **WARNING** This template creates two Amazon EC2 instances and an IAM role. You will be billed for the AWS resources used if you create a stack from this template.",  "Parameters": {    "KeyName": {      "Type": "AWS::EC2::KeyPair::KeyName",      "Description": "Name of an existing EC2 KeyPair to enable SSH access to the ECS instances."    },    "InstanceType": {      "Description": "EC2 instance type",      "Type": "String",      "Default": "t2.micro",      "AllowedValues": [        "t2.micro",        "t2.small",        "t2.medium",        "t2.large",        "m3.medium",        "m3.large",        "m3.xlarge",        "m3.2xlarge",        "m4.large",        "m4.xlarge",        "m4.2xlarge",        "m4.4xlarge",        "m4.10xlarge",        "c4.large",        "c4.xlarge",        "c4.2xlarge",        "c4.4xlarge",        "c4.8xlarge",        "c3.large",        "c3.xlarge",        "c3.2xlarge",        "c3.4xlarge",        "c3.8xlarge",        "r3.large",        "r3.xlarge",        "r3.2xlarge",        "r3.4xlarge",        "r3.8xlarge",        "i2.xlarge",        "i2.2xlarge",        "i2.4xlarge",        "i2.8xlarge"      ],      "ConstraintDescription": "Please choose a valid instance type."    },    "InstanceAZ": {      "Description": "EC2 AZ.",      "Type": "AWS::EC2::AvailabilityZone::Name",      "ConstraintDescription": "Must be the name of an Availability Zone."    },    "WindowsAMIID": {      "Description": "The Latest Windows 2016 AMI taken from the public Systems Manager Parameter Store",      "Type": "AWS::SSM::Parameter::Value<String>",      "Default": "/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base"    },    "LinuxAMIID": {      "Description": "The Latest Amazon Linux 2 AMI taken from the public Systems Manager Parameter Store",      "Type": "AWS::SSM::Parameter::Value<String>",      "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"    }  },  "Resources": {    "WindowsInstance": {      "Type": "AWS::EC2::Instance",      "Properties": {        "ImageId": {          "Ref": "WindowsAMIID"        },        "InstanceType": {          "Ref": "InstanceType"        },        "AvailabilityZone": {          "Ref": "InstanceAZ"        },        "IamInstanceProfile": {          "Ref": "InstanceProfile"        },        "KeyName": {          "Ref": "KeyName"        },        "UserData": {          "Fn::Base64": {            "Fn::Join": [              "",              [                "<powershell>\n",                "try {\n",                "$AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/placement/availability-zone' -UseBasicParsing).Content\n ",                "$AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)\n ",                "$AWS_INSTANCE_ID=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/instance-id' -UseBasicParsing).Content\n ",                "$ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object DeviceName -match '/dev/sda1').Ebs.VolumeId\n ",                "$tag = New-Object Amazon.EC2.Model.Tag\n ",                "$tag.key = \"MyRootTag\"\n ",                "$tag.value = \"MyRootVolumesValue\"\n ",                "New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag $tag\n",                "}\n",                "catch {\n",                "Write-Output $PSItem\n",                "}\n",                "</powershell>\n"              ]            ]          }        },        "Tags": [          {            "Key": "Name",            "Value": {              "Ref": "AWS::StackName"            }          }        ],        "BlockDeviceMappings": [          {            "DeviceName": "/dev/sdm",            "Ebs": {              "VolumeType": "io1",              "Iops": "200",              "DeleteOnTermination": "true",              "VolumeSize": "10"            }          }        ]      }    },    "LinuxInstance": {      "Type": "AWS::EC2::Instance",      "Properties": {        "ImageId": {          "Ref": "LinuxAMIID"        },        "InstanceType": {          "Ref": "InstanceType"        },        "AvailabilityZone": {          "Ref": "InstanceAZ"        },        "IamInstanceProfile": {          "Ref": "InstanceProfile"        },        "KeyName": {          "Ref": "KeyName"        },        "UserData": {          "Fn::Base64": {            "Fn::Join": [              "",              [                "#!/bin/sh\n",                "AWS_AVAIL_ZONE=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)\n",                "AWS_REGION=${AWS_AVAIL_ZONE::-1}\n",                "AWS_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)\n",                "ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION --instance-id $AWS_INSTANCE_ID --output text --query Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)\n",                "aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValue\n"              ]            ]          }        },        "Tags": [          {            "Key": "Name",            "Value": {              "Ref": "AWS::StackName"            }          }        ],        "BlockDeviceMappings": [          {            "DeviceName": "/dev/sdm",            "Ebs": {              "VolumeType": "io1",              "Iops": "200",              "DeleteOnTermination": "true",              "VolumeSize": "10"            }          }        ]      }    },    "InstanceRole": {      "Type": "AWS::IAM::Role",      "Properties": {        "AssumeRolePolicyDocument": {          "Version": "2012-10-17",          "Statement": [            {              "Effect": "Allow",              "Principal": {                "Service": [                  "ec2.amazonaws.com"                ]              },              "Action": [                "sts:AssumeRole"              ]            }          ]        },        "Path": "/",        "Policies": [          {            "PolicyName": "taginstancepolicy",            "PolicyDocument": {              "Version": "2012-10-17",              "Statement": [                {                  "Effect": "Allow",                  "Action": [                    "ec2:Describe*"                  ],                  "Resource": "*"                },                {                  "Effect": "Allow",                  "Action": [                    "ec2:CreateTags"                  ],                  "Resource": [                    {                      "Fn::Sub": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:volume/*"                    },                    {                      "Fn::Sub": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*"                    }                  ]                }              ]            }          }        ]      }    },    "InstanceProfile": {      "Type": "AWS::IAM::InstanceProfile",      "Properties": {        "Path": "/",        "Roles": [          {            "Ref": "InstanceRole"          }        ]      }    }  }}YAML template:AWSTemplateFormatVersion: 2010-09-09Description: >-  AWS CloudFormation Sample Template Tagging Root Volumes of EC2 Instances: This  template shows you how to automatically tag the root volume of the EC2  instances that are created through the AWS CloudFormation template. This is  done through the UserData property of the AWS::EC2::Instance resource.  **WARNING** This template creates two Amazon EC2 instances and an IAM role.  You will be billed for the AWS resources used if you create a stack from this  template.Parameters:  KeyName:    Type: 'AWS::EC2::KeyPair::KeyName'    Description: Name of an existing EC2 KeyPair to enable SSH access to the ECS instances.  InstanceType:    Description: EC2 instance type    Type: String    Default: t2.micro    AllowedValues:      - t2.micro      - t2.small      - t2.medium      - t2.large      - m3.medium      - m3.large      - m3.xlarge      - m3.2xlarge      - m4.large      - m4.xlarge      - m4.2xlarge      - m4.4xlarge      - m4.10xlarge      - c4.large      - c4.xlarge      - c4.2xlarge      - c4.4xlarge      - c4.8xlarge      - c3.large      - c3.xlarge      - c3.2xlarge      - c3.4xlarge      - c3.8xlarge      - r3.large      - r3.xlarge      - r3.2xlarge      - r3.4xlarge      - r3.8xlarge      - i2.xlarge      - i2.2xlarge      - i2.4xlarge      - i2.8xlarge    ConstraintDescription: Please choose a valid instance type.  InstanceAZ:    Description: EC2 AZ.    Type: 'AWS::EC2::AvailabilityZone::Name'    ConstraintDescription: Must be the name of an Availability Zone.  WindowsAMIID:    Description: >-      The Latest Windows 2016 AMI taken from the public Systems Manager      Parameter Store    Type: 'AWS::SSM::Parameter::Value<String>'    Default: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base  LinuxAMIID:    Description: >-      The Latest Amazon Linux 2 AMI taken from the public Systems Manager      Parameter Store    Type: 'AWS::SSM::Parameter::Value<String>'    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2Resources:  WindowsInstance:    Type: 'AWS::EC2::Instance'    Properties:      ImageId: !Ref WindowsAMIID      InstanceType: !Ref InstanceType      AvailabilityZone: !Ref InstanceAZ      IamInstanceProfile: !Ref InstanceProfile      KeyName: !Ref KeyName      UserData: !Base64         'Fn::Join':          - ''          - - |              <powershell>            - |              try {            - >-              $AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri              'http://169.254.169.254/latest/meta-data/placement/availability-zone'              -UseBasicParsing).Content                           - |-              $AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)                           - >-              $AWS_INSTANCE_ID=(Invoke-WebRequest -Uri              'http://169.254.169.254/latest/meta-data/instance-id'              -UseBasicParsing).Content                           - >-              $ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId              $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object              DeviceName -match '/dev/sda1').Ebs.VolumeId                           - |-              $tag = New-Object Amazon.EC2.Model.Tag                           - |-              $tag.key = "MyRootTag"                           - |-              $tag.value = "MyRootVolumesValue"                           - >              New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag              $tag            - |              }            - |              catch {            - |              Write-Output $PSItem            - |              }            - |              </powershell>      Tags:        - Key: Name          Value: !Ref 'AWS::StackName'      BlockDeviceMappings:        - DeviceName: /dev/sdm          Ebs:            VolumeType: io1            Iops: '200'            DeleteOnTermination: 'true'            VolumeSize: '10'  LinuxInstance:    Type: 'AWS::EC2::Instance'    Properties:      ImageId: !Ref LinuxAMIID      InstanceType: !Ref InstanceType      AvailabilityZone: !Ref InstanceAZ      IamInstanceProfile: !Ref InstanceProfile      KeyName: !Ref KeyName      UserData: !Base64         'Fn::Join':          - ''          - - |              #!/bin/sh            - >              AWS_AVAIL_ZONE=$(curl              http://169.254.169.254/latest/meta-data/placement/availability-zone)            - |              AWS_REGION=${AWS_AVAIL_ZONE::-1}            - >              AWS_INSTANCE_ID=$(curl              http://169.254.169.254/latest/meta-data/instance-id)            - >              ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION              --instance-id $AWS_INSTANCE_ID --output text --query              Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)            - >              aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region              $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValue      Tags:        - Key: Name          Value: !Ref 'AWS::StackName'      BlockDeviceMappings:        - DeviceName: /dev/sdm          Ebs:            VolumeType: io1            Iops: '200'            DeleteOnTermination: 'true'            VolumeSize: '10'  InstanceRole:    Type: 'AWS::IAM::Role'    Properties:      AssumeRolePolicyDocument:        Version: 2012-10-17        Statement:          - Effect: Allow            Principal:              Service:                - ec2.amazonaws.com            Action:              - 'sts:AssumeRole'      Path: /      Policies:        - PolicyName: taginstancepolicy          PolicyDocument:            Version: 2012-10-17            Statement:              - Effect: Allow                Action:                  - 'ec2:Describe*'                Resource: '*'              - Effect: Allow                Action:                  - 'ec2:CreateTags'                Resource:                  - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:volume/*'                  - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*'  InstanceProfile:    Type: 'AWS::IAM::InstanceProfile'    Properties:      Path: /      Roles:        - !Ref InstanceRole6.    In the UserData section of the template, update --tags Key=Name,Value=newAMI to match your requirements for a Linux instance. For a Windows instance, update $tag.key="MyRootTag" and $tag.value="MyRootVolumesValue". See the following UserData section examples for Linux and Windows.Linux example:#Linux UserData  UserData:     Fn::Base64: !Sub |      #!/bin/bash      AWS_AVAIL_ZONE=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)      AWS_REGION="`echo \"$AWS_AVAIL_ZONE\" | sed 's/[a-z]$//'`"      AWS_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)      ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION --instance-id $AWS_INSTANCE_ID --output text --query Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)      aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValueWindows example:#Windows UserData with standard Powershell commands (no AWS CLI installed)    UserData:     Fn::Base64: !Sub |      <powershell>      try {        $AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/placement/availability-zone' -UseBasicParsing).Content      $AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)      $AWS_INSTANCE_ID=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/instance-id' -UseBasicParsing).Content      $ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object DeviceName -match '/dev/sda1').Ebs.VolumeId      $tag = New-Object Amazon.EC2.Model.Tag      $tag.key = "MyRootTag"      $tag.value = "MyRootVolumesValue"      New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag $tag      }      catch {      Write-Output $PSItem      }      </powershell>Important: To use the AWS CLI commands with UserData, you must install the AWS CLI within the Amazon Machine Image (AMI) of your EC2 instances. The AWS CLI is installed by default on all Amazon Linux AMIs. You must also attach an instance profile to your EC2 instances. The instance profile includes the permissions to call the ec2:DescribeInstances and ec2:CreateTags APIs only on EC2 volumes and instances within the AWS Region and account.7.    Choose the Create stack icon.8.    For Stack name, enter a name for your stack.9.    In the Parameters section, enter the appropriate information based on the needs of your environment, including your instance type, EC2 key pair, and AMI.10.    Choose Next.11.    In the Options section, enter the appropriate information for your stack, and then choose Next.12.    To enable the CloudFormation stack to create an IAM resource, select the "I acknowledge that AWS CloudFormation might create IAM resources" check box.13.    Choose Create.Tag the root volume of the instance1.    Open the Amazon EC2 console.2.    In the navigation pane, in the Elastic Block Store section, choose Volumes.3.    In the Filter field, enter the tag that you set in the CloudFormation stack to confirm that the volume was tagged.