Docker basics: Docker compose

Docker Compose is a tool that allows you to run multi-container applications. With compose we can use yaml files to configure our application’ services and then using a single command create and start all of the configured services. I use this tool a lot when it comes to local development in a microservice environment. It is also lightweight and needs just a small effort. Instead of managing how to run each service while developing you can have the environment and services needed preconfigured and focus on the service that you currently develop.

With docker compose we can configure a network for our services, volumes, mount-points, environmental variables just about everything.
To showcase this we are going to solve a problem. Our goal would be to extract data from mongodb using grafana. Grafana does not have out of the box support for MongoDB therefore we will have to use a plugin.

First step we shall create our networks. Creating a network is not necessary since your services once started will join the default network. We shall make a showcase of using custom networks. We shall have a network for backend services and a network for frontend services. Apparently network configuration can get more advanced and specify custom network drivers or even configure static addresses.

version: '3.5'

networks:
  frontend:
    name: frontend-network
  backend:
    name: backend-network
    internal: true

The backend network is going to be internal therefore there won’t be any outbound connectivity to the containers attached to it.

Then we shall setup our mongodb instance.

version: '3.5'

services:
  mongo:
    image: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
    volumes:
      - ${DB_PATH}:/data/db
    networks:
      - backend

As you see we specified a volume. Volumes can also be specified separately and attach them to a service.
Also we used environmental variables for the root account, you might as well have spotted that the password is going to be provided through environmental variables ie. MONGO_USER=root MONGO_PASSWORD=root docker-compose -f stack.yaml up. The same applies for the volume path too. You can have a more advanced configuration for volumes in your compose configuration and reference them from your service.

Our next goal is to setup the proxy server which shall be in the middle of our grafana and mongodb server. Since it needs a custom Dockerfile to create it, we shall do it through docker-compose. Compose has the capability to spin up a service by specifying the docker file.

So let’s start with the Dockerfile.

FROM node

WORKDIR /usr/src/mongografanaproxy

COPY . /usr/src/mongografanaproxy

EXPOSE 3333

RUN cd /usr/src/mongografanaproxy
RUN npm install
ENTRYPOINT ["npm","run","server"]

Then let’s add it to compose

version: '3.5'

services:
  mongo-proxy:
    build:
      context: .
      dockerfile: ProxyDockerfile
    restart: always
    networks:
      - backend

And the same shall be done to the Grafana image that we want to use. In stead of using a ready grafana image we shall create one with the plugin preinstalled.

FROM grafana/grafana

COPY . /var/lib/grafana/plugins/mongodb-grafana

EXPOSE 3000

version: '3.5'

services:
  grafana:
    build:
      context: .
      dockerfile: GrafanaDockerfile
    restart: always
    ports:
      - 3000:3000
    networks:
      - backend
      - frontend

Let’s wrap them all together

version: '3.5'

services:
  mongo:
    image: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
    volumes:
      - ${DB_PATH}:/data/db
    networks:
      - backend
  mongo-proxy:
    build:
      context: .
      dockerfile: ProxyDockerfile
    restart: always
    networks:
      - backend
  grafana:
    build:
      context: .
      dockerfile: GrafanaDockerfile
    restart: always
    ports:
      - 3000:3000
    networks:
      - backend
      - frontend
networks:
  frontend:
    name: frontend-network
  backend:
    name: backend-network
    internal: true

So let’s run them all together.

docker-compose -f stack.yaml build
MONGO_USER=root MONGO_PASSWORD=root DB_PATH=~/grafana-mongo  docker-compose -f stack.yaml up

The above can be found on github.

You might as well find the Docker Images, Docker Containers and Docker registry posts useful.

Kubernetes and Secrets

This is going to be a small post since it has to deal with kubernetes and secrets. Yet it is a very useful once since adding secrets is so common yet so easy to forget (guilty as charged).

So we will cover username and password, key/values, file uploading, secrets.

Upload username and password using command line.

kubectl create secret generic accountpassword --from-literal=username=yourusername --from-literal=password=yourpassword

Upload just a key

kubectl create secret generic application-key --from-literal=key=yourusername

Upload username and password through files

printf "yourusername" > username.txt
printf "yourpassword" > password.txt
kubectl create secret generic accountpassword --from-file=./username.txt --from-file=./password.txt

Then let’s upload a secret. Be aware that this secret can be used with your secret rules.

kubectl create secret tls your-server-tls --key ./privkey.pem --cert ./fullchain.pem

Another step is to upload a file. This file can then be used by being mounted on your container.

