AWS CloudFormation
User Guide (API Version 2010-05-15)
« PreviousNext »
View the PDF for this guide.Go to the AWS Discussion Forum for this product.Go to the Kindle Store to download this guide in Kindle format.Did this page help you?  Yes | No |  Tell us about it...

Deploying Applications with AWS CloudFormation

AWS CloudFormation Deploying Applications Overview

You can use AWS CloudFormation to automatically install, configure, and start up your applications. Doing so can save you a lot of time and effort. For example, there are a number of ways you could deploy an Amazon instance to run a LAMP (Linux, Apache, MySQL, and PHP) web server:

  • Manual installation. A manual installation is reliable, but it is also time-consuming. You can reduce the effort by choosing an AMI that already contains all the necessary software, but you will still need to connect to the resulting Amazon EC2 instance to complete the configuration process and start your applications.

  • Cloud-init. The latest Linux AMIs contain cloud-init, an open source program that automatically configures and starts your applications according to a script that you provide. If you want to change the configuration or install new applications or updates, you must connect to the instance and make the changes manually.

Using AWS CloudFormation to automate deployment of your application provides two advantages. First, it's easier to duplicate a deployment when all of the installation, configuration, and startup commands are included in the CloudFormation template. Second, you can update an existing installation without connecting directly to the instance.

AWS CloudFormation includes a set of helper applications (cfn-init, cfn-signal, cfn-get-metadata, and cfn-hup) that are based on cloud-init. These helper applications not only provide functionality similar to cloud-init, but also allow you to update your metadata after your instance and applications are up and running. You can update the metadata after deployment because AWS CloudFormation stores the metadata. This added flexibility does require some additional setup—you need to create security credentials for the instance so that the instance can call the CloudFormation API to retrieve the updated metadata.

The following sections describe how to create a template that describes a LAMP stack and uses cfn-init to install, configure and start Apache, MySQL, and PHP. We'll start with a simple template that sets up a basic Amazon EC2 instance running Linux, and then we'll continue adding to the template until it describes a full LAMP stack with deployment instructions.

Create a Basic Amazon EC2 Instance Using CloudFormation

We start with a basic template that defines a single Amazon EC2 instance with a security group that allows SSH traffic on port 22 and HTTP traffic on port 80. The template consists of five top-level JSON objects:

  • Description—EC2_Single_Instance

    EC2_Single_Instance specifies a single EC2 instance and a corresponding security group that allows SSH and HTTP access.

  • Parameters—KeyName and InstanceType

    The KeyName parameter specifies the EC2 keypair to use for SSH access, and the InstanceType parameter specifies the type of EC2 instance.

  • Mappings—AWSInstanceType2Arch and AWSRegionArch2AMI

    AWSInstanceType2Arch maps the proper architecture to the instance size so that the template user need specify only the instance size. AWSRegionArch2AMI maps AMI IDs to their specific region so that template users do not need to research which AMI IDs are available in each region.

  • Resources—WebServer and WebServerSecurityGroup

    The WebServer resource defines the Amazon EC2 instance and the WebServerSecurityGroup defines the security group that allows incoming traffic on ports 22 (SSH) and 80 (HTTP).

  • Outputs—WebsiteURL

    The WebsiteURL returns the public URL for the newly created website.

