Build your CloudFormation Templates with the latest Ubuntu AMI

1. Overview

If you are already familiar with CloudFormation Templates, you know that it is a common practice to create a mapping with hard-coded AMI IDs with their respective regions for launching instances. While the unique AMI ID per region is solved, the Always get the latest AMI is not.

In this quick tutorial, I would like to show how to ensure you always get the latest AMI ID for any given version of Ubuntu.

Why is this important? Simple, it gives you faster boot times and less reboots. If the AMI you are choosing is an old build, the update & upgrade process will not only take longer, but it could also lead you into a reboot if the package being updated is none other than the kernel itself.

In short, we will be querying the SSM parameter store for getting the latest AMI ID.

What you’ll need

  • An AWS account
  • A basic knowledge of EC2
  • A basic knowledge of AWS CloudFormation Templates

What you’ll learn

How to avoid hardcoding AMI IDs in your CloudFormation templates.


2. Let’s start

Instead of building a list of mapped and hard-coded AMI IDs into your template, you can query the SSM parameter store for getting the latest version in a very consistent way.

Regular (free) Ubuntu LTS

From the AWS Documentation, “Each Amazon Linux AMI now has its own Parameter Store namespace that is public and describable. Upon querying, an AMI namespace returns only its regional ImageID value.”

If you want to use the regular Ubuntu server version in your CloudFormation template, add the following to your Parameters section. You can change the Ubuntu version (e.g. jammy, focal, bionic) and also the architecture (ARM64 or AMD64):

    LatestAmiId:
                Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
                Default: '/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id'

When launching the instance, in the Resources section, you can call this parameter (the AMI ID) as it follows:

    MyInstance:
        Type: AWS::EC2::Instance
        Properties:
            ImageId: !Ref LatestAmiId

Ubuntu Pro

Ubuntu Pro now follows the same pattern as regular Ubuntu, changing only the product string:

    LatestAmiId:
                Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
                Default: '/aws/service/canonical/ubuntu/pro-server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id'

Note: Since the release of Ubuntu Noble Numbat 24.04 LTS, the default volume uses gp3 partition tables, changing the Ubuntu and Ubuntu Pro AMI names to:

  • /aws/service/canonical/ubuntu/server/noble/stable/current/amd64/hvm/ebs-gp3/ami-id
  • /aws/service/canonical/ubuntu/pro-server/noble/stable/current/amd64/hvm/ebs-gp3/ami-id

Ubuntu Pro FIPS

Since Ubuntu Pro FIPS is available only at the AWS Marketplace, the parameters needed for getting Ubuntu Pro FIPS are different. Now you need to specify the product ID according to the following table:

Name Architecture identifier
Ubuntu Pro FIPS 16.04 LTS amd64 prod-hykkbajyverq4
Ubuntu Pro FIPS 18.04 LTS amd64 prod-7izp2xqnddwdc
Ubuntu Pro FIPS 20.04 LTS amd64 prod-k6fgbnayirmrc

The parameter in your CloudFormation template will look like this (change <product-id> with one from the table above):

    LatestAmiId:
                Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
                Default: '/aws/service/marketplace/<product-id>/latest'

Note:
Remember that if you are launching a marketplace product, you will need to subscribe to it prior to launch your CFT, even if the product is completely free of charge.

For EKS Ubuntu LTS

For Ubuntu-EKS AMI IDs, the search is as follows (you can replace Ubuntu version, EKS version and architecture):

    LatestAmiId:
                Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
                Default: '/aws/service/canonical/ubuntu/eks/20.04/1.23/stable/current/amd64/hvm/ebs-gp2/ami-id'

3. Putting all together

This is how it would look like in a very basic template for Ubuntu LTS:

AWSTemplateFormatVersion: 2010-09-09
Description: Launch EC2 instance with the latest Ubuntu AMI

Parameters:
    AvailabilityZone:
        Type: AWS::EC2::AvailabilityZone::Name
    LatestAmiId:
                Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
                Default: '/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id'
    KeyPair:
        Description: Amazon EC2 Key Pair used to ssh to the cluster nodes
        Type: "AWS::EC2::KeyPair::KeyName"
    InstanceType:
        Type: String
        Default: t2.micro
        AllowedValues:
            - t2.micro
            - t2.medium
            - t2.large
            - t2.xlarge
            - t2.2xlarge

Resources:
    MyInstance:
        Type: AWS::EC2::Instance
        Properties:
            ImageId: !Ref LatestAmiId
            InstanceType: !Ref InstanceType
            AvailabilityZone: !Ref AvailabilityZone
            KeyName: !Ref KeyPair
            SecurityGroupIds:
                - !Ref MyBasicSecurityGroup

    MyBasicSecurityGroup:
        Type: AWS::EC2::SecurityGroup
        Properties:
            GroupName: "A very basic Security group"
            GroupDescription: "Allows SSH inbound traffic"
            SecurityGroupIngress:
                - IpProtocol: tcp
                  FromPort: 22
                  ToPort: 22
                  CidrIp: 0.0.0.0/0

Outputs:
    InstanceIP:
        Value: !GetAtt MyInstance.PublicIp
        Description: Instance public IP

4. That’s all folks!