kubectl create secret generic secretfile --from-file=key.json=./secret_json.yaml

Then you can mount it to the pod

    spec:
      volumes:
      - name: secret-json
        secret:
          secretName: secretfile
      containers:
      - name: containername
        volumeMounts:
        - name: secret-json
          mountPath: /var/secrets/json

That’s all! The full docs can be found here.

Secure a docker registry using ssl

As mentioned on a previous article having a registry with a username and password is not secure if the registry is not ssl configured.

DockerImage

So we are going to add the ssl certificates to our registry. To make things easier we will use let’s encrypt which is free.

Once we have generated the credentials we have to add them to the registry. We will create a directory called certificates which will contain the certificate pem file and the key pem file. Then we will move the generated certificates on the certificates directory with the names crt.pem and key.crt.

We will follow exactly the same steps we followed in the previous article to generate the password.

docker run --entrypoint htpasswd registry:2 -Bbn {your-user} {your-password} > auth/password-file

Now we are ready to create our registry by also specifying the certificates. To do so we will mount the certificates directory to our docker container. The we will specify where the registry is going to find the credentials on the containers filesystem

docker run -d -p 5000:5000 --restart=always --name registry -v `pwd`/auth:/auth -v `pwd`/certificates:/certificates -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/password-file -e REGISTRY_HTTP_TLS_CERTIFICATE=/certificates/crt.pem -e REGISTRY_HTTP_TLS_KEY=/certificates/key.pem registry:2

So your registry will pickup the credentials specified and will also use the certificates created.
Next step is to do the dns mapping and add a dns entry which directs your subdomain to your registry’s ip.

However if you just wan’t to test it, you can run your registry locally and just change your /etc/hosts and add this entry.