Here is the template:

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template EC2_Single_Instance: Create a single EC2 instance. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Parameters" : {

    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
      "Type" : "String",
      "MinLength": "1",
      "MaxLength": "64",
      "AllowedPattern" : "[-_ a-zA-Z0-9]*",
      "ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores."
    },

    "InstanceType" : {
      "Description" : "WebServer EC2 instance type",
      "Type" : "String",
      "Default" : "m1.small",
      "AllowedValues" : [ "t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "c1.medium", "c1.xlarge" ],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    }

  },

  "Mappings" : {
    "AWSInstanceType2Arch" : {
      "t1.micro"    : { "Arch" : "32" },
      "m1.small"    : { "Arch" : "32" },
      "m1.large"    : { "Arch" : "64" },
      "m1.xlarge"   : { "Arch" : "64" },
      "m2.xlarge"   : { "Arch" : "64" },
      "m2.2xlarge"  : { "Arch" : "64" },
      "m2.4xlarge"  : { "Arch" : "64" },
      "c1.medium"   : { "Arch" : "32" },
      "c1.xlarge"   : { "Arch" : "64" }
    },
    "AWSRegionArch2AMI" : {
      "us-east-1"      : { "32" : "ami-7f418316", "64" : "ami-7341831a" },
      "us-west-1"      : { "32" : "ami-951945d0", "64" : "ami-971945d2" },
      "us-west-2"      : { "32" : "ami-16fd7026", "64" : "ami-10fd7020" },
      "eu-west-1"      : { "32" : "ami-24506250", "64" : "ami-20506254" },
      "sa-east-1"      : { "32" : "ami-3e3be423", "64" : "ami-3c3be421" },
      "ap-southeast-1" : { "32" : "ami-74dda626", "64" : "ami-7edda62c" },
      "ap-northeast-1" : { "32" : "ami-dcfa4edd", "64" : "ami-e8fa4ee9" }
    }
  },

  "Resources" : {

    "WebServer": {
      "Type": "AWS::EC2::Instance",

      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName"        : { "Ref" : "KeyName" }

      }
    },


    "WebServerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access via port 80",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"}
        ]
      }
    }
  },

  "Outputs" : {
    "WebsiteURL" : {
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServer", "PublicDnsName" ]}]] },
      "Description" : "URL for newly created EC2 Instance"
    }
  }
}

Automate LAMP Installation Using CloudFormation

In this section, we'll build on the basic Amazon EC2 template to create a LAMP template that automatically installs Apache, MySQL, and PHP. In later sections, we'll configure and start the applications. Before we add the necessary metadata and shell commands to the template, we need to set up an IAM user with permissions to read the AWS CloudFormation metadata.

In order to read the metadata from an Amazon EC2 instance created in the template, we recommend creating an IAM user in the template and passing the AWS access key ID and secret access key to the instance as metadata. Give the IAM user permission to call only the DescribeStackResource action. Creating an IAM user locks down the account so that it can only retrieve metadata, but it can perform no other action. By using the IAM features in the template, the existence of the user is tied to the lifetime of the stack. Each new stack will have a separate and unique user. For more information about using IAM in AWS CloudFormation, see Controlling Access with AWS Identity and Access Management.

The updated template includes several new sections. The stack created by this template installs Apache, MySQL, and PHP, but it does not configure or start any of them. The new template sections are as follows:

  • Metadata Key—AWS::CloudFormation::Init

    Cfn-init reads this metadata key and installs the packages listed in this key (e.g., httpd, mysql, and php). Cfn-init also retrieves and expands files listed as sources.

  • Properties Resource—UserData

    The UserData key allows you to execute shell commands. This template issues two shell commands: the first command installs the AWS CloudFormation helper scripts; the second executes the cfn-init script.

The following example template creates a LAMP template that automatically installs Apache, MySQL, and PHP. Sections marked with an ellipsis (...) are omitted for brevity. Additions to the template are shown in red italic text.

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template LAMP_Install_Only: ...",

  "Parameters" : {

    "KeyName" : { ... },

    "InstanceType" : { ...  },

  "Mappings" : { ... },

  "Resources" : {
    "WebServer": {
      "Type": "AWS::EC2::Instance",
      "Metadata" : {
        "Comment1" : "Configure the bootstrap helpers to install the Apache Web Server and PHP",
        "Comment2" : "The website content is downloaded from the CloudFormationPHPSample.zip file",

        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "mysql"        : [],
                "mysql-server" : [],
                "mysql-libs"   : [],
                "httpd"        : [],
                "php"          : [],
                "php-mysql"    : []
              }
            },

            "sources" : {
              "/var/www/html" : "https://s3.amazonaws.com/cloudformation-examples/CloudFormationPHPSample.zip"
            }
          }
        }
      },
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",
          "yum update -y aws-cfn-bootstrap\n",

          "# Install LAMP packages\n",
          "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" }, " -r WebServer ",
          "    --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n"
		  ]]}}        
      }
    },


    "WebServerSecurityGroup" : { ...  }
  },

  "Outputs" : { ... }
}

Automate LAMP Configuration Using CloudFormation

