<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mathieu Rey</title>
    <description>The latest articles on DEV Community by Mathieu Rey (@matrey).</description>
    <link>https://dev.to/matrey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F459223%2Fd143f3cb-9e87-4045-b588-4bbbe1bbfd48.jpg</url>
      <title>DEV Community: Mathieu Rey</title>
      <link>https://dev.to/matrey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matrey"/>
    <language>en</language>
    <item>
      <title>Build your own Ubuntu AMI</title>
      <dc:creator>Mathieu Rey</dc:creator>
      <pubDate>Thu, 15 Oct 2020 06:40:01 +0000</pubDate>
      <link>https://dev.to/matrey/build-your-own-ubuntu-ami-4k9h</link>
      <guid>https://dev.to/matrey/build-your-own-ubuntu-ami-4k9h</guid>
      <description>&lt;p&gt;The steps in this article are run from an EC2 instance in the same region you want your AMI to be registered into. This builder VM should have the same architecture &amp;amp; OS as the image you intend to build. &lt;br&gt;
You can use the official Ubuntu AMI the first time, then use your own Ubuntu AMI for subsequent builds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that the following focuses on building an Ubuntu &lt;code&gt;bionic&lt;/code&gt; (18.04 LTS) AMI, as it has been my workhorse for the past 2 years, and is still supported for another couple of years.&lt;br&gt;
But if you are starting fresh, you should probably aim at &lt;code&gt;focal&lt;/code&gt; (20.04 LTS).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Leverage Ubuntu Cloud Images
&lt;/h2&gt;

&lt;p&gt;Rather than attempting to build an image from scratch, we will start with a ready made image from &lt;a href="https://cloud-images.ubuntu.com" rel="noopener noreferrer"&gt;https://cloud-images.ubuntu.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given a codename (e.g. &lt;code&gt;bionic&lt;/code&gt; or &lt;code&gt;focal&lt;/code&gt;) we need to identify the most recent release available.&lt;/p&gt;

