Skip to content

Choosing the right Ubuntu AMI for EC2

Tags: aws, deployment • Categories: Web Development

Table of Contents

As part of setting up an app on an EC2 instance on AWS I wanted to try out Amazon Linux. Picking the latest compatible Amazon Linux AMI using CDK is easy:

const ami = aws_ec2.MachineImage.latestAmazonLinux2023({
  cpuType: aws_ec2.AmazonLinuxCpuType.ARM_64

However, Amazon Linux isn’t always the right choice.

What is Amazon Linux?

It’s not Ubuntu, it’s Fedora (also, Amazon Linux 2 is older than 2023):

$ cat /etc/os-release
NAME="Amazon Linux"
PRETTY_NAME="Amazon Linux 2023"

Why? I have no idea. I’ve done a lot of random stuff in my time as a developer, and running into Fedora is not one of them.

When I ran my ansible scripts against the gravitron fedora box it immediately failed:

fatal: []: FAILED! => {"changed": false, "failures": ["No package docker-ce available.", "No package docker-ce-cli available.", "No package docker-ce-rootless-extras available.", "No package available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}

I was attempting to use ansible-dokku with Amazon Linux 2023, which explicitly is not supported.

Gravitron-supported OS are listed here and included Ubuntu, so it was time to use an Unbuntu image.

Programmatically choosing Ubuntu AMI

Unfortunately, there is not a nice aws_ec2.MachineImage.latestAmazonLinux2023 API for grabbing the Ubuntu AMI (these sharp edges of CDK are driving me nuts!). The AMIs for Ubuntu are indexed here and seem to be specified on a per-zone basis, which doesn’t make any sense (why should the image references change based on where they are hosted?!). Even worse, the AMIs need to be chosen by region, process, disk type, etc. There’s some pretty complex logic out there for picking the right AMI.

This guide was super helpful. Here’s a quick summary:

  • Public SSM params are a great way to get specific AMI references: aws ssm get-parameter --name "/aws/service/canonical/ubuntu/server/focal/stable/current/amd64/hvm/ebs-gp2/ami-id"
  • You can get the list of public image names using this approach
    • aws ec2 describe-images --output json --region us-east-2 --filters "Name=name,Values=ubuntu/images/*" | jq '.Images | map([.ImageId, .Name])'
  • The owner is not you, but the company that publishes the image (Canonical, Inc behind Ubuntu, in this case).
  • This snippet seems like the most sane approach: lookup the AMI, then set as the default genericLinux map.

Here’s the final snippet you can plop into your AppStack:

export class AppStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    const machineImage = this.genericLinuxImage()
    const ec2Instance = new aws_ec2.Instance(this, 'Instance', {
      machineImage: machineImage

  genericLinuxImage() {
    const ubuntuCompanyOwnerId = "099720109477"
    // NOTE only pick LTS versions for your sanity!
    const ubuntuName = "jammy"

    const machineImage = aws_ec2.MachineImage.genericLinux({
      [this.region]: new aws_ec2.LookupMachineImage({
        // `YEAR-ARCH` are the first two stars
        name: `ubuntu/images/hvm-ssd/ubuntu-${ubuntuName}-*-*-server-*`,
        owners: [ubuntuCompanyOwnerId],
        filters: {
          architecture: [aws_ec2.InstanceArchitecture.ARM_64],
          "image-type": ["machine"],
          state: ["available"],
          "root-device-type": ["ebs"],
          "virtualization-type": ["hvm"],

    return machineImage

There was some conflicting evidence of this online, but the Ubuntu image I ended up using worked fine with the new GP3 EBS stores.

As an aside, I had zero problems with Gravitron. Impressive that as a developer community, we’ve migrated to a completely different processor architecture without too many issues (both on the server and dev side!). Gravitron also seems to be quite a bit snappier than the x86 box I was using.