In this section, we'll use AWS CloudFormation to automatically configure and start Apache, MySQL, and PHP.

  • Parameters—DBName, DBUsername, DBPassword, and DBRootPassword

    These parameters allow the template user to specify database names and passwords. The parameters also contain constraints that catch incorrectly formatted values before stack creation begins.

  • Metadata—files and services

    The files metadata key creates a MySQL setup file. The services metadata key ensures that the httpd and mysqld services are not only running when cfn-init finishes (ensureRunning is set to true); but that they are also restarted upon reboot (enabled is set to true).

  • Properties Resource—UserData

    The UserData key contains the MySQL shell commands that create a database and a user now follow the call to cfn-init. Additionally, shell commands to configure PHP follow the MySQL shell commands.

The following example template creates a LAMP stack, automatically installs Apache, MySQL, and PHP, and then configures and starts each service. Sections marked with an ellipsis (...) are omitted for brevity. Additions to the template are shown in red italic text.

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template LAMP_Single_Instance: Create a LAMP stack using a single EC2 instance and a local MySQL database for storage. This template demonstrates using the AWS CloudFormation bootstrap scripts to install the packages and files necessary to deploy the Apache web server, PHP and MySQL at instance launch time. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",

  "Parameters" : {

    "KeyName" : { ...  },

    "DBName": {
      "Default": "MyDatabase",
      "Description" : "MySQL database name",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "64",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },

    "DBUsername": {
      "NoEcho": "true",
      "Description" : "Username for MySQL database access",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "16",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },

    "DBPassword": {
      "NoEcho": "true",
      "Description" : "Password for MySQL database access",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },

    "DBRootPassword": {
      "NoEcho": "true",
      "Description" : "Root password for MySQL",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },

    "InstanceType" : { ...  }

  },

  "Mappings" : {
  ...
  },

  "Resources" : {
    "WebServer": {
      "Type": "AWS::EC2::Instance",
      "Metadata" : {
        "Comment1" : "Configure the bootstrap helpers to install the Apache Web Server and PHP",
        "Comment2" : "The website content is downloaded from the CloudFormationPHPSample.zip file",

        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "mysql"        : [],
                "mysql-server" : [],
                "mysql-libs"   : [],
                "httpd"        : [],
                "php"          : [],
                "php-mysql"    : []
              }
            },

            "sources" : {
              "/var/www/html" : "https://s3.amazonaws.com/cloudformation-examples/CloudFormationPHPSample.zip"
            },

            "files" : {
              "/tmp/setup.mysql" : {
                "content" : { "Fn::Join" : ["", [
                  "CREATE DATABASE ", { "Ref" : "DBName" }, ";\n",
                  "GRANT ALL ON ", { "Ref" : "DBName" }, ".* TO '", { "Ref" : "DBUsername" }, "'@localhost IDENTIFIED BY '", { "Ref" : "DBPassword" }, "';\n"
                  ]]},
                "mode"  : "000644",
                "owner" : "root",
                "group" : "root"
              }
            },

            "services" : {
              "sysvinit" : {
                "mysqld" : {
                  "enabled"       : "true",
                  "ensureRunning" : "true"
                },
                "httpd" : {
                  "enabled"       : "true",
                  "ensureRunning" : "true"
                }
              }

            }
          }
        }
      },
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",
          "yum update -y aws-cfn-bootstrap\n",

          "# Install LAMP packages\n",
          "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" }, " -r WebServer ",
          "    --region ", { "Ref" : "AWS::Region" }, "\n",

          "# Setup MySQL, create a user and a database\n",
          "mysqladmin -u root password '", { "Ref" : "DBRootPassword" }, "\n",
          "mysql -u root --password='", { "Ref" : "DBRootPassword" }, "\n",

          "# Configure the PHP application - in this case, fix up the page with the right references to the database\n",
          "sed -i \"s/REPLACE_WITH_DATABASE/localhost/g\" /var/www/html/index.php\n",
          "sed -i \"s/REPLACE_WITH_DBUSER/", { "Ref" : "DBUsername" }, "/g\" /var/www/html/index.php\n",
          "sed -i \"s/REPLACE_WITH_DBPASSWORD/", { "Ref" : "DBPassword" }, "/g\" /var/www/html/index.php\n"

        ]]}}
      }
    },

    "WebServerSecurityGroup" : { ... }
  },

  "Outputs" : { ... }
}