127.0.0.1 registry.{your certificate's domain }

Once you navigate through your browser to https://registry.{your certificate’s domain }:5000
you will get a 200 status code and your browser will identify your connection as secure.

Docker basics: Docker Registry

By default when using docker you pull the images from the Dockerhub docker registry. Most probably you have your own docker images for you application and you want to distribute them and do so in a secure way. One way to do so is to go with the already set options such as a paid plan from Dockerhub or the registries provided by cloud providers like amazon, azure etc.

The other option is setting up your own docker registry.
In any case since you use docker you need to have a registry to distribute your images so that they can make it into production.
There are many benefits on managing your own registry but be aware that it requires effort on your side on provisioning and maintaining it.
Therefore we will create our docker registry

docker run -d -p 5000:5000 --restart=always --name registry registry:2

So we have a docker registry running on port 5000 and the registry will always restart.

Now let’s test our registry and push an image.
First I will build a simple image with no specific purpose.

FROM ubuntu
ENTRYPOINT top

It is just a dummy image printing top.

so we are gonna build it

docker build --tag top-ubuntu:1.0 .

The key is to tag your image based on the domain under which your registry runs.
Currently our registry runs on the localhost therefore by tagging we also specify the location of the registry.

docker tag top-ubuntu:1.0 localhost:5000/top-ubuntu:1.0

And no we push our image

docker push localhost:5000/top-ubuntu:1.0

Now let’s remove our images and see if our image will be downloaded from our running registry

docker rmi top-ubuntu:1.0
docker rmi localhost:5000/top-ubuntu:1.0

And let’s pull

docker pull localhost:5000/top-ubuntu:1.0

As you can see our image has been downloaded from our local registry and is ready to be used.

So far so good. The next step is securing our registry with a username and password.

Let’s start by setting the username and password

First let’s create a directory which shall contain our credentials

mkdir auth

The we shall creae

docker run --entrypoint htpasswd registry:2 -Bbn {your-user} {your-password} > auth/password-file

The file shall contain your username and password information. The password shall be hashed.

Now let’s run our secured registry

 docker run -d -p 5000:5000 --restart=always --name registry -v `pwd`/auth:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/password-file registry:2

As you can see we mounted the credentials file to the docker container and we specified the location of the password-file.

Let’s try to push our image

docker push localhost:5000/top-ubuntu:1.0
.
.
.

059ad60bcacf: Preparing 
8db5f072feec: Preparing 
67885e448177: Preparing 
ec75999a0cb1: Preparing 
65bdd50ee76a: Preparing 
no basic auth credentials

It’s time to login to our registry

docker login localhost:5000

Once your have provided your credentials you will be able to push the image to your local repository.

docker push localhost:5000/top-ubuntu:1.0

Be aware that our registry is not secure. Having your registry secured with credentials does not make it secure since you need to have ssl encryption.

On the next tutorial we will secure a docker registry with ssl.

 

Docker basics: Containers

So we have just created a docker image and now it’s time for us to run a container and interact with it.

docker run nginx

This will run an nginx image and as we can see due to running the container in an interactive mode the terminal we issued the command is no longer available.

We will press ctrl-c. By doing so the docker process exists and our container is no longer running. Our container lifecycle is bound to the main process. If the process exits the container stops.

So let’s run our container in the detached mode and interact with it

docker run -d nginx 

The minus -d option makes the container to run in a detached mode.

Now let’s have a look on our running containers

docker ps

Only one container is listed. The ps command by default show only the running containers. The ps command gives us various information such as when the container was created, the status of the container and the image which was created.
If we want to see our previously stopped container all we have to do is to execute ps with the -a option

docker ps -a

We can remove a container either after stopping the container or we can specify the container to get removed automatically once the container is stopped.

docker run --rm -d nginx 

Otherwise in order to remove a container we just have to issue the rm command

docker rm {container-id|container-name}

Be aware that in order to delete a container, the container must be stopped. Otherwise you must use the -f argument which forces the container to stop and gets deleted.

In order to stop the container issue

docker stop {container-id|container-name}

As we can see we can remove a container by specifying it’s name. In the previous examples we did not any names so let’s do it now.

We can set a name by either renaming a running container

docker rename 53552a9f0a95 my-nginx

Or we can specify the name of the container while we create it.

docker run --name='my-nginx' -d nginx 

Now that we have only one container the ps command serves us well, however in cases where we run multiple containers we need to apply the filter argument which comes along with the ps command.

You can filter by the id, name, label, status etc.
Also you can use filter with substrings in case of name.

docker ps --filter=name="nginx"

So everything is set for us. As we have seen running a container in a detached state does not display any logs in the console. To get the logs we can use the logs command.

docker logs my-nginx

If we want to follow the logs we can also follow the output

docker logs -f my-nginx

The next step is to get into interactive mode with our container while it is running

docker run --rm --name='my-nginx' -it nginx /bin/bash

with this command our container is running and we are logged in to the containers bash console. Be aware that in cases where an entrypoint has been specified you need to override it.

docker run -it --entrypoint "/bin/bash" {image}

However still we might need some extra commands to be run. One way to do this is to use
the docker exec command

docker exec my-nginx echo 'hello wolrd'

As you see we specified the container name and the bash command to execute.

So most of the thing to get us covered has been mentioned. The last tip has to do with exposing the port of our docker container.
Our container is accessible through the ip it has been assigned while creating it however there are cases where we want to map the container’s listening port to our host.

To do so we will use the -p command

docker run -d -p 8080:80 --name my-nginx nginx 

In the above example 8080 is the port of our host and 80 is the port of our docker container.

Last but not least you can create an image by a running container by committing it

docker commit e2e222e22e2e dockerimage:version1

So that was a quick reference on the docker commands which you might use on a daily basis.

Docker basics: Images

So what is a docker image? A docker image is read-only layer consisting of binaries libraries and the software which the container shall execute.

A docker image can have as a base another docker image. Actually they are a read only template.
Once created they are stored in your file system.

To view the docker images currently installed on your system you can issue

docker images

Let’s download an image

docker pull ubuntu

By default your images are downloaded through DockerHub. Also you can upload your images to DockerHub too, if you want to share them.
In cases you want to share your images privately then you have to check the private option on image hosting on dockerhub or other providers such as Google, Amazon, Azure etc.

Since we have just downloaded an image, we will list again our images. Now we will go a bit further and issue

docker images -a

What you can see is that some images don’t have any name on them. Those images are called intermediate images. Behind the scenes the image that you use consists of many other images. Actually it is a parent-child relation. An intermediate image might be contained in two different images due to common dependencies and binaries.
So next time you download an image the intermediate files and your image will be downloaded.

Now let’s filter some of our images.
Current filters supported are
dangling, label, before, since, reference.

Most probably the reference is the one you are gonna use the most.

docker images --filter=reference='*ubuntu*'

What you actually do with the reference filter is filtering based on the image reference and the pattern you specified.

Now let’s build an image. To build an image you need to create a Dockerfile. The dockerfile will specify the image which you will inherit from and any extra action you need to do.

Here’s the content of the Dockerfile

FROM ubuntu
ARG username
ENV USERNAME $username
ENTRYPOINT echo $USERNAME

An argument is passed and the default command that will be executed when the container is running would be to echo the USERNAME environmental variable which is set based on the argument.

So let’s build the image

docker build --build-arg username=john .

As you can see you have your intermediate images printed and the segment images. Try building it again and the same steps and ids will fill your screen, changes and new intermediates will happen only if you change your Dockerfile.

Next step is to check all our images.

docker images

Seems like our image is different that the others. The others have name and tags whilst ours has the autogenerated image id.

Let’s add one

docker tag d88ef0502ecf ubuntu-hello:1.0

Also we can do the tagging while we build the image

docker build --build-arg username=john --tag ubuntu-hello:1.0 .

As you can see I have put a version on the image. For your application most probably you are going to have more than one images created therefore you can tag your images in order to keep track of the versions.

Now time to clean up.

docker rmi ubuntu-hello:1.0

This one will not be successful if you have already run the image. You can force it’s deletion by using the -f argument but is not graceful so don’t do it. Instead delete the containers created from this image and then delete the images.

Also be aware that when you issue rmi using tags, if your image has been tagged with more multiple tags then your tags get removed from the image until the image remains with one tag. If your image has only one tag left then the image gets removed as well.

Docker basics: An introduction

On this blog docker is mentioned many times, and many tutorials utilize docker. It is always good to have a go on docker basics and provide a fast reference, in our workday the more we get absorbed with certain problems the easier it gets to forget.

So with docker you can achieve containerization. You can achieve a special runtime for your process to get executed isolated without affecting any other resources of your system.

Instead of spawning a new virtual machine for your applications and consume extra resources, which a vm needs to simulate a machine and the operating system, you can use docker.

So here are some of the benefits on using docker.

  • Isolation
  • Portability
  • Safety

Isolation

If your application needs some certain binaries installed on your os, instead of installing them to your os you can have them installed on your docker image. This way you application does not affect your host system installation.

Portability

You application and it’s dependencies are stored in the form of an image. This image can be shipped, distributed and run on any docker installation without the need of taking any extra action such as installing dependencies.

Safety

The software that comes packaged with your image is used by the container only. It is not installed or used by your os. If there is anything untrusted on that It will be run only by your containers process. Your container is isolated, it cannot interact with other parts of your system.

The main parts which we will discuss are the

In the meantime the best way to start is just by running a hello-world application on docker.

 docker run hello-world 

The best thing with the above image is that we have a step by step documentation on the process of running a docker image.

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

Push Spring Boot Docker images on ECR

On a previous blog we integrated a spring boot application with EC2.
It is one of the most raw forms of deployment that you can have on Amazon Web Services.

On this tutorial we will create a docker image with our application which will be stored to the Amazon EC2 container registry.

You need to have the aws cli tool installed.

We will get as simple as we can with our spring application therefore we will use an example from the official spring source page. The only changes applied will be on the packaging and the application name.

Our application shall be named ecs-deployment

rootProject.name = 'ecs-deployment'

Then we build and run our application

gradle build
gradle bootRun

Now let’s dockerize our application.
First we shall create a Dockerfile that will reside on src/main/docker.

FROM frolvlad/alpine-oraclejdk8
VOLUME /tmp
ADD ecs-deployment-1.0-SNAPSHOT.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]

Then we should edit our gradle file in order to add the docker dependency, the docker plugin and an extra gradle task in order to create our docker image.

buildscript {
    ...
    dependencies {
        ...
        classpath('se.transmode.gradle:gradle-docker:1.2')
    }
}

...
apply plugin: 'docker'


task buildDocker(type: Docker, dependsOn: build) {
    push = false
    applicationName = jar.baseName
    dockerfile = file('src/main/docker/Dockerfile')
}

And we are ready to build our docker image.

./gradlew build buildDocker

You can also run your docker application from the newly created image.

docker run -p 8080:8080 -t com.gkatzioura.deployment/ecs-deployment:1.0-SNAPSHOT

First step is too create our ecr repository

aws ecr create-repository  --repository-name ecs-deployment

Then let us proceed with our docker registry authentication.

aws ecr get-login

Then run the command given in the output. The login attempt will succeed and your are ready to proceed to push your image.

First tag the image in order to specify the repository that we previously created and then do a docker push.

docker tag {imageid} {aws account id}.dkr.ecr.{aws region}.amazonaws.com/ecs-deployment:1.0-SNAPSHOT
docker push {aws account id}.dkr.ecr.{aws region}.amazonaws.com/ecs-deployment:1.0-SNAPSHOT

And we are done! Our spring boot docker image is deployed on the Amazon EC2 container registry.

