DockerコンテナのベースイメージをUbuntuからAlpine Linuxに変えるために必要なこと

巷で流行り始めてるAlpine LinuxをつかったDockerコンテナ作成を自分のアプリで試してみた時の覚書です
どうせ 16.04 LTSに切り替えるのなら、いっそのことAlpine Linuxでも使ってやろうという試みです

【結果】70%の削減
===============

中身は RubyとUnicornベースのWebアプリ, PostgreSQL, Nginx なDockerコンテナです

```
$ docker images
REPOSITORY   TAG                 IMAGE ID            CREATED             SIZE
hoge         alpine              29dfb2fd387f        3 hours ago         174.1 MB
hoge         ubuntu14.04         7a4e83184274        4 days ago          582.2 MB
```

約400MB(70%)の削減となりました

やったこと
==========

_*-dev_ パッケージ
------------------

Gemなんかをインストールする際、Nativeなbinaryを作るためにヘッダファイルが入っている _*-dev_ なパッケージを入れざるを得なくなります
ですが、これらを入れると+150MBくらい簡単にサイズが増えますので、それを回避します

```
RUN apk add --update --no-cache --virtual=.build-deps ruby-dev postgresql-dev \
                                                  make g++ musl-dev libffi-dev \
                                                  linux-headers \
 && gem install unicorn --no-rdoc --no-ri \
 && (cd APP_ROOT ; bundle install --path vendor/bundle) \
 && apk del .build-deps
```

こんな感じです

1. [`apk add --no-cache`](http://qiita.com/pottava/items/970d7b5cda565b995fe7#apk---update-add-%E3%81%A8-apk---no-cache-add) で `rm -rf /var/cache/apk/*` の手間をなくします
2. `&&` でつなげ、この後の `apk del` まで単一のRUN(=レイヤー)まで一気に実行します
3. [`apk --virtual=NAME`](http://qiita.com/pottava/items/970d7b5cda565b995fe7#apk-add---virtual-%E3%81%AE%E5%88%A9%E7%94%A8) を使ってラベル付けをしておき、実行には必要のない _*-dev_ パッケージや make などを最後に `apk del NAME` で一括削除します

gosu
----

`&&` 等を駆使すればレイヤーを軽くできます
しかし、アプリを一般ユーザで実行する場合はどうでしょうか

例えばこんな場合です (ちょっと端折ってます)

```
RUN apk add ruby \
 && apk add --virtual=.build-deps ruby-dev postgresql-dev \
 && gem install pg
USER foo
RUN git clone https://github.com/hoge/app.git /home/foo/app \
 && cd /home/foo/app \
 && bundle install
```

`foo` に切り替えるタイミングで一旦RUNが切れてしまいレイヤーがコミットされます
そのためpostgresql-dev等が残ったままになり、後半で `apk del postgresql-dev` とかやっても[サイズ縮小にはなんら寄与しません](https://www.ianlewis.org/jp/small-docker-images)

### `su - USER -c ""` の出番?? ###

あーエスケープが面倒だからお呼びしたくないです

### で gosu を使う ###

`su -c ` のエスケープ問題を [gosu](https://github.com/tianon/gosu) で解決しましょう

インストールがちょっと(だいぶ)面倒ですが、こんな感じになります

```
RUN apk --virtual=.gosu-deps add curl gnupg openssl \
 && curl -o /usr/local/bin/gosu -sL "https://github.com/tianon/gosu/releases/download/1.8/gosu-amd64" \
 && curl -o /usr/local/bin/gosu.asc -sL "https://github.com/tianon/gosu/releases/download/1.8/gosu-amd64.asc" \
 && export GNUPGHOME="$(mktemp -d)" \
 && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
 && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
 && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
 && chmod +x /usr/local/bin/gosu \
 && gosu nobody true \
#--- ここまででgosuインストール
 && apk add ruby \
 && apk add --virtual=.build-deps ruby-dev postgresql-dev \
 && gem install pg
#--- さっきはここで USER foo としてた
 && gosu foo git clone https://github.com/hoge/app.git /home/foo/app \
 && cd /home/foo/app \
 && gosu foo bundle install \
 && apk del .gosu-deps .build-deps
```

これでワンレイヤーに収めることができます

Timezone
--------

我々はJSTです

```
RUN apk --update --no-cache add tzdata \
 && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
 && apk del tzdata
```

[あざます](http://qiita.com/dtan4/items/8359e389b95cbc60952d)

Edge(最新版Alpine Linux)のapkを借りてくる
-----------------------------------------

Stableなバージョンのapkはちょっと古い時があります

```
$ docker run --rm -it alpine apk --update search -x nginx
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/community/x86_64/APKINDEX.tar.gz
nginx-1.8.1-r0
```

そんな時は `apk --repository` オプションで edge を参照するようにしてみましょう

```
$ docker run --rm -it alpine apk --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/main/ search -x nginx
fetch http://dl-cdn.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.3/community/x86_64/APKINDEX.tar.gz
nginx-1.9.14-r1
```

無論インストールはこんな感じです

```
RUN apk --update --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/main/ add nginx
```

参考資料
--------

* [Alpine Linux で Docker イメージを劇的に小さくする - Qiita](http://qiita.com/asakaguchi/items/484ba262965ef3823f61)
* [Alpine Linux で軽量な Docker イメージを作る - Qiita](http://qiita.com/pottava/items/970d7b5cda565b995fe7)
* [Alpine Linux でタイムゾーンを変更する - Qiita](http://qiita.com/dtan4/items/8359e389b95cbc60952d)
* [tianon/gosu: Simple Go-based setuid+setgid+setgroups+exec](https://github.com/tianon/gosu)
* [(より)小さいDockerイメージを作ろう - Ian Lewis](https://www.ianlewis.org/jp/small-docker-images]

あとがき
--------

Alpine Linux、結構くる希ガス
EventMachineとかも[コンパイル方法掲載してるくらい](https://github.com/eventmachine/eventmachine/wiki/Building-EventMachine)ですしおすし

Ubuntu関係無かったですね
apk と gosu を駆使してワンレイヤーに押し込むというテクニックに終始してしまいました