Handel vs. CloudFormation

CloudFormation is one of the most commonly used methods for automatically deploying applications to AWS. In fact, Handel uses CloudFormation under the hood to do your deployments. This page compares using vanilla CloudFormation and the Handel library.

CloudFormation

CloudFormation is one of the most popular ways to deploy applications to AWS. It is an extremely flexible tool that allows you great control over how you wire up applications. That flexibility comes at the cost of complexity. You need to learn quite a bit before you can ever deploy your first production-quality application.

Here is an example CloudFormation template that creates a Beanstalk server and wires it up with an S3 bucket, a DynamoDB table, and an SQS queue:

AWSTemplateFormatVersion: '2010-09-09'
Description: Beanstalk application with SQS queue, S3 bucket, and DynamoDB table

Resources:
  Queue:
    Type: AWS::SQS::Queue
    Properties:
      DelaySeconds: 0
      MaximumMessageSize: 262144
      MessageRetentionPeriod: 345600
      QueueName: dsw88-testapp-dev-queue-sqs
      ReceiveMessageWaitTimeSeconds: 0
      VisibilityTimeout: 30

  Table:
    Type: "AWS::DynamoDB::Table"
    Properties:
      AttributeDefinitions:
      - AttributeName: MyPartitionKey
        AttributeType: S
      KeySchema:
      - AttributeName: MyPartitionKey
        KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      TableName: dsw88-testapp-dev-table-dynamodb

  Bucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: dsw88-testapp-dev-bucket-s3
      VersioningConfiguration:
        Status: Enabled

  BeanstalkRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Sid: ''
          Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action: sts:AssumeRole
      Path: /services/
      RoleName: dsw88-testapp-dev-webapp-beanstalk

  BeanstalkPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - s3:ListBucket
          Resource:
          - arn:aws:s3:::dsw88-testapp-dev-bucket-s3
        - Effect: Allow
          Action:
          - s3:PutObject
          - s3:GetObject
          - s3:DeleteObject
          Resource:
          - arn:aws:s3:::dsw88-testapp-dev-bucket-s3/*
        - Effect: Allow
          Action:
          - sqs:ChangeMessageVisibility
          - sqs:ChangeMessageVisibilityBatch
          - sqs:DeleteMessage
          - sqs:DeleteMessageBatch
          - sqs:GetQueueAttributes
          - sqs:GetQueueUrl
          - sqs:ListDeadLetterSourceQueues
          - sqs:ListQueues
          - sqs:PurgeQueue
          - sqs:ReceiveMessage
          - sqs:SendMessage
          - sqs:SendMessageBatch
          Resource:
          - arn:aws:sqs:us-west-2:111111111111:dsw88-testapp-dev-queue-sqs
        - Sid: DyanmoDBAccessT7eFcR52BF7VnlQF
          Effect: Allow
          Action:
          - dynamodb:BatchGetItem
          - dynamodb:BatchWriteItem
          - dynamodb:DeleteItem
          - dynamodb:DescribeLimits
          - dynamodb:DescribeReservedCapacity
          - dynamodb:DescribeReservedCapacityOfferings
          - dynamodb:DescribeStream
          - dynamodb:DescribeTable
          - dynamodb:GetItem
          - dynamodb:GetRecords
          - dynamodb:GetShardIterator
          - dynamodb:ListStreams
          - dynamodb:PutItem
          - dynamodb:Query
          - dynamodb:Scan
          - dynamodb:UpdateItem
          Resource:
          - arn:aws:dynamodb:us-west-2:111111111111:table/dsw88-testapp-dev-table-dynamodb
        - Sid: BucketAccess
          Action:
          - s3:Get*
          - s3:List*
          - s3:PutObject
          Effect: Allow
          Resource:
          - arn:aws:s3:::elasticbeanstalk-*
          - arn:aws:s3:::elasticbeanstalk-*/*
        - Sid: XRayAccess
          Action:
          - xray:PutTraceSegments
          - xray:PutTelemetryRecords
          Effect: Allow
          Resource: "*"
        - Sid: CloudWatchLogsAccess
          Action:
          - logs:PutLogEvents
          - logs:CreateLogStream
          Effect: Allow
          Resource:
          - arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk*
        - Sid: ECSAccess
          Effect: Allow
          Action:
          - ecs:Poll
          - ecs:StartTask
          - ecs:StopTask
          - ecs:DiscoverPollEndpoint
          - ecs:StartTelemetrySession
          - ecs:RegisterContainerInstance
          - ecs:DeregisterContainerInstance
          - ecs:DescribeContainerInstances
          - ecs:Submit*
          - ecs:DescribeTasks
          Resource: "*"
      PolicyName: dsw88-testapp-dev-webapp-beanstalk
      Roles:
      - !Ref BeanstalkRole

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/services/"
      Roles:
      - !Ref BeanstalkRole

  BeanstalkSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: dsw88-testapp-dev-webapp-beanstalk
      VpcId: vpc-aaaaaaaa
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        SourceSecurityGroupId: sg-44444444
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: '0'
        ToPort: '65335'
        CidrIp: 0.0.0.0/0
      Tags:
      - Key: Name
        Value: dsw88-testapp-dev-webapp-beanstalk

  BeanstalkIngressToSelf:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId:
        Ref: BeanstalkSecurityGroup
      IpProtocol: tcp
      FromPort: '0'
      ToPort: '65335'
      SourceSecurityGroupId:
        Ref: BeanstalkSecurityGroup

  Application:
    Type: AWS::ElasticBeanstalk::Application
    Properties:
      ApplicationName: dsw88-testapp-dev-webapp-beanstalk
      Description: Application for dsw88-testapp-dev-webapp-beanstalk

  ApplicationVersion:
    Type: AWS::ElasticBeanstalk::ApplicationVersion
    Properties:
      ApplicationName: !Ref Application
      Description: Application version for dsw88-testapp-dev-webapp-beanstalk
      SourceBundle:
        S3Bucket: beanstalk-us-west-2-111111111111
        S3Key: dsw88-testapp/dev/webapp/beanstalk-deployable-SOME_GUID.zip

  ConfigurationTemplate:
    DependsOn:
    - Queue
    - Table
    - Bucket
    - BeanstalkSecurityGroup
    - InstanceProfile
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref Application
      Description: Configuration template for dsw88-testapp-dev-webapp-beanstalk
      OptionSettings:
      - Namespace: aws:autoscaling:launchconfiguration
        OptionName: IamInstanceProfile
        Value: !Ref InstanceProfile
      - Namespace: aws:autoscaling:asg
        OptionName: MinSize
        Value: 1
      - Namespace: aws:autoscaling:asg
        OptionName: MaxSize
        Value: 1
      - Namespace: aws:autoscaling:launchconfiguration
        OptionName: InstanceType
        Value: t2.micro
      - Namespace: aws:autoscaling:launchconfiguration
        OptionName: SecurityGroups
        Value: !Ref BeanstalkSecurityGroup
      - Namespace: aws:autoscaling:updatepolicy:rollingupdate
        OptionName: RollingUpdateEnabled
        Value: true
      - Namespace: aws:ec2:vpc
        OptionName: VPCId
        Value: vpc-aaaaaaaa
      - Namespace: aws:ec2:vpc
        OptionName: Subnets
        Value: subnet-ffffffff,subnet-77777777
      - Namespace: aws:ec2:vpc
        OptionName: ELBSubnets
        Value: subnet-22222222,subnet-66666666
      - Namespace: aws:ec2:vpc
        OptionName: DBSubnets
        Value: subnet-eeeeeeee,subnet-cccccccc
      - Namespace: aws:ec2:vpc
        OptionName: AssociatePublicIpAddress
        Value: false
      - Namespace: aws:elasticbeanstalk:application:environment
        OptionName: MY_INJECTED_VAR
        Value: myValue
      SolutionStackName: 64bit Amazon Linux 2016.09 v4.0.1 running Node.js

  Environment:
    Type: "AWS::ElasticBeanstalk::Environment"
    Properties:
      ApplicationName: !Ref Application
      Description: environment for dsw88-testapp-dev-webapp-beanstalk
      TemplateName: !Ref ConfigurationTemplate
      VersionLabel: !Ref ApplicationVersion
      Tags:
      - Key: Name
        Value: dsw88-testapp-dev-webapp-beanstalk