&lt;p&gt;Ubuntu publishes release data in simple streams format. You can read more about it on &lt;a href="https://github.com/smoser/talk-simplestreams/blob/master/Notes.txt" rel="noopener noreferrer"&gt;https://github.com/smoser/talk-simplestreams/blob/master/Notes.txt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following can be used to find the most recent &lt;code&gt;bionic&lt;/code&gt; build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;simplestreams
&lt;span class="nv"&gt;$ &lt;/span&gt;sstream-query &lt;span class="nt"&gt;--no-verify&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="nt"&gt;--max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 https://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:download.sjson &lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="nv"&gt;release&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'bionic'&lt;/span&gt; &lt;span class="nv"&gt;ftype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'disk1.img'&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"aliases"&lt;/span&gt;: &lt;span class="s2"&gt;"18.04,b,bionic"&lt;/span&gt;,
    &lt;span class="s2"&gt;"arch"&lt;/span&gt;: &lt;span class="s2"&gt;"amd64"&lt;/span&gt;,
    &lt;span class="s2"&gt;"content_id"&lt;/span&gt;: &lt;span class="s2"&gt;"com.ubuntu.cloud:released:download"&lt;/span&gt;,
    &lt;span class="s2"&gt;"datatype"&lt;/span&gt;: &lt;span class="s2"&gt;"image-downloads"&lt;/span&gt;,
    &lt;span class="s2"&gt;"format"&lt;/span&gt;: &lt;span class="s2"&gt;"products:1.0"&lt;/span&gt;,
    &lt;span class="s2"&gt;"ftype"&lt;/span&gt;: &lt;span class="s2"&gt;"disk1.img"&lt;/span&gt;,
    &lt;span class="s2"&gt;"item_name"&lt;/span&gt;: &lt;span class="s2"&gt;"disk1.img"&lt;/span&gt;,
    &lt;span class="s2"&gt;"item_url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://cloud-images.ubuntu.com/releases/server/releases/bionic/release-20201014/ubuntu-18.04-server-cloudimg-amd64.img"&lt;/span&gt;,
    &lt;span class="s2"&gt;"label"&lt;/span&gt;: &lt;span class="s2"&gt;"release"&lt;/span&gt;,
    &lt;span class="s2"&gt;"license"&lt;/span&gt;: &lt;span class="s2"&gt;"http://www.canonical.com/intellectual-property-policy"&lt;/span&gt;,
    &lt;span class="s2"&gt;"md5"&lt;/span&gt;: &lt;span class="s2"&gt;"9aa011b2b79b1fe42a7c306555923b1b"&lt;/span&gt;,
    &lt;span class="s2"&gt;"os"&lt;/span&gt;: &lt;span class="s2"&gt;"ubuntu"&lt;/span&gt;,
    &lt;span class="s2"&gt;"path"&lt;/span&gt;: &lt;span class="s2"&gt;"server/releases/bionic/release-20201014/ubuntu-18.04-server-cloudimg-amd64.img"&lt;/span&gt;,
    &lt;span class="s2"&gt;"product_name"&lt;/span&gt;: &lt;span class="s2"&gt;"com.ubuntu.cloud:server:18.04:amd64"&lt;/span&gt;,
    &lt;span class="s2"&gt;"pubname"&lt;/span&gt;: &lt;span class="s2"&gt;"ubuntu-bionic-18.04-amd64-server-20201014"&lt;/span&gt;,
    &lt;span class="s2"&gt;"release"&lt;/span&gt;: &lt;span class="s2"&gt;"bionic"&lt;/span&gt;,
    &lt;span class="s2"&gt;"release_codename"&lt;/span&gt;: &lt;span class="s2"&gt;"Bionic Beaver"&lt;/span&gt;,
    &lt;span class="s2"&gt;"release_title"&lt;/span&gt;: &lt;span class="s2"&gt;"18.04 LTS"&lt;/span&gt;,
    &lt;span class="s2"&gt;"sha256"&lt;/span&gt;: &lt;span class="s2"&gt;"9fdd8fa3091b8a40ea3f571d3461b246fe4e75fbd329b217076f804c9dda06a3"&lt;/span&gt;,
    &lt;span class="s2"&gt;"size"&lt;/span&gt;: &lt;span class="s2"&gt;"359923712"&lt;/span&gt;,
    &lt;span class="s2"&gt;"support_eol"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-04-26"&lt;/span&gt;,
    &lt;span class="s2"&gt;"supported"&lt;/span&gt;: &lt;span class="s2"&gt;"True"&lt;/span&gt;,
    &lt;span class="s2"&gt;"updated"&lt;/span&gt;: &lt;span class="s2"&gt;"Wed, 14 Oct 2020 18:20:34 +0000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"version"&lt;/span&gt;: &lt;span class="s2"&gt;"18.04"&lt;/span&gt;,
    &lt;span class="s2"&gt;"version_name"&lt;/span&gt;: &lt;span class="s2"&gt;"20201014"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However, it feels a bit overkill for what we need, and fortunately, there is also a "low tech" option available, relying on some text files at specific URLs.&lt;/p&gt;

&lt;p&gt;There is a section at the bottom of &lt;a href="https://help.ubuntu.com/community/UEC/Images" rel="noopener noreferrer"&gt;https://help.ubuntu.com/community/UEC/Images&lt;/a&gt; explaining how these text files work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Machine Consumable Ubuntu Cloud Guest images Availability Data
&lt;/h4&gt;

&lt;p&gt;In order to provide information about what builds are available for download or running on ec2, a 'query' interface is exposed at &lt;a href="http://cloud-images.ubuntu.com/query" rel="noopener noreferrer"&gt;http://cloud-images.ubuntu.com/query&lt;/a&gt; . This will allow users of the service to download images or find out the latest ec2 AMIs programmatically.&lt;/p&gt;

&lt;p&gt;The data is laid out as follows:&lt;/p&gt;

