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.
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).
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>
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.
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).
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"
}