Building custom AMIs with Packer and EC2 instances
TypeScriptIn the context of AWS, an Amazon Machine Image (AMI) is a snapshot of the root filesystem of your instance, which can be used as a template for launching new instances. Packer is an open-source tool designed to automate the creation of machine images for multiple platforms. You’ll use Packer to create your custom AMIs by provisioning EC2 instances with your configuration, and then Pulumi to create an instance based on this AMI.
First, you create a custom AMI using Packer. Packer will run on an EC2 instance, provision it according to your specifications, and then create an AMI from that instance.
Once the AMI is created, we will use the Pulumi AWS package to provision an EC2 instance from the AMI. Let’s start by looking at the code required to provision an EC2 instance using Pulumi.
Before running this Pulumi code, you need to have Packer installed on your machine, AWS credentials set up, and you will need to run a Packer build that outputs the id of the newly created AMI.
Here is a template for a Pulumi program that launches an AWS EC2 instance using a custom AMI:
import * as aws from "@pulumi/aws"; // Assuming you have the AMI id from your Packer build process, // which should look something like this: 'ami-1234abcd'. const customAmiId = "ami-1234abcd"; const instance = new aws.ec2.Instance("web-server-www", { // The type of instance to start instanceType: "t2.micro", // The AMI id provided by Packer after successful image creation. ami: customAmiId, // The name of the SSH keypair you have registered with AWS. keyName: "my-key-pair", // Configuring network access to your instance vpcSecurityGroupIds: ["sg-12345678"], // Replace with your security group id // The subnet to start the instance in. subnetId: "subnet-12345678", // Replace with your subnet id // The data to use for bootstrapping the instance. // For example, you could use this to install software on instance start. userData: `#!/bin/bash echo "Hello, World!" > index.html nohup python -m SimpleHTTPServer 80 &`, // The tags will be applied to the instance and are used to // identify and organize your resources within the AWS console. tags: { Name: "web-server-www", }, }); // The public IP address of the instance can be used to access the instance over SSH. export const publicIp = instance.publicIp; export const publicHostName = instance.publicDns;
In this TypeScript program, we use Pulumi to declare an AWS EC2 instance (
aws.ec2.Instance
). We specify the instance type (instanceType
) as't2.micro'
, which is suitable for low to moderate traffic websites or experimentation. The AMI ID (ami
) is the one that Packer would have created.We've also defined a security group and subnet ID that the instance will belong to. These will need to be replaced with your actual security group and subnet IDs.
The
userData
parameter is used to pass a script or other user data to the instance. In this example, it's a simple bash script that creates an index.html file with "Hello, World!" and serves it over a simple HTTP server. In actual scenarios, you might useuserData
to run more complex provisioning scripts.Lastly, we export the public IP (
publicIp
) and hostname (publicHostName
) of the instance so that you can access it.Please note that you need to replace
customAmiId
,my-key-pair
, the VPCsecurityGroupIds
, and thesubnetId
with actual values from your AWS environment and Packer output.Keep in mind that this Pulumi code assumes that you have the correct IAM permissions set up and that your AWS environment is correctly configured with the VPC, subnet, and security group.
With Pulumi, you define your desired state with real code and provision your cloud resources with that code, making sure your infrastructure is versioned, reusable, and shareable.