Bootstrapping on AWS

A woman riding a bike.

Bootstrapping is, I think, one of those skills that changes things. It enables you to do more, faster, to cover more ground, and in that respect when you learn to bootstrap it’s a lot like when you first learn to ride a bike.

There’s a whole plethora of bootstrapping options on AWS, but there’s one in particular tool that I wish I’d known about earlier: cfn-init.


Cfn-init is part of the CloudFormation toolset, and at the risk of putting the cart before the horse I’m going to skip over the majority of CloudFormation for now. If you don’t already know CloudFormation it’s well worth a look, but for now it’s enough to know that it allows you to specify AWS resources in a template using JSON code.

Specifically, cfn-init reads and executes instructions entered into the instance metadata within your template, which looks something like this;

"Metadata" : {
    "AWS::CloudFormation::Init" : {
        "configSets" : {
            "full" : [ "config1", "config2" ],
        "config1" : {
            "packages" : {
                "yum" : {
                    "httpd" : []
            "files" : {
                "/var/www/html/index.html" : {
                    "mode" : "000644", "owner" : "apache", "group" : "apache",
                    "source" : ""
        "config2" : {
            "services" : {
                "sysvinit" : {
                    "httpd" : {
                        "enabled" : "true",
                        "ensureRunning" : "true",
                        "packages" : { "yum" : ["httpd"] }

In this simple example we instruct cfn-init to install Apache, pull some content from an S3 Bucket into our instance for Apache to serve, and then make sure Apache is running.
So what instructions can you put into the metadata? A lot!

The full list is given in the AWS documentation here, and to summarise this you can;

  • Install packages from a variety of sources, including YUM and APT repos
  • Pull in an unpack tarballs and zip files from a URL
  • Execute any command
  • Specify the content and properties of any file
  • Control services
  • Create local users and groups

If you’ve used something like Puppet or Chef, this’ll be starting to sound familiar!

The main differences, and potentially advantages, cfn-init has versus the likes of Puppet and Chef are;

  • You don’t need a central server to use it, which means one less system to manage and maintain
  • It integrates with other AWS services, like IAM for access to content in secured S3 buckets

Order of Execution

When I first started looking at cfn-init one of the hardest things to work out was the order of execution – how do I make sure that instruction A is executed before instruction B?

There’s two pieces to this puzzle…

Config Keys

The instructions in the metadata is divided up into one or more config keys. The example above has a two config keys named simply “config1” and “config2”.

Each config key contains a set of instructions, and within that config key there’s a particular order in which cfn-init will execute these instructions regardless of what order you write them in. It took a bit of digging, but I eventually found a forum post that spells this out. The various sections are executed in a very particular order;

  1. packages
  2. sources
  3. users
  4. groups
  5. files
  6. commands
  7. services

Within most of these sections the instructions are unordered, which means they may executed in any order. Three of them behave a bit differently though;

  • Packages: YUM and APT packages are installed first, then all other kinds of package are unordered
  • Commands: These are executed in alphabetical order by name
  • Files: These are processed in lexicographical order

Ordering Commands

I mentioned that the order of execution for commands is based on their name. This is a name you specify rather than the name of the command itself, which makes it easy to order the commands by placing a number at the start of the name, e.g.;

"commands" : {
    "1-mount_all" : {
        "command" : "mount -a"
    "2-update_yum_packages" : {
        "command" : "sudo yum update -y"


So what if you need to, say, execute a command before starting a service?

This is where configsets come in, they allow you to specify which config keys cfn-init executes and in what order. The example above has a single configset name “full”. The syntax of a config set is pretty straight forward: list the names of the config keys to be executed, in the order in which you want them executed.

Executing cfn-init

So once you’ve written all your configuration instructions in the metadata, how do you get cfn-init to actually execute them?

This is where it gets a little bit more complicated. First, you need some other components in your CloudFormation template to allow read the metadata;

  • An IAM user
  • Keys for the IAM user
  • A policy allowing the user to read the metadata

These are used by cfn-init to gain access to the metadata, and they’re pretty simple to define;

"CfnInitUser" : {
    "Type" : "AWS::IAM::User"
"CfnInitUserKeys" : {
    "Type" : "AWS::IAM::AccessKey",
    "Properties" : {
        "UserName" : { "Ref": "CfnInitUser" }
"CfnInitUserPolicies" : {
    "Type" : "AWS::IAM::Policy",
    "Properties" : {
    "PolicyName" : "CfnInitUsers",
    "PolicyDocument" : {
        "Statement": [{
            "Effect" : "Allow",
            "Action" : [
            "Resource" : "*"
        "Users" : [{ "Ref" : "CfnInitUser" }]

While the need for the user and keys are pretty self-explanatory, the policy is perhaps a bit less obvious. From a security standpoint, everything in AWS is implicitly denied, which means nothing is allowed to do anything unless you explicitly permit it. Because of this, is necessary to allow the new user to read the metadata by granting it the allow permission to the “cloudformation:DescribeStackResource” action.

Once you’ve got these, simple put the cfn-init command into the “userdata” property of your instance. The userdata can contain a script which is executed when the instance is launched, and looks a bit like this;

"UserData" : { "Fn::Base64" : { "Fn::Join" : ["",
        "/opt/aws/bin/cfn-init -c full -s ", { "Ref" : "AWS::StackName" }, " -r WebServerLaunchConfig --access-key ", { "Ref" : "CfnInitUserKeys" }, " --secret-key=", { "Fn::GetAtt" : ["CfnInitUserKeys", "SecretAccessKey"]}, " --region ", { "Ref" : "AWS::Region" }, "\n"

As you can see the cfn-init command takes a number of arguments, “-c” specifies which configset to execute, while “-r” points it at the metadata for a particular instance in your template.

I should note at this point that the path I’ve used here, /opt/aws/bin/cfn-init, is valid on Amazon Linux where cfn-init and other tools come pre-installed. On other operating systems you’ll likely need to install the tools yourself, which again can be done in the userdata script. The latest source for these is available from Amazon’s Developer Tools site.

And that’s about it, cfn-init will now bootstrap your instance for you!

Log Files and Diagnostics

First up, you can access and if needed re-execute the userdata script on a running instance. This script is located here;


And when executing it manually it has a “-v” verbose output option which comes in handy for troubleshooting
Cfn-init also produces an often helpful log file, which can be found here;


Lastly, I’ve put a copy of the sample template that most of the examples in this post are pulled from up here (just remove the “.doc” extension which I had to add to get round WordPress’ restrictions). You’ll need to replace the “source” URL for the index.html file with one of your own if you want to try it out, and please do be aware that it will create for which AWS will charge you!

So that’s all for now, and as always, thanks for reading :)


2 responses to “Bootstrapping on AWS

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s