Michał Kalbarczyk

Deploy Elixir application to AWS Elastic Beanstalk

21 July 2016 - Reading time: 4 minutes

Lately I was playing with AWS. I’m just wondering how can I easily deploy Elixir application with Elastic Beanstalk. Quickly I found exrm. It’s a great tool for generating elixir/erlang project releases. So I added to my deps in mix.exs something like this:

{:exrm, "~> x.x.x"}

next step was generate a release:

$ mix deps.get
$ mix release

After a while new release was generated! So I want to deploy it! ELastic Beanstalk supports docker. So let’s generate one. Quickly find a proper Dockerfile:

FROM alpine:edge

RUN apk --update add erlang erlang-sasl erlang-crypto erlang-syntax-tools && rm -rf /var/cache/apk/*

ENV APP_NAME myapp
ENV APP_VERSION "0.0.1"
ENV PORT 4000

RUN mkdir -p /$APP_NAME
ADD rel/$APP_NAME/bin /$APP_NAME/bin
ADD rel/$APP_NAME/lib /$APP_NAME/lib
ADD rel/$APP_NAME/releases/start_erl.data                 /$APP_NAME/releases/start_erl.data
ADD rel/$APP_NAME/releases/$APP_VERSION/$APP_NAME.sh      /$APP_NAME/releases/$APP_VERSION/$APP_NAME.sh
ADD rel/$APP_NAME/releases/$APP_VERSION/$APP_NAME.boot    /$APP_NAME/releases/$APP_VERSION/$APP_NAME.boot
ADD rel/$APP_NAME/releases/$APP_VERSION/$APP_NAME.rel     /$APP_NAME/releases/$APP_VERSION/$APP_NAME.rel
ADD rel/$APP_NAME/releases/$APP_VERSION/$APP_NAME.script  /$APP_NAME/releases/$APP_VERSION/$APP_NAME.script
ADD rel/$APP_NAME/releases/$APP_VERSION/start.boot        /$APP_NAME/releases/$APP_VERSION/start.boot
ADD rel/$APP_NAME/releases/$APP_VERSION/sys.config        /$APP_NAME/releases/$APP_VERSION/sys.config
ADD rel/$APP_NAME/releases/$APP_VERSION/vm.args           /$APP_NAME/releases/$APP_VERSION/vm.args

EXPOSE $PORT

CMD trap exit TERM; /$APP_NAME/bin/$APP_NAME foreground & wait

Let’s try build docker locally:

$ docker build -t myapp .

Wow, It build! Let’s move on, and try to run it!

$ docker run -t myapp

Unfortunately my app using hakey and need and ssl stuff. After a lot of searching, I noticed that my release was build on mac. It simply will not work if there are packages that includes erlang erts etc. After a while found another solution, that don’t include this things in release. Added rel/relx.config:

{include_erts, true}.
{include_src, false}.

Yes. It’s erlang file, dots are necessary. But this solution also won’t work. Tried added packages like erlang-erts, erlang-asn1 into docker image, but without any luck. So it will not work. So I was thinking about creating a docker that will create release… Sounds interesting. But first let just run my elixir app on Beanstalk.

So I started to build another docker with following Dockerfile:

FROM msaraiva/elixir-dev

ENV APP_NAME myapp
ENV APP_VERSION "0.0.1.0"
ENV MIX_ENV prod

EXPOSE 80

RUN mkdir /$APP_NAME
ADD . /$APP_NAME/
WORKDIR /$APP_NAME

RUN ls -la

RUN mix deps.get
RUN mix deps.compile
RUN mix compile
RUN mix test
RUN mix do ecto.migrate

CMD mix server

Tired it local! Works. And on Beanstalk ?

$ eb deploy

Woow! Works. So I want to cleanup things a little. We don’t need all this files in a docker image anyway. I used .dockerignore file:

.git/
_build/
dist/
cover/
deps/
doc/
tarballs/
rel/
erl_crash.dump
*.ez
.edip.log

.*

OK, but what about Beanstalk ? Beanstalk use a .ebignore file witch can look exactly the same:

.git/
_build/
dist/
cover/
deps/
doc/
tarballs/
rel/
erl_crash.dump
*.ez
.edip.log

.*

So right now we send to Beanstalk only files that we need to build our docker image. BTW - Beanstalk don’t ignore all files in deps folder. I don’t know why, and really don’t know why few files was send. I check it with eb deploy --staged -v. Nevermind!

Now everything works just fine, but this is very stupid thing. I don’t need to upload source code of compiled language to server. I don’t want to.

After a while edib comes to rescue! This tool use exrm and docker to generate a proper release. This is what I was thinking of. Docker for building docker.

Great. Let’s run it! Add another dependency to mix.exs:

{:edib, "~> x.x.x"}

and let magic begins…

$ mix edib

Cool. Release generated, docker created! Now I can run it:

$ docker run --rm -e "PORT=4000" -p 4000:4000 local/myapp:0.0.0

It works. But how can I put it into Beanstalk ? I have a image tar.gz file in tarballs directory. But there must be a Dockerfile so beanstalk can create a proper image. OK lets try to build one:

FROM hackmann/empty

ENV APP_NAME myapp
ENV APP_VERSION "0.0.0"
ENV MIX_ENV prod
ENV ARCHIVE $APP_NAME-$APP_VERSION.tar.gz

EXPOSE 4000

ADD ./tarballs/$ARCHIVE /

CMD trap exit TERM; /app/bin/myapp foreground & wait

We have to start from scratch.

Interesting think is that Beanstalk don’t allow us to create empty docker with FROM scratch directive, do I use one empty one. If you want to your docker image stays empty forever, you can create your own.

Next we need to add files from release archive to docker. We can use ADD directive. It will unarchive it for us. Just wonderful isn’t it ?

And yes it works on Beanstalk. You can deploy it with eb deploy. But wait. I have to cleanup our .dockerignore and ebignore files. We only need tarball and Dockerfile. We can use ! directive like this:

*
!tarballs/
!Dockerfile

First line will ignore all things, and just not ignore tarballs and Dockerfile

And that’s it. We have a proper docker image only with compiled code. Also we have to remember that within release we don’t have a mix. So things like migration will not work. There is a lot of solutions for that. Just google exrm release migration, you’ll find a few.

Right now, deploying to aws is easy as deploying to heroku. You just type:

$ mix release
$ eb deploy

In the end I like to quote Borat:

It’s Very Nice!