Alpine Linux で Docker イメージを劇的に小さくする

Docker イメージを小さく作るテクニックって、いろいろありますよね。不要なファイルやディレクトリを削除したり、複数の RUN 命令をひとつにまとめたりなどなど。

ところが、ベースイメージに Alpine Linux を使う（`FROM alpine` とする）と、Docker イメージのサイズを **劇的に小さくできる** ことがわかりました。

いままで、Docker イメージのサイズを小さくするために、ちまちまとやってきたことは、なんだったんだろうという感じです。まあ、それはそれで組み合わせて使いますが . . . なんとも . . . ねえ（笑）


## Alpine Linux とは
- [Alpine Linux](http://alpinelinux.org) は、セキュアで軽量な Linux ディストリビューション
- [musl libc](http://www.musl-libc.org) と [BusyBox](http://www.busybox.net) をベースに構成されている
- 組込み系に適した Linux ディストリビューション
- パッケージ管理は、[APK](http://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management) と呼ばれる独自のシステムを使用


## 実際どれくらい小さくなるのか
Ruby を組み込んだ「Ubuntu ベースの Docker イメージ」と「Alpine ベースの Docker イメージ」で、そのサイズを比較してみました。

Ubuntu ベース、Alpine ベースそれぞれの Dockerfile は、次のとおりです。

```dockerfile:ubuntu-ruby.dockerfile
FROM ubuntu:xenial-20160525
RUN apt-get update && apt-get install -y ruby && rm -rf /var/lib/apt/lists/*
```

```dockerfile:alpine-ruby.dockerfile
FROM alpine:3.4
RUN apk --update add ruby && rm -rf /var/cache/apk/*
```

これを、次のコマンドで、それぞれビルドします。

```
docker build -f ubuntu-ruby.dockerfile -t ubuntu-ruby .
docker build -f alpine-ruby.dockerfile -t alpine-ruby .
```

ちゃんと動くか動作を確認しておきます。

```
docker run --rm ubuntu-ruby ruby -e 'puts "hello, world"'
docker run --rm alpine-ruby ruby -e 'puts "hello, world"'
```

それぞれ、hello, world と表示されれば、OK です（というか、表示されます）。

`docker images` コマンドで、サイズを確認すると . . .

```
$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
alpine-ruby                        latest              1ac582156fbc        28 seconds ago      20.39 MB
ubuntu-ruby                        latest              b90dadde262c        50 seconds ago      156.9 MB
```

なんと！！！ Alpine Linux ベースの Docker イメージは、20.39MB に！！！
Ubuntu ベースの Docker イメージは、156.9MB ですから、この差はデカイです！


## この差はなにか
ベースイメージを変えただけで、なぜ、こんなにも差が出るのでしょうか。

まあ、答えは簡単ですよね。ベースイメージの大きさの差がそのまま結果に表れているということですね。

ベースイメージでよく使われていると思われる Ubuntu、Debian および CentOS と Alpine で、その大きさを比較してみました（2016/06/04 時点）。

```
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
alpine                             3.4                 f70c828098f5        32 hours ago        4.799 MB
centos                             7                   904d6c400333        34 hours ago        196.8 MB
ubuntu                             xenial-20160525     2fa927b5cdd3        7 days ago          122 MB
debian                             8.4                 1742affe03b5        11 days ago         125.1 MB
```

Alpine のサイズは、驚きの 4.799MB ！！！

Ubuntu、Debian、CentOS がいずれも 100MB を超えているので、その差は本当に大きいですね。

ただ、あまりの小ささに少し不安になりますよね。本当に大丈夫なのかと。なので、いくつかの Docker イメージを作って、試してみました。本格的に使用しているわけではありませんが、まずまずちゃんと動いているようです。

そのいくつか試した中から、Nginx の例を載せておきます。参考にどうぞ。


## Nginx サンプル
パッケージのインストールは、[APK](http://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management) を使います。APK は、Alpine Linux のパッケージ管理システムです。APK は、同種の APT や Yum と同じような感じで使えます。次の Dockerfile のサンプルで、APK の使い方を確認してみましょう。

なお、利用できるパッケージの一覧は、[Alpine Linux package index](https://pkgs.alpinelinux.org/packages) で確認できます。


### Dockerfile
ここでは、Nginx をソースからコンパイルしてインストールする方法を紹介します。APK は、Nginx ソースの入手と、コンパイル＆インストールに必要なパッケージのインストールで使用しました。

（APK で、Nginx をインストールすると、stable 版の v1.8.0 がインストールされるようです。より新しいバージョンをインストールしたい場合や、欲しいパッケージが用意されていない場合は、このソースからコンパイルしてインストールする方法が参考になるかと思います。）

```dockerfile:alpine-nginx.dockerfile
FROM alpine:3.4

ENV NGINX_VERSION 1.11.1

RUN apk --update add pcre-dev openssl-dev \
  && apk add --virtual build-dependencies build-base curl \
  && curl -SLO http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \
  && tar xzvf nginx-${NGINX_VERSION}.tar.gz \
  && cd nginx-${NGINX_VERSION} \
  && ./configure \
       --with-http_ssl_module \
       --with-http_gzip_static_module \
       --prefix=/usr/share/nginx \
       --sbin-path=/usr/local/sbin/nginx \
       --conf-path=/etc/nginx/conf/nginx.conf \
       --pid-path=/var/run/nginx.pid \
       --http-log-path=/var/log/nginx/access.log \
       --error-log-path=/var/log/nginx/error.log \
  && make \
  && make install \
  && ln -sf /dev/stdout /var/log/nginx/access.log \
  && ln -sf /dev/stderr /var/log/nginx/error.log \
  && cd / \
  && apk del build-dependencies \
  && rm -rf \
       nginx-${NGINX_VERSION} \
       nginx-${NGINX_VERSION}.tar.gz \
       /var/cache/apk/*

VOLUME ["/var/cache/nginx"]

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]
```
※**2016/05/22 変更**: `alpine:3.2` → `alpine:3.3`、`NGINX_VERSION 1.9.7` → `NGINX_VERSION 1.10.0`
※**2016/06/04 変更**: `alpine:3.3` → `alpine:3.4`、`NGINX_VERSION 1.10.0` → `NGINX_VERSION 1.11.1`

APK コマンドの解説:

- `apk add`: 指定したパッケージをインストールします。
  - `--update` オプション: パッケージリストを更新します。最初の `apk add` 時に必要です。
  - `--virtual` オプション: インストールするパッケージ群に名前をつけます。`apk del` で使用します。  
    この例では、build-base と curl をひとつのグループとして扱い、build-dependencies と命名しました。
- `apk del`: 指定したパッケージをアンインストールします。  
  この例では、`apk add` の `--virtual` オプションで命名した build-dependencies を指定しました。これにより、build-dependencies に紐付いた build-base と curl をアンインストールします。


### 使い方の例
どこか適当なディレクトリで、上記 Dockerfile を alpine-nginx.dockerfile というファイル名で保存して、次のコマンンドを実行します。最後に、'hello, world' と表示されれば成功です。

```
docker build -f alpine-nginx.dockerfile -t nginx .
echo 'hello, world' > index.html
docker run -d -p 8080:80 -v $(pwd):/usr/share/nginx/html nginx
curl http://$(docker-machine ip default):8080
```

なお、Linux 環境の場合は、最後の curl コマンドは、`curl http://localhost:8080` に読み替えてください。


## まとめ
作成する Docker イメージのベースイメージに、Alpine Linux を指定すると、Docker イメージのサイズを劇的に小さくできることがわかりました。

これは、試していて実感したのですが、Docker イメージのサイズが小さいと、`docker pull` も `docker push` も `docker build` も超高速です。これは、嬉しかったですね。

ただし、どのような Docker イメージにも適用できるかと問われると、なんとなくですが難しいケースも多々ある気がします。とはいえ、試して見る価値は、十分にあると思います。