Deploying a Haskell Application to AWS Elastic Beanstalk
This article describes a simple approach to set up a continuous integration and continuous deployment pipeline for a sample Haskell application deploying to AWS Elastic Beanstalk. While some of the scripts provided are specific to Haskell, the general approach is language agnostic and could be applied to any application that can be wrapped in a docker container. A sample application containing all the scripts can be found on Github.
Tools we’ll use
By the end of this article, we will have a continuous integration workflow which when triggered by a
git push, runs test, builds a new version of a
binary, and deploys the binary in a Docker container to EB.
Pros and cons
The advantages of Elastic Beanstalk should be apparent as we don’t have to configure or manage any servers. We have auto scalability with highly configurable triggers, we get automatic health checking, log aggregation and much more. The downside is that the “native” support for EB is pretty limited, so we have to resort to wrapping our application in a Docker container and pushing that to a registry. The use of Docker is an unfortunate side-effect here, but doesn’t really add that much irritation to the process, and potentially has some extra benefits if you actually want to use other features of Docker and not just use it as a wrapper.
Step 1: Create your EB application through the UI
First, we need to set up the EB application, so head over to the AWS management console and select Beanstalk (make sure you’re in the region you want to deploy your application to). Then go through the wizard to create your application, the platform you should select is the generic Docker one, not the Multi-container Docker. Take note of the application and environment names, while you’re in the UI you can also check what your Account ID is, as you’ll need these for the next steps.
Ideally I’d like to be able to automate this step as well through the CLI, but unfortunately I can’t seem to get AWS to create the necessary service and instance roles unless I create the application through the UI. If anyone has a way of doing this reliably I’d love to hear it.
Step 2: Run the setup script
Once you have an EB application with an environment launched, the rest is pretty straightforward. Make sure to go through the scripts found in the sample application and replace the placeholder values
<account-id> with the appropriate values from the previous step. Run the
setup.sh script, shown below. This will create an ECR repo with a name of your choosing, and create a user for the CI system with the appropriate permissions.
Step 3: Configure CircleCI and push code
The ultimate goal of
our setup here is to be able to have automatic tests and deploys (on successful test run) with
git push, so in order to achieve that, we
use CircleCI as our test-runner and build machine. Push your code to a CircleCI supported
code repository like Github or Bitbucket, then go into the CircleCI UI and configure the AWS
permissions to the CI user that was created during setup. When you push code now, CircleCI will
go through the steps in
circle.yml and execute
them, the steps being:
- Install dependencies
- Run the tests
- [If tests pass] Build a binary and save it to current directory
- [If tests pass] Wrap the built binary in a Docker container and run the deploy script
This works well because CircleCI builds are also run in a Docker container on an Ubuntu machine, if we had a build server that produces binaries incompatible with Docker (like OSX), we would have to do something else.
The deploy script will then
- Push the Docker container to ECR
- Upload a specification of the new container to an S3 bucket where EB can get the details, setting the version number to the git commit hash for the build.
- Tell EB to update all the running instances to this new version
Grow into the future
There are several things not covered here, because how you handle it can be pretty specific to your business or your opinions. You can add databases to your EB application or run them separately. You shouldn’t really allow SSH to the instances themselves as that typically promotes bad behavior, furthermore, you might want to make sure that you have everything in a VPC and only allow ingress to the environment inside the VPC through some sort of bastion server setup. You could possibly tweak the IAM policies that I apply to the CI user or to the Beanstalk role to be more restrictive.
I haven’t talked at all about which instances your EB application should be running, because likewise the size of them will depend on what your application needs.
In general, I believe this is a good starting template to get your application deployed quickly and easily, but of course you should get familiar with AWS in general and tweak things to your liking. If you have any feedback or suggestions for edit, don’t hesitate to comment here or open an issue on the Github repo.
Thanks to Brendan Hay for reviewing this post.