&lt;p&gt;There are 2 files in top level director 'daily.latest.txt' and 'released.latest.txt'. Each of these files contains tab delimited data, with 4 fields per record. daily.latest.txt has information about the daily builds, released.latest.tt about released builds:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;suite&amp;gt; &amp;lt;build_name&amp;gt; &amp;lt;label&amp;gt;     &amp;lt;serial&amp;gt;
hardy   server       release     20100128
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;For each record in the top level files another set of files will exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;suite&amp;gt;/&amp;lt;build_name&amp;gt;/released-dl.current.txt&lt;/code&gt; downloadable images data for the most recent released build&lt;/li&gt;
&lt;li&gt;[...]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downloadable image data files contain 7 tab delimited fields:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;suite&amp;gt;  &amp;lt;build_name&amp;gt; &amp;lt;label&amp;gt; &amp;lt;serial&amp;gt; &amp;lt;&lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;download_path&amp;gt; &amp;lt;suggested_name&amp;gt;
maverick server       daily   20100826 i386   server/maverick/20100826/maverick-server-uec-i386.tar.gz  ubuntu-maverick-daily-i386-server-20100826
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;We are interested in the "released" version, not the "daily" builds.&lt;br&gt;
So we just need to download &lt;a href="https://cloud-images.ubuntu.com/query/bionic/server/released-dl.current.txt" rel="noopener noreferrer"&gt;https://cloud-images.ubuntu.com/query/bionic/server/released-dl.current.txt&lt;/a&gt; and grep for our architecture:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-Ss&lt;/span&gt; &lt;span class="s1"&gt;'https://cloud-images.ubuntu.com/query/bionic/server/released-dl.current.txt'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;amd64 | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\t'&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;
bionic
server
release
20201014
amd64
server/releases/bionic/release-20201014/ubuntu-18.04-server-cloudimg-amd64.tar.gz
ubuntu-bionic-18.04-amd64-server-20201014
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that we could also directly take a blind shot and download from &lt;a href="https://cloud-images.ubuntu.com/releases/bionic/release/" rel="noopener noreferrer"&gt;https://cloud-images.ubuntu.com/releases/bionic/release/&lt;/a&gt;, which always points to the latest release. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;knowing the build date helps validate the most recent image is newer than what we already have (2 to 3 weeks can elapse between 2 "released" images)&lt;/li&gt;
&lt;li&gt;we would need to rely on a hardcoded name fragment, e.g. "ubuntu-18.04-server-cloudimg-amd64" for &lt;code&gt;bionic&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Convert the image
&lt;/h2&gt;

&lt;p&gt;Column 6 of "released-dl.current.txt" provides us the "download_path", with a URI ending in ".tar.gz":&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;server/releases/bionic/release-20201014/ubuntu-18.04-server-cloudimg-amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;According to the directory listing, this is in a "Cloud Image/EC2 tarball" format. &lt;/p&gt;

&lt;p&gt;However... I never got a booting instance this way, so instead we will go for the ".img" version, which un-helpfully reads "USB image", but is actually a qcow2 disk image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0g555wul6gwwz6lrxt5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0g555wul6gwwz6lrxt5q.png" alt="Listing of amd64 cloud image formats"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we need to convert the qcow2 image into raw. Note that where qcow2 images are "sparse" (unused disk space doesn't count), raw images require the same amount of space as their size (i.e. several GB).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;qemu-utils
&lt;span class="nv"&gt;$ &lt;/span&gt;qemu-img convert &lt;span class="nt"&gt;-O&lt;/span&gt; raw ubuntu-18.04-server-cloudimg-amd64.img ubuntu-18.04-server-cloudimg-amd64.raw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The raw image is ready to "burn" into an EBS volume. We will leverage the AWS CLI for the following steps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make the AMI
&lt;/h2&gt;