You can find the source code on github.

Integrate Spring Boot and EC2 using Cloudformation

On a previous blog we integrated a spring boot application with elastic beanstalk.
The application was a servlet based application responding to requests.

On this tutorial we are going to deploy a spring boot application, which executes some scheduled tasks on an ec2 instance.
The application will be pretty much the same application taken from the official spring guide with some minor differences on packages.

The name of our application will be ec2-deployment

rootProject.name = 'ec2-deployment'

Then we will schedule a task to our spring boot application.

package com.gkatzioura.deployment.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * Created by gkatzioura on 12/16/16.
 */
@Component
public class SimpleTask {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTask.class);

    @Scheduled(fixedRate = 5000)
    public void reportCurrentTime() {
        LOGGER.info("This is a simple task on ec2");
    }

}


Next step is to build the application and deploy it to our s3 bucket.

gradle build
aws s3 cp build/libs/ec2-deployment-1.0-SNAPSHOT.jar s3://{your bucket name}/ec2-deployment-1.0-SNAPSHOT.jar 

What comes next is a bootstrapping script in order to run our application once the server is up and running.

#!/usr/bin/env bash
aws s3 cp s3://{bucket with code}/ec2-deployment-1.0-SNAPSHOT.jar /home/ec2-user/ec2-deployment-1.0-SNAPSHOT.jar
sudo yum -y install java-1.8.0
sudo yum -y remove java-1.7.0-openjdk
cd /home/ec2-user/
sudo nohup java -jar ec2-deployment-1.0-SNAPSHOT.jar > ec2dep.log

This script is pretty much self explanatory. We download the application from the bucket we uploaded it previously, we install the java version needed and then we run the application (this script serves us for example purposes, there are certainly many ways to set up you java application running on linux).

Next step would be to proceed to our cloudformation script. Since we will download our application from s3 it is essential to have an IAM policy that will allow us to download items from the s3 bucket we used previously. Therefore we will create a role with the policy needed

"RootRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [ {
            "Effect": "Allow",
            "Principal": {
              "Service": [ "ec2.amazonaws.com" ]
            },
            "Action": [ "sts:AssumeRole" ]
          } ]
        },
        "Path": "/",
        "Policies": [ {
          "PolicyName": "root",
          "PolicyDocument": {
            "Version" : "2012-10-17",
            "Statement": [ {
              "Effect": "Allow",
              "Action": [
                "s3:Get*",
                "s3:List*"
              ],
              "Resource": {"Fn::Join" : [ "", [ "arn:aws:s3:::", {"Ref":"SourceCodeBucket"},"/*"] ] }
            } ]
          }
        } ]
      }
    }

Next step is to encode our bootstrapping script to Base64 in order to be able to pass it as user data.
Once the ec2 instance is up and running it will run the shell commands previously specified.

Last step is to create our instance profile and specify the ec2 instance to be launched

    "RootInstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [ {
          "Ref": "RootRole"
        } ]
      }
    },
    "Ec2Instance":{
      "Type":"AWS::EC2::Instance",
      "Properties":{
        "ImageId":"ami-9398d3e0",
        "InstanceType":"t2.nano",
        "KeyName":"TestKey",
        "IamInstanceProfile": {"Ref":"RootInstanceProfile"},
"UserData":"IyEvdXNyL2Jpbi9lbnYgYmFzaA0KYXdzIHMzIGNwIHMzOi8ve2J1Y2tldCB3aXRoIGNvZGV9L2VjMi1kZXBsb3ltZW50LTEuMC1TTkFQU0hPVC5qYXIgL2hvbWUvZWMyLXVzZXIvZWMyLWRlcGxveW1lbnQtMS4wLVNOQVBTSE9ULmphcg0Kc3VkbyB5dW0gLXkgaW5zdGFsbCBqYXZhLTEuOC4wDQpzdWRvIHl1bSAteSByZW1vdmUgamF2YS0xLjcuMC1vcGVuamRrDQpjZCAvaG9tZS9lYzItdXNlci8NCnN1ZG8gbm9odXAgamF2YSAtamFyIGVjMi1kZXBsb3ltZW50LTEuMC1TTkFQU0hPVC5qYXIgPiBlYzJkZXAubG9n"
      }
    }

KeyName stands for the ssh key name, in case you want to login to the ec2 instance.

So we are good to go and create our cloudformation stack. You have to add the CAPABILITY_IAM flag.

