Deploying a Spring Boot app to ElasticBeanstalk with Concourse

Paul Milian
BeTomorrow
8 min readFeb 6, 2018

--

ElasticBeanstalk is a great tool to host web applications without hassle. However, it can be quite tedious to have to manually create an environment and then upload code to it. What if we could automate that?

We will see how to do this by using EB CLI and Concourse.

EB CLI

EB CLI is a command line tool provided by Amazon to interact with ElasticBeanstalk. It allows us to, amongst other things, create applications, environments and deploy apps to ElasticBeanstalk. This will make our life much easier when writing scripts.

Its reference documentation can be found here: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html

Concourse

Concourse (https://concourse.ci) is a continuous integration platform that runs tasks in docker containers. It uses resources such as S3 files, git repositories or other files on the internet as inputs and outputs for each task.

In our case we will use Concourse to build our Spring Boot app and deploy it to ElasticBeanstalk continuously on each git commit.

You can easily run your own Concourse server if you do not have access to one by following the instructions found here: https://concourse.ci/installing.html

The Demo App

The demo app that we will use is a very simple Spring Boot app that displays a “Hello world!” message in JSON.

The source code of the demo app can be found here: https://github.com/pmilianbeto/elasticbeanstalk-demo-app

The app runs, like any other Spring Boot app, on the port 8080:

$ curl localhost:8080 | jq
{
"message": "Hello World!"
}

Building the app

Now that we have a demo app, let’s go ahead and create a pipeline and a job to build and package our app to a jar file.

First, we need to create a git repository to store our pipeline code and to be able to use it as a Concourse resource. In our case the git repository will be https://github.com/pmilianbeto/elasticbeanstalk-concourse-pipeline.git

The other resource will be the git repository for the app source code.

This is what the first version of our pipeline looks like:

In the resources section we declare our two resources. The build-demo-app job in the jobs section indicates that we will be using them. The trigger: true tells Concourse to automatically start a build whenever a new commit is pushed to the repository. The task section indicates that the configuration for the task is in the build.yml file.

The build.yml is pretty simple. It specifies what container the task will use, in this case maven/3.5.0-jdk-8-alpine, and the command to be executed. In our case we simply run a shell script called build.sh. Also, since the task is running inside a temporary container, any produced files will be lost when the task ends because the container is destroyed. To prevent that from happening we tell concourse about which output folder we need so that it will create and mount it for us.

If we have a look at the build.sh script, we can see that we just run mvn package to create the jar and then move that jar to the output folder.

Let’s try our first pipeline out!

First we will login to Concourse to avoid entering our credentials all the time:

$ fly -t demo login --concourse-url <your concourse url>

And then push the pipeline, which will be named demo-app:

$ fly -t demo set-pipeline -n -p demo-app -c pipeline.yml

Now, if you navigate to your Concourse URL, you should see the pipeline composed of a build-demo-app job and its two inputs: demo-app-src and pipeline-src. The pipeline is paused by default. We can click on the play button in the menu on the top left to un-pause it.

Freshly created pipeline. It is paused.

As we un-pause the pipeline, the build-demo-app job will start after a short while. The build-demo-app box will turn green to indicate success and another color otherwise. We can check the build details by clicking on it:

Build success !

We can see in the details that the build folder contains our demo-app.jar. But, as it is, the file is useless because it cannot be stored by Concourse. Indeed, Concourse is just a job orchestrator. It only stores configurations and statuses about builds and resources. We will use an S3 bucket to store the file.

For this we will have to go to the amazon S3 console and create the bucket. We will name our bucket demo-app-ops. Be sure to enable versioning on it otherwise you will have to add temporal or build number information in the filename. This is because the S3 resource only uses lexicographical sorting on the filename when bucket versioning is not enabled, regardless of their upload date.

Now that our bucket is created we can modify our pipeline to push our jar file to the bucket.

The additions here are the new S3 resource named demo-app-artifact that is declared in resources section and the put task declared after the build one.

In the S3 resource we can see that there are two variables aws-key-id and aws-access-key. These are provided in a configuration file that looks like this:

This file will be provided to Concourse and the variables declared in it will be used to replace the variables in the pipeline.

The put task tells Concourse to upload the file build/demo-app.jar file to the S3 resource and name it demo-app.jar.

Let’s give it a try:

$ fly -t demo set-pipeline -n -p demo-app -c pipeline.yml -l credentials.yml

Notice the additional-l credentials.yml.

The demo-app-artifact has been added.

If we go back to Concourse we can see the new demo-app-artifact output. We can trigger a new build manually in the build-demo-app job details to check if the jar is uploaded to S3.

This is the result of the build:

And this is the file on S3:

Ok, now that our jar is built and available on S3, let’s move on and actually deploy it to ElasticBeanstalk.

Deploying to ElasticBeanstalk

We will be using EB CLI to create the ElasticBeanstalk application, the environment and then to deploy the app. As Concourse tasks run in docker containers, we will need a container that has the EB CLI installed. Fortunately such a container already exists: https://hub.docker.com/r/betomorrow/docker-awsebcli/

Let’s add a new deploy task:

In the shell script we can see that we move into the bundle folder before executing a few eb commands. This is because the eb init command creates a .elasticbeanstalk folder in which it will store some configuration files. EB CLI needs this folder and configuration files to exist in order to call the other commands such as eb create or eb deploy. The eb init also creates, if it doesn’t already exist, the application on AWS.

When creating the application, we declare an environment variable SERVER_PORT=5000. This environment variable tells Spring to listen on the port 5000 instead of 8080. Indeed, ElasticBeanstalk Java applications are deployed with an nginx server that is configured to redirect incoming traffic on the port 80 to the port 5000.

Updating the pipeline file

Now we must update the pipeline.yml file to add a new job:

There is not much new here, besides the passed configuration. It just means that the build-demo-app job must have succeeded for this job to run.

Let’s update our pipeline and check out the result!

$ fly -t demo set-pipeline -n -p demo-app -c pipeline.yml -l credentials.yml
The job is creating the environment.
A new version is being deployed to ElasticBeanstalk.
The deployment succeeded.

The deployment succeeded. If we navigate to the ElasticBeanstalk console we can now see that our app has been created with its environment.

Our freshly created app.

Our app is now available at the URL: http://demo-app-env.eu-west-1.elasticbeanstalk.com/

$ curl http://demo-app-env.eu-west-1.elasticbeanstalk.com/ | jq
{
"message": "Hello World!"
}

Testing the Continuous Delivery

Good, now we can commit a new version of the app to see if our pipeline is truly automated.

In the src/main/resources/ folder of the demo-app there is an application.yml file. Let’s change the message:

And now let’s commit and push the new code.

$ git commit -am "test concourse trigger"
$ git push

After a short while we can see that Concourse starts a new build, uploads the new artifact to S3 and then deploys that new artifact to ElasticBeanstalk, all automatically.

A new application version has been deployed.

Let’s use curl to check that the new app has been deployed properly:

$ curl http://demo-app-env.eu-west-1.elasticbeanstalk.com/ | jq
{
"message": "Goodbye, world!"
}

Great, that’s it! Now, whenever someone pushes to the demo-app git repository, Concourse will run the build and, if it succeeds, deploy the app to ElasticBeanstalk.

Additional links

Wow, that was a lot of YAML 😱! Here are some tips and tricks to keep on top of things:

Perhaps you would also like to know which books to read next?

Source code

The source code is available here:

Alternatives

The following alternatives let you deploy to ElasticBeanstalk directly from maven:

--

--

Paul Milian
BeTomorrow

A Software Engineer who is passionate about his craft.