Elixirアプリケーションのコンテナをできるだけダイエットした話

## 概要
Elixirで書いたchatops用アプリケーション `anago`をGKEで動かそうとdocker buildしたらかなり重めのRails並の容量になってしまったのでどこまでそぎ落とせるか検証した話です。

```Dockerfile
FROM elixir:latest

WORKDIR /app
ENV MIX_ENV=prod

RUN mix local.hex --force && \
  mix local.rebar --force

COPY mix.exs mix.exs
COPY mix.lock mix.lock
COPY config config
RUN mix deps.get --only prod && \
  mix deps.compile

COPY . .
CMD ["mix", "run", "--no-halt"]
```

最初の頃のDockerfileはこんな感じです。
このDockerfileでビルドしたアプリケーションのサイズは下記の通り...

```
docker images
##
REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
anago                                     latest          aa7e0779d912   2 seconds ago    1.27GB
```
画像もJSONもDBもない簡単なアプリケーションでのサイズが1.27GBと出てきたらさすがにダイエットを決意するよね...

## ベースイメージの確認

Elixirのベースイメージサイズは下記の通り。

```bash
docker pull elixir
## docker images
REPOSITORY                                TAG             IMAGE ID       CREATED         SIZE
elixir                                    latest          83478b936eba   2 days ago      1.24GB
```

比較にGo言語のコンテナを取得してみると...

```bash
docker pull golang
### docker images
REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
golang                                    latest          5f9d35ce5cfe   3 weeks ago      839MB
```

やっぱりファイルサイズはビルドしたライブラリのコード含めても30MB程度。
だけどベースイメージがそもそも1.24GBもある...
比較に取ったGoのイメージと比べても300MB近くElixirのほうが重たい:upside_down:


## alpineにしてみる

イメージサイズのダイエットの入り口はベースイメージをalpineにすることだと思うのでベースイメージを変えてみる。

```bash
docker pull elixir:alpine
## docker images
REPOSITORY                                TAG             IMAGE ID       CREATED         SIZE
elixir                                    alpine          dd10458addd0   2 days ago       84.9MB
```

圧倒的シェイプ！96%減！
これにアプリケーションの30MBを追加しても114MBに！

```
REPOSITORY                                TAG             IMAGE ID       CREATED              SIZE
anago                                     alpine          c8b8c08b7aef   About a minute ago   114MB
```

もともとのサイズと比較すると10%未満にダイエット成功！！
もうこれで良いんじゃない？ :thinking: 

## escript、動きます
Erlangにはアプリケーションを一つの実行ファイルに固めるescriptという仕組みがあります。
実行ファイルはErlangのランタイムがある環境でなら実行可能だったりします。
ということはErlang:alpineのイメージで動くんじゃない？ :thinking: 
さっそくErlang:alpineのベースイメージを手に入れます。

```bash
docker pull erlang:alpine
## docker images

REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
erlang                                    alpine          daa6bbdd7458   2 weeks ago      69.4MB
```

ElixirはErlang上で動くためErlangのベースイメージのほうが軽量だったりします。
15MB程度ですが軽量ですね。
これで行きましょう！！

ということでDockerfileに `mix escript build`を追加。
ついでにソースコードも要らないのでマルチステージビルドもやりましょう。

```Dockerfile
FROM elixir:alpine as build

WORKDIR /app
ENV MIX_ENV=prod

RUN mix local.hex --force && \
  mix local.rebar --force

COPY mix.exs mix.exs
COPY mix.lock mix.lock
COPY config config
RUN mix deps.get --only prod && \
  mix deps.compile

COPY . .

RUN mix escript.build

FROM erlang:alpine

WORKDIR /app
COPY --from=build /app/anago /app/anago
```

これでヨシ！ :thumbsup: 
あらためで `docker build`してサイズを見てみると :eyes: 

```
REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
anago                                     erlang_alpine   a01f810d11dc   2 seconds ago    71.4MB
```

71.4MBまで軽量化に成功！！！
ついに100MBの壁を突破しましたよ！！

---

#### 閑話休題

このアプリケーションはもともと `mix run --no-halt`で動かすつもりだったのですがescriptにしたら即終了されてしまいます。
そういう時は `timer.sleep(:infinity)`と書いておくとずっと動いてたりします。

```elixir
defmodule Anago.CLI do
  def main(_args) do
    :timer.sleep(:infinity)
  end
end
```
---

## どこまで減らせる？

erlang:alpineだって最低限何らか一緒に入っているだろうとは思うのでalpineに `apk add erlang`してどこまで減るのか気になってきました。
バニラalpineのサイズはわずかに5MBです。
試しにalpineにerlangだけインストールするDockerfileを作ってみます。

```Dockerfile
FROM alpine:latest
RUN apk add --no-cache erlang
```

このDockerfileをビルドすると結果は 63.6MBまで軽量化されます！(Erlangが58MBくらいの容量になるのでこれ以上のダイエットは無理)
ここまで減らしちゃうとちゃんと動くのかが不安になりますがここまで来たら目標もどこまでサイズを削ぎおとせるかにシフトしているのでアプリケーションを含めてビルドしてみます。

```bash
REPOSITORY                                TAG             IMAGE ID       CREATED             SIZE
anago                                     custom_alpine   fb1de28697a1   9 minutes ago       65.6MB
```
65.6MBまで来ました :tada: 

## あとがき

ちなみにこのイメージもちゃんと実行できました:tada:(すごい...)
Erlangのサイズの限界でおそらくこれ以上の軽量化はできないと思うんで軽量化の限界は63.6MB + escriptとなるようです。