aws s3 cp ec2spring.template s3://{bucket with templates}/ec2spring.template
aws cloudformation create-stack --stack-name SpringEc2 --parameters ParameterKey=SourceCodeBucket,ParameterValue={bucket with code} --template-url https://s3.amazonaws.com/{bucket with templates}/ec2spring.template --capabilities CAPABILITY_IAM

That’s it. Now you have your spring application up and running on top of an ec2 instance.
You can download the source code from GitHub.

Integrate Spring boot and Elastic Beanstalk using Cloudformation

AWS beanstalk is an amazon web service that does most of the configuration for you and creates an infrastructure suitable for a horizontally scalable application. Instead of Beanstalk the other approach would be to configure load balancers and auto scalling groups, which requires a bit of AWS expertise and time.

On this tutorial we are going to upload a spring boot jar application using amazon elastic beanstalk and a cloud formation bundle.

Less is more therefore we are going to use pretty much the same spring boot application taken from the official Spring guide as a template.

The only change would be to alter the rootProject.name to beanstalk-deployment and some changes on the package structure. Downloading the project from github is sufficient.

Then we can build and run the project

gradlew build
java -jar build/libs/beanstalk-deployment-1.0-SNAPSHOT.jar 

Next step is to upload the application to s3.

aws s3 cp build/libs/beanstalk-deployment-1.0-SNAPSHOT.jar s3://{you bucket name}/beanstalk-deployment-1.0-SNAPSHOT.jar

You need to install the elastic beanstalk client since it helps a lot with most beanstalk operations.

Since we will use Java 8 I would get a list with elastic beanstalk environments in order to retrieve the correct SolutionStackName.

aws elasticbeanstalk list-available-solution-stacks |grep Java 

Based on the results I will use the “64bit Amazon Linux 2016.09 v2.3.0 running Java 8” stackname.

Now we are ready to proceed to our cloudformation script.

We will specify a parameter and this will be the bucket containing the application code

  "Parameters" : {
    "SourceCodeBucket" : {
      "Type" : "String"
    }
  }

Then we will specify the name of the application

    "SpringBootApplication": {
      "Type": "AWS::ElasticBeanstalk::Application",
      "Properties": {
        "Description":"Spring boot and elastic beanstalk"
      }
    }

Next step will be to specify the application version

    "SpringBootApplicationVersion": {
      "Type": "AWS::ElasticBeanstalk::ApplicationVersion",
      "Properties": {
        "ApplicationName":{"Ref":"SpringBootApplication"},
        "SourceBundle": {
                  "S3Bucket": {"Ref":"SourceCodeBucket"},
                  "S3Key": "beanstalk-deployment-1.0-SNAPSHOT.jar"
        }
      }
    }

And then we specify our configuration template.

    "SpringBootBeanStalkConfigurationTemplate": {
      "Type": "AWS::ElasticBeanstalk::ConfigurationTemplate",
      "Properties": {
        "ApplicationName": {"Ref":"SpringBootApplication"},
        "Description":"A display of speed boot application",
        "OptionSettings": [
          {
            "Namespace": "aws:autoscaling:asg",
            "OptionName": "MinSize",
            "Value": "2"
          },
          {
            "Namespace": "aws:autoscaling:asg",
            "OptionName": "MaxSize",
            "Value": "2"
          },
          {
            "Namespace": "aws:elasticbeanstalk:environment",
            "OptionName": "EnvironmentType",
            "Value": "LoadBalanced"
          }
        ],
        "SolutionStackName": "64bit Amazon Linux 2016.09 v2.3.0 running Java 8"
      }
    }

The last step would be to glue the above properties by defining an environment

    "SpringBootBeanstalkEnvironment": {
      "Type": "AWS::ElasticBeanstalk::Environment",
      "Properties": {
        "ApplicationName": {"Ref":"SpringBootApplication"},
        "EnvironmentName":"JavaBeanstalkEnvironment",
        "TemplateName": {"Ref":"SpringBootBeanStalkConfigurationTemplate"},
        "VersionLabel": {"Ref": "SpringBootApplicationVersion"}
      }
    }

Now you are ready to upload your cloudformation template and deploy your beanstalk application

aws s3 cp beanstalkspring.template s3://{bucket with templates}/beanstalkspring.template
aws cloudformation create-stack --stack-name SpringBeanStalk --parameters ParameterKey=SourceCodeBucket,ParameterValue={bucket with code} --template-url https://s3.amazonaws.com/{bucket with templates}/beanstalkspring.template

You can download the full sourcecode and the cloudformation template from Github.