Minimal Erlang/Elixir docker images with Alpine Linux
Alpine Linux is a lightweight Linux distribution built around musl libc and busybox. The main focus of this distribution is security, simplicity and resource efficiency. All that makes Alpine Linux perfect to work as base images for linux containers.
When creating a docker image, you probably want to minimize its size as much as possible. At the same time, you might want to have access to a full-featured package system with a large range of packages available. As far as I can see, Alpine Linux is the best choice for that.
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE hello latest dfee0002c943 12 minutes ago 20.75 MB hello_phoenix latest 0ea00b410d90 24 minutes ago 25.09 MB msaraiva/elixir 1.2.0 df35f2590cd3 38 minutes ago 23.23 MB msaraiva/erlang 18.1 55ac7fb64a42 56 minutes ago 18.3 MB
In order to keep packages as compact as possible, Erlang libraries for Alpine Linux are split into many different packages. The full list of Erlang packages available can be found here.
The
apkcommand is the official tool for package management on Alpine Linux. Something like
apt-geton Ubuntu. More information about
apkcan be found here.
Create a Dockerfile
FROM alpine:3.3RUN apk --update add erlang && rm -rf /var/cache/apk/*
CMD ["/bin/sh"]
Build the image:
$ docker build -t erlang .
Run
docker images. You should see something like:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE erlang latest d76965a1f753 4 seconds ago 18.3 MB
You can see how packages are built by looking at the APKBUILD scripts:
For more info, see http://wiki.alpinelinux.org/wiki/APKBUILD_Reference
If you take a look at the APKBUILD scripts, you'll notice that some patches are applied in order to successfully build the packages. Some of those patches are related to musl, some to Busybox and some just split or remove stuff to make packages smaller. - Patches for Erlang
Note: All base images listed here are automated builds and their Dockerfiles can be found in the dockerfiles folder.
I'll describe here some examples on how to create minimal docker images for Erlang/Elixir projects using Alpine Linux.
A simple command line executable
mix escript.build
Dockerfile:
FROM msaraiva/erlang:18.1ADD hello /usr/local/bin/hello
ENTRYPOINT ["/usr/local/bin/hello"]
Building:
$ git clone https://github.com/msaraiva/docker-alpine-examples $ cd docker-alpine-examples/hello $ MIX_ENV=prod mix escript.build $ docker build -t hello .
Run
docker images. You should see something like:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE hello latest ab3d45ddf551 18 seconds ago 20.75 MB
Running:
$ docker run --rm hello Docker Hello, Docker!
The same simple command line executable. But:
Building:
$ docker run --rm -v $PWD:$PWD -w $PWD -e "MIX_ENV=prod" msaraiva/elixir sh -c "mix escript.build"
Running:
$ docker run --rm hello Docker Hello, Docker!
In order to generate releases for phoenix applications, you need to make some minimal changes in a couple of files. See this page from the Phoenix documentation for details. If you just want to see the changes, take a look at this commit.
Another thing to pay attention to, is the architecture of the build environment. From the same Phoenix documentation:
We need to be sure that the architectures for both our build and hosting environments are the same, e.g. 64-bit Linux -> 64-bit Linux. If the architectures don't match, our application might not run when deployed.
By default,
exrmpulls the Erlang runtime system from the build environment. That means, if you generate a release, for instance, on Windows or OSX, your application will not run on Alpine Linux. There are two ways to deal with this:
exrmto exclude the Erlang runtime from the release
Note: To instruct
exrmto exclude the Erlang runtime from the release, we need to create a file calledrel/relx.configwith this content:{include_erts, false}..
Let's see how we can do this.
This is the hello phoenix application created when you run
mix phoenix.new hello_phoenix --no-brunch --no-ecto
mix release
Dockerfile:
FROM msaraiva/erlang:18.1RUN apk --update add erlang-crypto erlang-sasl && rm -rf /var/cache/apk/*
ENV APP_NAME hello_phoenix 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
Building:
$ cd hello_phoenix $ mix deps.get $ MIX_ENV=prod mix compile $ MIX_ENV=prod mix release $ docker build -t hello_phoenix .
Running:
$ docker run --rm -p 4000:4000 hello_phoenix
A simple command line executable that calculates a dot product of two lists on a NIF
mix escript.build
Compiling:
$ cd hello_nif $ docker run --rm -v $PWD:$PWD -w $PWD -e "MIX_ENV=prod" msaraiva/elixir-gcc sh -c "mix deps.get && mix escript.build"
Building the docker image:
$ docker build -t hello_nif .
Running:
$ docker run --rm hello_nif Hello! This dot product was calculated by a NIF: [1.0, 2.0, 3.0] x [5.0, 10.0, 20.0] = 85.0
Contributions are more than welcome. There're a lot of ways to contribute:
Feedback is also very important. If you have something to share, fell free to open an issue.