Use AWS CloudFormation Wait Conditions

The template so far will create the stack resources and attempt to start all of the specified services. At that point, it will mark the status of the stack as CREATE_COMPLETE, even if one or more services failed to start. To prevent the status from changing to CREATE_COMPLETE until all the services have successfully started, AWS CloudFormation supports a WaitCondition resource that you can use to ensure that either all of your stack resources are created or none of them are. The WaitCondition resource pauses execution of the template until a specified condition is met or a timeout period is exceeded.

To wait for the application to be ready, we can extend the previous template to create a WaitCondition and a WaitConditionHandle and use the cfn-signal helper script to signal that the application is installed. The WaitConditionHandle resource is a signaling mechanism that notifies the WaitCondition resource of status changes that determine if deployment can continue or must be rolled back. For more information about wait conditions, see Creating Wait Conditions in a Template.

To add the WaitCondition and WaitConditionHandle resources, we’ll add the following to the template:

  • Resources > WebServer > Properties—UserData error_exit helper function

    The error_exit function calls cfn-signal with an exit code that signals an error. The UserData script invokes error_exit if cfn-init fails or if the attempts to set up MySQL fail.

  • Resources > WebServer > Properties—UserData cfn-signal on success

    If all the services are configured and started successfully, the UserData script calls cfn-signal with an exit code that signals success.

  • Resources > WaitHandle—AWS::CloudFormation::WaitConditionHandle

    The AWS::CloudFormation::WaitConditionHandle key creates a pre-signed URL that is used as the signaling mechanism.

  • Resources > WaitCondition—AWS::CloudFormation::WaitCondition

    The AWS::CloudFormation::WaitCondition key creates a special resource that waits for a specified period of time for a success signal. If the time period elapses without a success signal, the wait condition's status is set to CREATE_FAILED and the entire stack is rolled back.

The template now looks as follows. Additions to the template are shown in red italic text.

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Sample Template LAMP_Single_Instance: ...",

  "Parameters" : { ... },

  "Mappings" : { ... },

  "Resources" : {
    "WebServer": {
      "Type": "AWS::EC2::Instance",
      "Metadata" : { ...
      },
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",
          "yum update -y aws-cfn-bootstrap\n",

          "# Helper function\n",
          "function error_exit\n",
          "{\n",
          "  /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n",
          "  exit 1\n",
          "}\n",

          "# Install LAMP packages\n",
          "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" }, " -r WebServer ",
          "    --region ", { "Ref" : "AWS::Region" },
          " || error_exit 'Failed to run cfn-init'\n",

          "# Setup MySQL, create a user and a database\n",
          "mysqladmin -u root password '", { "Ref" : "DBRootPassword" },
          "' || error_exit 'Failed to initialize root password'\n",
          "mysql -u root --password='", { "Ref" : "DBRootPassword" },
          "' < /tmp/setup.mysql || error_exit 'Failed to initialize database'\n",

          "# Configure the PHP application - in this case, fix up the page with the right references to the database\n",
          "sed -i \"s/REPLACE_WITH_DATABASE/localhost/g\" /var/www/html/index.php\n",
          "sed -i \"s/REPLACE_WITH_DBUSER/", { "Ref" : "DBUsername" }, "/g\" /var/www/html/index.php\n",
          "sed -i \"s/REPLACE_WITH_DBPASSWORD/", { "Ref" : "DBPassword" }, "/g\" /var/www/html/index.php\n",

          "# All is well so signal success\n",
          "/opt/aws/bin/cfn-signal -e 0 -r \"LAMP Stack setup complete\" '", { "Ref" : "WaitHandle" }, "'\n"
        ]]}}
      }
    },

    "WaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },

    "WaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "DependsOn" : "WebServer",
      "Properties" : {
        "Handle" : {"Ref" : "WaitHandle"},
        "Timeout" : "300"
      }
    },

    "WebServerSecurityGroup" : { ...
    }
  },

  "Outputs" : {
    "WebsiteURL" : { ...
    }
  }
}

The full template is available at the following location:

https://s3.amazonaws.com/cloudformation-templates-us-east-1/LAMP_Single_Instance.template