&lt;p&gt;Here is a high-level overview of what we need to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new EBS volume&lt;/li&gt;
&lt;li&gt;Attach the new EBS to the current instance managing the build&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;dd&lt;/code&gt; to write the raw image to the volume&lt;/li&gt;
&lt;li&gt;Detach the volume&lt;/li&gt;
&lt;li&gt;Request a snapshot of the volume and wait until it is completed&lt;/li&gt;
&lt;li&gt;Delete the volume&lt;/li&gt;
&lt;li&gt;Register the snapshot as an AMI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will need to create an IAM policy that allows these actions through AWS APIs, without exposing the account too much.&lt;br&gt;
Granularity is not too great, and the way I found to lock down actions as much as possible relies on tags (on the builder instance and on the volume &amp;amp; snapshot).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The builder VM should have a tag "WithRole=amibuilder"&lt;/li&gt;
&lt;li&gt;We will give the same tag to the temporary EBS volume we will write the image onto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While not as tightly locked down as I would have liked, restricting permissions this way still has the nice side-effect of preventing some mistakes (e.g. can't detach the wrong volume from a different instance)&lt;/p&gt;

&lt;p&gt;Here is the IAM policy I ended up with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"ec2:RegisterImage"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:DescribeVolumes"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:CreateSnapshot"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:DescribeSnapshots"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:CreateVolume"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;,
            &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"*"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"ec2:DetachVolume"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:AttachVolume"&lt;/span&gt;,
                &lt;span class="s2"&gt;"ec2:DeleteVolume"&lt;/span&gt;
            &lt;span class="o"&gt;]&lt;/span&gt;,
            &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"*"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Condition"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"ec2:ResourceTag/WithRole"&lt;/span&gt;: &lt;span class="s2"&gt;"amibuilder"&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="s2"&gt;"ec2:CreateTags"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:ec2:*:*:volume/*"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Condition"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"ec2:CreateAction"&lt;/span&gt;: &lt;span class="s2"&gt;"CreateVolume"&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="s2"&gt;"ec2:CreateTags"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:ec2:*:*:snapshot/*"&lt;/span&gt;,
            &lt;span class="s2"&gt;"Condition"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"ec2:CreateAction"&lt;/span&gt;: &lt;span class="s2"&gt;"CreateSnapshot"&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Subsequent steps rely on the AWS cli. Setup instructions are provided at: &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; aws awscliv2.zip 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We want to create a volume just big enough for our raw image. So for that, we need to check the image size, and the trick is to use &lt;code&gt;du --apparent-size&lt;/code&gt; (on some &lt;code&gt;bionic&lt;/code&gt; raw image, &lt;code&gt;du&lt;/code&gt; returned 1.1 GB whereas &lt;code&gt;du --apparent-size&lt;/code&gt; returned a more proper 2.2 GB). And using &lt;code&gt;--block-size=1G&lt;/code&gt; directly gives us a value rounded up to the next GB.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;--block-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1G &lt;span class="nt"&gt;--apparent-size&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-18.04-server-cloudimg-amd64.raw"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1 &lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;aws ec2 create-volume &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=volume,Tags=[{Key=WithRole,Value=amibuilder},{Key=Name,Value="AMI building"}]'&lt;/span&gt; &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt; &lt;span class="nt"&gt;--size&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SIZE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--volume-type&lt;/span&gt; gp2 &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'VolumeId'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once the volume is created, we need to attach it to our builder instance. But it's not really obvious where it will end up: &lt;code&gt;/dev/sd{x}&lt;/code&gt;? &lt;code&gt;/dev/xvd{x}&lt;/code&gt;? &lt;code&gt;/dev/nvme{x}n{y}&lt;/code&gt;? &lt;br&gt;
We will use &lt;code&gt;lsblk&lt;/code&gt; before attaching the volume, then after attaching it, and compare the two, to figure out where the new volume landed.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsblk &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .blockdevices[].name | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-before.txt"&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-before.txt"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-after.txt"&lt;/span&gt;

&lt;span class="nv"&gt;instance_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-Ss&lt;/span&gt; http://169.254.169.254/latest/meta-data/instance-id&lt;span class="si"&gt;)&lt;/span&gt;
aws ec2 attach-volume &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/sdi &lt;span class="nt"&gt;--instance-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$instance_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$volumeid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'State'&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;cmp &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-before.txt"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-after.txt"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;lsblk &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .blockdevices[].name | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-after.txt"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# If we are here, it means a new device appeared&lt;/span&gt;
&lt;span class="nv"&gt;NEWDEV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="nb"&gt;comm&lt;/span&gt; &lt;span class="nt"&gt;-13&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-before.txt"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/volumes-after.txt"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NEWDEV&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As an extra precaution, in the case of NVMe volumes, we can actually query the "sn" attribute and confirm it matches the ID returned by &lt;code&gt;ec2 create-volume&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;nvme-cli
&lt;span class="nv"&gt;$ &lt;/span&gt;nvme id-ctrl &lt;span class="s2"&gt;"/dev/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEVICE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .sn | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/vol/vol-/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We then just &lt;code&gt;dd&lt;/code&gt; the raw image onto the device&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ubuntu-18.04-server-cloudimg-amd64.raw"&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dev&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then run the rest of the API calls to detach the volume and make a snapshot of it. Note that snapshots are fairly slow, it's not uncommon to have to wait several minutes, even for a tiny 4 GB volume.&lt;br&gt;
Once we have the snapshot, we can delete the volume.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Detach the volume&lt;/span&gt;
aws ec2 detach-volume &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$volumeid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'State'&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;aws ec2 describe-volumes &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$volumeid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Volumes[*].State'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; available&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Create a snapshot of the volume&lt;/span&gt;
&lt;span class="nv"&gt;snapshotid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ec2 create-snapshot &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=snapshot,Tags=[{Key=Name,Value="For AMI"}]'&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$volumeid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'SnapshotId'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;aws ec2 describe-snapshots &lt;span class="nt"&gt;--snapshot-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$snapshotid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Snapshots[*].State'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; pending&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;10&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# We can now delete the volume&lt;/span&gt;
aws ec2 delete-volume &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$volumeid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally, we register the snapshot as an AMI. &lt;/p&gt;

