Tips for Building Bottlerocket AMIs

Bottlerocket is a Linux-based operating system optimized for hosting containers. At my work, we migrated from Amazon Linux to Bottlerocket and experienced the following benefits:

  1. Developer-friendly: Easy to understand and fast to build. RPM spec and configuration TOML files are all you need. Every developer can build a Bottlerocket AMI on an EC2 instance in just a few minutes. For example, I can build and register a new Bottlerocket AMI from scratch in less than 4 minutes on a c5.4xlarge instance. Follow-up builds take about 3 minutes due to caching.

  2. Significantly improved boot performance: The time from Linux boot to containerd server running dropped from 23 seconds to 14 seconds. A recent addition of EROFS made it start even faster while using a 46% smaller root EBS volume.

  3. Enhanced security: Features a read-only root filesystem. See more details at https://bottlerocket.dev/#security-focused.

Getting started with Bottlerocket is a smooth experience thanks to good documentation. This post shares some tips I've found useful.

Clean up build cache periodically

Bottlerocket uses Docker to build RPM packages. Over time, Docker can consume significant disk space.

1dev-dsk % docker system df
2TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
3Images          48        3         134.5GB   128.4GB (95%)
4Containers      3         1         14B       14B (100%)
5Local Volumes   53        0         68B       68B (100%)
6Build Cache     4444      15        249.6GB   248.1GB

If your build fails, check available disk space and run docker system prune -a if space is low. Alternatively, you can expand the EBS volume size live on EC2.

List all installed packages

In Bottlerocket, there's no way to install packages on a running instance. All packages come with the Bottlerocket OS image. You can check the list of installed packages in ./build/images/${variant}/latest/application-inventory.json. For example:

 1    {
 2      "Name": "bottlerocket-amazon-cloudwatch-agent",
 3      "Publisher": "Bottlerocket",
 4      "Version": "1.300034.0",
 5      "Release": "1.1755731416.9333e95b.br1",
 6      "Epoch": "0",
 7      "InstalledTime": "2025-08-21T08:09:05Z",
 8      "ApplicationType": "Unspecified",
 9      "Architecture": "x86_64",
10      "Url": "(none)",
11      "Summary": "Amazon cloudwatch agent"
12    },

Debug RPM build

When the build log isn't sufficient to debug RPM build failures, you can enter the build environment container to see what's happening:

  1. Add sleep 1000 to the %build section of the package's RPM spec
  2. Run ps aux | grep sleep to find the build container and get its PID
  3. sudo nsenter -a -t <container_pid>
  4. cd home/builder/rpmbuild/BUILD to access the build environment

Inspect RPM package contents

Bottlerocket places RPM packages in ./build/rpms/. When you need to examine files within an RPM package:

1% RPM_FILE=/home/peng/BottlerocketVariants/build/rpms/foo/foo-0.0-0.1755731416.9333e95b.br1.x86_64.rpm
2% IMAGE=public.ecr.aws/amazonlinux/amazonlinux:2023
3% docker run --rm -it -v ${RPM_FILE}:/tmp/foo.rpm $IMAGE /bin/sh
4
5# Inside the container
6sh-5.2# rpm2cpio /tmp/foo.rpm | cpio -t
7sh-5.2# mkdir data && cd data
8sh-5.2# rpm2cpio /tmp/foo.rpm | cpio -idmv
9sh-5.2# ls -lh ./x86_64-bottlerocket-linux-gnu/sys-root/usr/sbin/foo

I often inspect the RPM to examine Bottlerocket's SELinux policy:

1% sesearch -A -s runtime_t -t data_t -c file -p relabelto policy.31
2allow trusted_s global:file { mounton quotaon relabelfrom relabelto };

Inspect disk images

When you need to debug the OS disk image:

 1# using the aws-dev variant as an example.
 2% IMG_FILE=/home/peng/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img.lz4
 3% lz4 -d ${IMG_FILE}
 4% IMG_FILE=/home/peng/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img
 5
 6% fdisk -l ${IMG_FILE}
 7dev-dsk-pezh1-2b-02d2437c % fdisk -l ${IMG_FILE}
 8Disk /home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img: 2 GiB, 2147483648 bytes, 4194304 sectors
 9Units: sectors of 1 * 512 = 512 bytes
10Sector size (logical/physical): 512 bytes / 512 bytes
11I/O size (minimum/optimal): 512 bytes / 512 bytes
12Disklabel type: gpt
13Disk identifier: 67987D5B-4D60-440F-A6F7-B7DD2EFE81DD
14
15Device                                                                                                Start     End Sectors  Size Type
16/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img1     2048   10239    8192    4M BIOS boot
17/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img2    10240   20479   10240    5M EFI System
18/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img3    20480  102399   81920   40M unknown
19/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img4   102400 1986559 1884160  920M unknown
20/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img5  1986560 2007039   20480   10M unknown
21/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img6  2007040 2058239   51200   25M unknown
22/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img7  2058240 2068479   10240    5M unknown
23/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img8  2068480 2150399   81920   40M unknown
24/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img9  2150400 4034559 1884160  920M unknown
25/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img10 4034560 4055039   20480   10M unknown
26/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img11 4055040 4106239   51200   25M unknown
27/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img12 4106240 4190207   83968   41M unknown
28/home/pezh/github/bottlerocket/build/images/x86_64-aws-dev/latest/bottlerocket-aws-dev-x86_64.img13 4190208 4192255    2048    1M unknown
29
30% sudo losetup --find --partscan --show ${IMG_FILE}
31/dev/loop0
32
33% sudo mkdir /mnt/bottlerocket
34% sudo mount /dev/loop0p4 /mnt/bottlerocket
35
36% ls -lh /mnt/bottlerocket
37total 80K
38lrwxrwxrwx. 1 root root    9 Jul 19 01:20 bin -> ./usr/bin
39drwxr-xr-x. 2 root root 4.0K Jul 26 23:10 boot
40drwxr-xr-x. 2 root root 4.0K Jul 19 01:20 dev
41drwxr-xr-x. 4 root root 4.0K Jul 26 23:10 etc
42drwxr-xr-x. 2 root root 4.0K Jul 19 01:20 home
43...
44
45% ls -lh /mnt/bottlerocket/bin/login
46-rwxr-xr-x. 1 root root 30 Apr 11 18:08 /mnt/bottlerocket/bin/login

Once you're done examining the files, unmount and detach the loop device:

1sudo umount -R /mnt/bottlerocket
2sudo losetup -d /dev/loop0