Outputs:
  BucketName:
    Description: The endpoint URL of the beanstalk environment
    Value:
      Fn::GetAtt:
        - Environment
        - EndpointURL

Handel

Handel is a deployment library that runs on top of CloudFormation. The services you specify in Handel are turned into CloudFormation templates that are created on your behalf.

Because of this approach, Handel frees you from having to worry about the detail of CloudFormation, as well as security services such as IAM and VPC. This simplicity comes at the cost of lack of flexibility in some cases. For example, when wiring up permissions between a Beanstalk app and an S3 bucket, you don’t get to choose what permissions exactly will be applied. Handel will apply what it considers to be reasonable and secure permissions.

Here is an example Handel file that creates the same set of resources (Beanstalk, S3, DynamoDB, and SQS) as the CloudFormation template above:

version: 1

name: dsw88-testapp

environments:
  dev:
    webapp:
      type: beanstalk
      path_to_code: .
      solution_stack: 64bit Amazon Linux 2016.09 v4.0.1 running Node.js
      instance_type: t2.micro
      health_check_url: /
      min_instances: 1
      max_instances: 1
      environment_variables:
        MY_INJECTED_VAR: myValue
      dependencies:
      - bucket
      - queue
      - table
    bucket:
      type: s3
    queue:
      type: sqs
    table:
      type: dynamodb
      partition_key:
        name: MyPartionKey
        type: String
      provisioned_throughput:
        read_capcity_units: 1
        write_capacity_units: 1

Note the greatly reduced file size, as well as the lack of any IAM or VPC configuration details.