&lt;p&gt;If you want to have a nice logo in the AMI list, make sure to include "ubuntu" somewhere into the AMI name.&lt;br&gt;
Thanks &lt;a href="https://www.turnkeylinux.org/comment/12501:" rel="noopener noreferrer"&gt;https://www.turnkeylinux.org/comment/12501:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS automatically 'determine' the platform by parsing the image name. So, if Ubuntu is included in the image name, the platform will be Ubuntu. If Redhat is included in the name, the platform will be Redhat. It's just for show...&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register the snapshot as a new AMI&lt;/span&gt;
&lt;span class="nv"&gt;block_device_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[
  {
    "DeviceName": "/dev/sda1",
    "Ebs": {
      "DeleteOnTermination": false, 
      "SnapshotId": "&lt;/span&gt;&lt;span class="nv"&gt;$snapshotid&lt;/span&gt;&lt;span class="sh"&gt;",
      "VolumeSize": &lt;/span&gt;&lt;span class="nv"&gt;$SIZE&lt;/span&gt;&lt;span class="sh"&gt;,
      "VolumeType": "gp2"
    }
  }, {
    "DeviceName": "/dev/sdb",
    "VirtualName": "ephemeral0"
  }
]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;amiid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ec2 register-image &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--ena-support&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--architecture&lt;/span&gt; x86_64 &lt;span class="nt"&gt;--virtualization-type&lt;/span&gt; hvm &lt;span class="nt"&gt;--block-device-mapping&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$block_device_mapping&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--root-device-name&lt;/span&gt; &lt;span class="s2"&gt;"/dev/sda1"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'ImageId'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Published AMI &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;amiid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in region &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it! You are now able to use your own AMI! Which is... pretty much identical to the official Ubuntu AMI. So what's the point?&lt;/p&gt;

&lt;p&gt;Building our own AMIs actually allows us to customize them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Customize the AMI
&lt;/h2&gt;

&lt;p&gt;Once you have the raw image, you can actually attach it to a loop device, mount it, and edit it.&lt;br&gt;
For that we will use &lt;code&gt;losetup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;devloop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt; losetup &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# e.g. /dev/loop0&lt;/span&gt;
losetup &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-18.04-server-cloudimg-amd64.raw"&lt;/span&gt;

&lt;span class="nv"&gt;MOUNTPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mount/image
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
mount &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;devloop&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;p1"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, in order to run our custom script in a &lt;code&gt;chroot&lt;/code&gt;, we need to tweak the environment a bit:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow network access from chroot environment&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;/etc/resolv.conf"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;/etc/resolv.conf"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf.bak
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/resolv.conf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf

&lt;span class="c"&gt;# Extra mounts&lt;/span&gt;
mount &lt;span class="nt"&gt;-t&lt;/span&gt; proc none &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/proc/
mount &lt;span class="nt"&gt;-t&lt;/span&gt; sysfs none &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/sys/
mount &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nb"&gt;bind&lt;/span&gt; /dev &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/dev/

