Skip to content

Instantly share code, notes, and snippets.

@ret
Last active January 19, 2019 17:56
Show Gist options
  • Save ret/7aec03eb96a18dedee58 to your computer and use it in GitHub Desktop.
Save ret/7aec03eb96a18dedee58 to your computer and use it in GitHub Desktop.

How to combine AWS Elastic Beanstalk, Docker and AWS CloudFormation

Introduction

This text explains how to create and deploy a Docker-based AWS Elastic Beanstalk site and how to control it using CloudFormation.

Please find the CloudFormation template at the end of this Gist.

We assume that you are familiar with AWS ElasticBeanstalk, AWS CloudFormation and Docker.

Preparation: specify the Docker image and container port

a) Create the following example Dockerrun.aws.json file and replace user, image, tag and the 8080 port to match your Dockerfile or Docker image:

{
  "Image": { "Name": "user/image:tag" },
  "Ports": [ { "ContainerPort": "8080" } ],
  "Logging": "/var/log",
  "AWSEBDockerrunVersion": "1"
}

b) Wrap in zip file

Create a zip file containing the Dockerrun.aws.json file, like so:

$ zip app.zip Dockerrun.aws.json

You can use any name for the zip file, but must make sure to maintain the .zip extension.

c) Upload the zip file to a S3 bucket

The template will refer to the bucket and S3 object name you use in this step (both are template parameters).

Create the Elastic Beanstalk site using a CloudFormation template:

a) Visit the AWS CloudFormation Management Console and create a new stack using the template below. Fill in the parameters to match the S3 bucket and object name you uploaded the app.zip file to and enter your domain name and the respective SSL certificate ARN (this template creates a SSL only site).

b) That's it - visit your secure site URL at: https://<site>.<domainname>

Redeploy the Docker image

a) Tag your new image using your favorite Docker command or tool

b) Update the Dockerrun.aws.json file with the new image tag, zip and upload to S3 again

c) Tweak the template.json file by updating the 'MySite-0.0.1' Description string of the AppVersion entry.

d) Visit the AWS CloudFormation Management Console and update the stack to the new template, containing the changed Description of the AppVersion entry. CloudFormation will detect the template changed and trigger a new application deployment to the Elastic Beanstalk Environment.

Continuous integration, and a little trivial scripting ...

For continuous integration, I often automate these simple steps with a few scripts that help me zip the Dockerrun.aws.json file and name the zip file based on the md5 hash (as a fingerprint) of the Dockerrun.aws.json file, like so:

fp=$(md5 -q Dockerrun.aws.json)
zip app-${fp}.zip Dockerrun.aws.json

The filename will now change as the content of the Dockerrun.aws.json file changes when I update to a new image tag. This same fingerprint makes for a unique S3 object name and I use it for the AppVersion Description in the template itself as well (best to make that a parameter of the template).

Delete the application and site

Don't forget to delete your CloudFormation stack when you no longer need your site. This will delete the Beanstalk application and environment as well.

#

{
  "Description": "CloudFormation Beanstalk Docker Example (with ELB, WARNING: full permission role)",

  "Parameters": {
    "KeyName": {
      "Description": "Name of an existing EC2 key pair to enable SSH access to the instances",
      "Type": "String"
    },
    "InstanceType": {
      "Description": "WebServer EC2 instance type",
      "Type": "String",
      "Default": "t1.micro"
    },
    "AppZipBucket": {
      "Description": "S3 Bucket with zip file of Dockerrun.aws.json",
      "Type": "String"
    },
    "AppZipObject": {
      "Description": "S3 Object containing zip file with Dockerrun.aws.json)",
      "Type": "String",
      "Default": "app.zip"
    }
  },

  "Outputs": {
    "SiteURL": {
      "Value": { "Fn::GetAtt" : [ "EBEnv", "EndpointURL" ] },
      "Description": "Site URL of Elastic Beanstalk Docker Application"
    }
  },

  "Resources": {

    "OnInstanceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Path": "/",
        "AssumeRolePolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [ {
            "Effect": "Allow",
            "Principal": {
              "Service": [ "ec2.amazonaws.com" ]
            },
            "Action": [ "sts:AssumeRole" ]
          } ]
        },
        "Policies": [ {
          "PolicyName": "full-access",
          "PolicyDocument": {
            "Version" : "2012-10-17",
            "Statement": [ {
              "Effect": "Allow",
              "Action": "*",
              "Resource": "*"
            } ]
          }
        } ]
      }
    },
    "InstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [ {
          "Ref": "OnInstanceRole"
        } ]
      }
    },

    "App": {
      "Type": "AWS::ElasticBeanstalk::Application",
      "Properties": {
        "Description": ""
      }
    },
    "AppVersion": {
      "Type": "AWS::ElasticBeanstalk::ApplicationVersion",
      "Properties": {
        "Description": "MySite-0.0.1",
        "ApplicationName": { "Ref": "App" },
        "SourceBundle": {
          "S3Bucket": { "Ref" : "AppZipBucket" },
          "S3Key": { "Ref" : "AppZipObject" }
        }
      }
    },

    "AppConfigurationTemplate": {
      "Type": "AWS::ElasticBeanstalk::ConfigurationTemplate",
      "Properties": {
        "SolutionStackName": "64bit Amazon Linux 2014.03 v1.0.1 running Docker 1.0.0",
        "ApplicationName": { "Ref": "App" },
        "Description": "",
        "OptionSettings": [
          {
            "Namespace": "aws:autoscaling:asg",
            "OptionName": "MinSize",
            "Value": "1"
          },
          {
            "Namespace": "aws:autoscaling:asg",
            "OptionName": "MaxSize",
            "Value": "1"
          },
          {
            "Namespace": "aws:autoscaling:launchconfiguration",
            "OptionName": "InstanceType",
            "Value": { "Ref": "InstanceType" }
          },
          {
            "Namespace": "aws:autoscaling:launchconfiguration",
            "OptionName": "EC2KeyName",
            "Value": { "Ref": "KeyName" }
          },
          {
            "Namespace": "aws:autoscaling:launchconfiguration",
            "OptionName": "IamInstanceProfile",
            "Value": { "Ref": "InstanceProfile" }
          },
          {
            "Namespace": "aws:elasticbeanstalk:environment",
            "OptionName": "EnvironmentType",
            "Value": "LoadBalanced"
          },
          {
            "Namespace": "aws:elb:loadbalancer",
            "OptionName": "LoadBalancerHTTPPort",
            "Value": "80"
          },
          {
            "Namespace": "aws:elasticbeanstalk:application:environment",
            "OptionName": "YOUR_ENV_VARIABLE_HERE",
            "Value": "somevalue"
          },
          {
            "Namespace": "aws:elasticbeanstalk:application:environment",
            "OptionName": "DATABASE_URL",
            "Value": "postgresql://etcetc"
          }
        ]
      }
    },
    "EBEnv": {
      "Type": "AWS::ElasticBeanstalk::Environment",
      "Properties": {
        "ApplicationName": { "Ref": "App" },
        "Description": "",
        "TemplateName": { "Ref": "AppConfigurationTemplate" },
        "VersionLabel": { "Ref": "AppVersion" }
      }
    }
  },

  "AWSTemplateFormatVersion": "2010-09-09"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment