Alpine Linux で軽量な Docker イメージを作る

# どれくらい小さくなるのか

ハンズオンをするにも、携帯でテザリングするにも
これくらい小さければいける・・かもしれないレベル。

## サイズ[^1] を比較してみる

| イメージ | version | Official | Alpine版 | Dockerfile
|:-----------|:-----------|------------:|------------:|:------------|
| Golang | 1.6.2 | [254MB](https://hub.docker.com/r/library/golang/tags/) | 72MB | [golang:1.6.2-alpine](https://github.com/docker-library/golang/blob/master/1.6/alpine/Dockerfile) (Official)
| Java | 8 | [243MB](https://hub.docker.com/r/library/java/tags/) | [52MB](https://hub.docker.com/r/library/java/tags/) | [java:8-alpine](https://github.com/docker-library/openjdk/blob/master/8-jdk/alpine/Dockerfile) (Official)
| 　　Clojure | Leiningen 2.6.1 | [257MB](https://hub.docker.com/r/library/clojure/tags/) | [71MB](https://hub.docker.com/r/library/clojure/tags/) | [clojure:alpine](https://github.com/Quantisan/docker-clojure/blob/master/alpine/Dockerfile) (Official)
| 　　Maven | 3.3 | [252MB](https://hub.docker.com/r/library/maven/tags/) | [51MB](https://hub.docker.com/r/pottava/maven/tags/) | [pottava/maven:3.3](https://github.com/pottava/docker-maven/blob/master/versions/3.3/Dockerfile)
| 　　Gradle | 2.11 | - | [89MB](https://hub.docker.com/r/pottava/gradle/tags/) | [pottava/gradle:2](https://github.com/pottava/docker-gradle/blob/master/versions/2/Dockerfile)
| 　　PlayFramework | 1.4 | - | [130MB](https://hub.docker.com/r/pottava/play/tags/) | [pottava/play:1.4](https://github.com/pottava/docker-playframework/blob/master/versions/1.4/Dockerfile)
| 　　AWS-DynamoDB | - | - | [103MB](https://hub.docker.com/r/pottava/aws-dynamodb-local/tags/) | [pottava/aws-dynamodb-local](https://github.com/pottava/aws-dynamodb-local/blob/master/versions/latest/Dockerfile)
| MySQL | 5.6 | [111MB](https://hub.docker.com/r/library/mysql/tags/) | - | なし[^2]
| Nginx | 1.11 [^3] | [71MB](https://hub.docker.com/r/library/nginx/tags/) | [19MB](https://hub.docker.com/r/library/nginx/tags/) | [nginx:1.11-alpine](https://github.com/nginxinc/docker-nginx/blob/master/mainline/alpine/Dockerfile) (Official)
| Node.js | 6.3 | [256MB](https://hub.docker.com/r/library/node/tags/) | [16MB](https://hub.docker.com/r/pottava/nodejs/tags/) | [pottava/nodejs:6.3](https://github.com/pottava/docker-nodejs/blob/master/versions/6.3/Dockerfile)
| PHP + Apache | 5.6 + 2.4 | [152MB](https://hub.docker.com/r/library/php/tags/) | [28MB](https://hub.docker.com/r/library/php/tags/) | [php:5.6-alpine](https://github.com/docker-library/php/blob/master/5.6/alpine/Dockerfile) (Official)
| PostgreSQL | 9.4 | [100MB](https://hub.docker.com/r/library/postgres/tags/) | [9MB](https://hub.docker.com/r/kiasaki/alpine-postgres/tags/) | [kiasaki/alpine-postgres:9.4](https://github.com/kiasaki/docker-alpine-postgres/blob/master/Dockerfile)
| Python | 2.7 | [261MB](https://hub.docker.com/r/library/python/tags/) | 19MB | [python:2.7-alpine](https://github.com/docker-library/python/blob/master/2.7/alpine/Dockerfile) (Official)
| 　　AWS-CLI | 1.10 | - | [27MB](https://hub.docker.com/r/pottava/awscli/tags/) | [pottava/awscli:1.10](https://github.com/pottava/docker-awscli/blob/master/versions/1.10/Dockerfile)
| 　　Sphinx | 1.4 | - | [38MB](https://hub.docker.com/r/pottava/sphinx/tags/) | [pottava/sphinx:1.4](https://github.com/pottava/docker-sphinx/blob/master/versions/1.4/Dockerfile)
| Redis | 3.0 | [62MB](https://hub.docker.com/r/library/redis/tags/) | 5MB | [redis:3.0-alpine](https://github.com/docker-library/redis/blob/master/3.0/alpine/Dockerfile) (Official)
| Ruby | 2.3 | [276MB](https://hub.docker.com/r/library/ruby/tags/) | 40MB | [ruby:2.3-alpine](https://github.com/docker-library/ruby/blob/master/2.3/alpine/Dockerfile) (Official)

## オフィシャルでない Alpine イメージを使うなら

上の表の通り、最近はオフィシャルでも Alpine 版があったりしますが
なければ、サイズの小さなサードパーティ製でも全然よいと思います。

選ぶなら、最低限 `Automated Build` で作成されており
Dockerfile から操作履歴が明瞭なもの[^4] がよさそうです。
ADD / COPY をしているものは GitHub に行けば中身を確かめることもできます。
もしくは、まるっとコピーして自分でイメージを作るのも手です。

## アプリのベースイメージに Alpine

いずれにせよ、特定 OS のシェルを直接叩くようなアプリでない限り、
Alpine はとてもいい選択肢と言えそうです。
Golang 製でもなければ[^5]、Alpine でよさそう。

参考資料でサイバーエージェント山田さんが [書かれている](https://speakerdeck.com/stormcat24/oqian-falsedockerimezihamadazhong-i?slide=20) ように
重いイメージはほんとにいいことがありません。

軽くしましょう！

# どうやって作るのか

## 基本はベストプラクティス

[効率的に安全な Dockerfile を作るには](/pottava/items/452bf80e334bc1fee69a) にもチラっと書きましたが
せっかくベースイメージで Alpine を使っても、Dockerfile がイケてないと台無しです。
以下あたりには注意しましょう！

* レイヤの数を最小に（&&でのコマンドチェーンなどを駆使）
* 同一レイヤー上で、最終的に不要なパッケージ・ソースコードは削除する
* ADD / COPY するなら `.dockerignore` も書いておく

## `apk --update add` と `apk --no-cache add`

Alpine Linux は 3.3 から apk で `--no-cache` というオプションが使えます。
従来は `--update add` でインストールした後に `rm -rf /var/cache/apk/*` で
不要なゴミファイルを削除していたようですが、いまや `--no-cache` で OK です。

## `apk add --virtual` の利用

apk では `--virtual` を指定してパッケージをインストールできますが
このオプションで仮の名前をつけてインストールしておけば、
後でそれらを仮の名前で一括削除できます。

```bash:--virtual
RUN apk add --no-cache --virtual .ruby-builddeps curl gcc make .. && \
    curl ... && \
    ... && \
    make install ... && \
    apk del .ruby-builddeps
```

これで削除もれも防げるし、とても便利ですね！

## `apk del --purge` の利用

パッケージを削除する際に `--purge` オプションを渡すことで、
関連する設定ファイルも合わせて削除することができます。

# おまけ

## 参考文献
* [お前のDockerイメージはまだ重い💢💢💢](https://speakerdeck.com/stormcat24/oqian-falsedockerimezihamadazhong-i)
Alpine Linux についても触れられていますが、とてもいい資料！
　

* [Alpine Linux package management](http://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management)
[Comparison with other distros](http://wiki.alpinelinux.org/wiki/Comparison_with_other_distros)
ちょろっと目を通しておくといいかもしれない。

## Alpine の読み方

アルプス（山脈）とかアルペン（スキー）とか日本語だと候補が複数あるんですが
発音記号を見るとアルパインな気がします。自信がないのでおもむろに Mac で

```bash:MacOSX
say alpine
```

を実行しましたが、やっぱりアルパインと聞こえる気がします。
たぶんアルパインなのでしょう・・

　

[^1]: DockerHub で見える、データ圧縮状態でのサイズ
[^2]: 現在 Alpine のパッケージマネージャ apk で MySQL をインストールすると MariaDB が入る状態。オフィシャルの MySQL イメージが洗練され [完成度高すぎ](http://dqn.sakusakutto.jp/2015/10/docker_mysqld_tutorial.html) なので、ここだけは 111MBで妥協してもいいと思ってます..
[^3]: バージョン 1.8 の Alpine 版なら、なんと [3MB](https://hub.docker.com/r/pottava/nginx/tags/) です。また、SSL や RealIP を静的コンパイルした Nginx がほしければ、[こんな感じ](https://github.com/pottava/docker-nginx/blob/master/versions/1.9/Dockerfile) で作れます。
[^4]: `docker history` というコマンドも便利
[^5]: Go なら [Linux 向けにビルドしたものを scratch に突っ込む](http://blog.codeship.com/building-minimal-docker-containers-for-go-applications/) 方法が最軽量。