&lt;span class="c"&gt;# prevent daemons from starting during apt-get&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/sh\nexit 101'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/usr/sbin/policy-rc.d
&lt;span class="nb"&gt;chmod &lt;/span&gt;755 &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/usr/sbin/policy-rc.d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can now run our script under &lt;code&gt;chroot&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CHROOT_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/tmp/custom_user_script
&lt;span class="nb"&gt;chroot&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt; /tmp/custom_user_script
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/tmp/custom_user_script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is not very different from RUN lines in a Dockerfile. Here are for instance some commands I would use to setup Docker, Netdata (monitoring) and Fluentbit (logging) on the image:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add utilities&lt;/span&gt;
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https ca-certificates curl software-properties-common make zip unzip jq

&lt;span class="c"&gt;# Install docker&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/docker.asc &lt;span class="s1"&gt;'https://download.docker.com/linux/ubuntu/gpg'&lt;/span&gt;
add-apt-repository &lt;span class="s2"&gt;"deb [arch=amd64] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt;
apt-get update
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker

&lt;span class="c"&gt;# Install netdata&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/netdata.asc https://packagecloud.io/netdata/netdata/gpgkey
add-apt-repository &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"deb https://packagecloud.io/netdata/netdata/ubuntu/ bionic main"&lt;/span&gt;
apt-get update
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; netdata

&lt;span class="c"&gt;# Install fluentbit&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/fluentbit.asc http://packages.fluentbit.io/fluentbit.key
add-apt-repository &lt;span class="s2"&gt;"deb http://packages.fluentbit.io/ubuntu/bionic bionic main"&lt;/span&gt;
apt-get update
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; td-agent-bit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that some packages don't like being installed that way ; e.g. I wasn't able to preinstall Percona server, because it has a post-install script waiting forever on the service to start. YMMV.&lt;/p&gt;

&lt;p&gt;Once we are done, we cleanup out tweaks:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Unmount extra mountpoints&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;PT &lt;span class="k"&gt;in &lt;/span&gt;dev proc sys&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;umount &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$PT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Put resolv.conf symlink back in place&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;  &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;/etc/resolv.conf.bak"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;&lt;span class="s2"&gt;/etc/resolv.conf.bak"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf.bak &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/etc/resolv.conf
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Clean up policy-rc.d&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$MOUNTPOINT&lt;/span&gt;/usr/sbin/policy-rc.d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Complete script
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not... ?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not use the official AMI directly?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Back in 2016 AWS China had no marketplace, you had to build your own AMI&lt;/li&gt;
&lt;li&gt;It allows customizing the instance, and contrary to &lt;code&gt;cloud-config&lt;/code&gt;, the instance is ready to use right after boot&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why not create an instance, configure it and make its snapshot into an AMI?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;From prior Windows sysadmin experience, I had to &lt;code&gt;sysprep&lt;/code&gt; the master instance to reset the instance-specific SID&lt;/li&gt;
&lt;li&gt;On Linux, it's not very clear what should be removed / cleaned up: bash history, SSH host keys, authorized_keys, but probably also some more arcane things like &lt;code&gt;/etc/machine-id&lt;/code&gt;, &lt;code&gt;/var/lib/systemd/random-seed&lt;/code&gt;, etc. It's much better if they have never been there in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why not use packer?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Scratch your own itch? (:&lt;/li&gt;
&lt;li&gt;What we did is probably similar to Packer's Amazon &lt;code&gt;chroot&lt;/code&gt; builder: &lt;a href="https://www.packer.io/docs/builders/amazon-chroot" rel="noopener noreferrer"&gt;https://www.packer.io/docs/builders/amazon-chroot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/alestic/alestic-git/blob/master/bin/alestic-git-build-ami" rel="noopener noreferrer"&gt;https://github.com/alestic/alestic-git/blob/master/bin/alestic-git-build-ami&lt;/a&gt; for the overall approach and ec2 commands&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kickstarter/build-ubuntu-ami/blob/master/data/user_data.sh.erb" rel="noopener noreferrer"&gt;https://github.com/kickstarter/build-ubuntu-ami/blob/master/data/user_data.sh.erb&lt;/a&gt; for the user script run in chroot&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.tinned-software.net/mount-raw-image-of-entire-disc/" rel="noopener noreferrer"&gt;https://blog.tinned-software.net/mount-raw-image-of-entire-disc/&lt;/a&gt; for how to mount the raw image with losetup&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ubuntu</category>
    </item>
  </channel>
</rss>
