Dockerイメージを軽量化する

# Dockerfileを工夫して、Dockerイメージを軽量化する

最近、Dockerを使い始めました。まずはDockerfileをゴリゴリ書いていたんですが、書いているうちに軽量化のテクニックを知っているかどうかで最終的にできるDockerイメージのサイズが劇的に変わることがわかったので、ここにそのテクニックをまとめてみようと思います。

ちなみに、Dockerイメージのサイズが小さくなると以下のようなメリットがあります。

- ホストのディスク容量を圧迫しない
- イメージの配布が効率的に行える

## 今回のお題

今回は人気サンドボックスゲーム Minecraft向けのオープンソースサーバーであるSpigotのDockerイメージを作成します。

記事中ではDockerイメージの作成で一般的に使えるテクニックについて解説するため、Minecraftに全く興味がない方も安心してご覧ください。

Spigotのビルド方法は以下を参考にしています。

[BuildTools | SpigotMC](https://www.spigotmc.org/wiki/buildtools/)

注意：Spigotは権利関係の事情により、ビルド済み実行ファイルを配布することができません。（詳しい経緯は以下が参考になるかと思います。）Dockerイメージでの配布も同様に問題になると考えられるため、Docker Hub等で公開しません。

[DMCA - Bukkit Spigot Japan Wiki](https://wiki.craftportal.jp/view/DMCA)

# まずは思いついたままに作る

とりあえず、一番単純なDockerfileを書いてみたら、どれぐらいのイメージサイズになるのか確認してみます。

以下のDockerfileをビルドすると、イメージサイズは**1.24GB**になりました。かなり大きいですね。

```dockerfile
FROM openjdk:8-jdk

ENV SPIGOT_VER 1.12.2

WORKDIR /minecraft
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER

WORKDIR /data
RUN echo "eula=true" > ./eula.txt

EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
```

# 軽量イメージを使用する

`openjdk:8-jdk` イメージはDebianを基に作られていますが、Javaを実行するだけならDebianに付随する多くのソフトウェアは必要ありません。そこで、最小限の機能を備えた軽量OSを基に作られたイメージを利用します。この用途にはAlpine Linuxが使われることが多く、openjdkのAlpine Linux版イメージもDocker Hubで公開されているため、今回はこれを使ってみます。

Alpine Linuxにはgit等も入っていないため、必要であれば随時インストールする必要があります。Alpine Linuxでは主にapkというパッケージマネージャを利用してソフトウェアをインストールします。

以下のようにAlpine Linux版のopenjdkイメージを利用してDockerfileを書き直し、ビルドしたところ、イメージサイズは**734MB**になりました。

```dockerfile
FROM openjdk:8-jdk-alpine

ENV SPIGOT_VER 1.12.2

WORKDIR /minecraft
RUN apk --no-cache add git
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER

WORKDIR /data
RUN echo "eula=true" > ./eula.txt

EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
```

# RUNをまとめる

`docker build` ではRUN, COPY, ADDを実行するたびにイメージのレイヤーを作成し、キャッシュ等に利用しています。つまり、これらの文が増えるほどイメージ内にレイヤーが作られるため、イメージサイズが大きくなってしまいます。各コマンドを `&&` で繋げて1つのRUN文内に押し込めることで、余計なレイヤーが作られるのを防ぐことができます。

...というはずだったんだすが、この方法を使って書き直したDockerfileをビルドしてみたところ、イメージサイズは先程の方法と同じ**734MB**でした。おそらく、もっとビルド手順が多い場合にある程度効果が出てくると思われます。

```dockerfile
FROM openjdk:8-jdk-alpine

ENV SPIGOT_VER 1.12.2

WORKDIR /minecraft
RUN apk --no-cache add git && \
  wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar && \
  java -jar BuildTools.jar –rev $SPIGOT_VER && \
  mkdir /data && \
  echo "eula=true" > /data/eula.txt

WORKDIR /data

EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
```

# マルチステージビルドを使う

今回のようにJavaソフトウェアのビルドを行う場合、ビルド時にはJDKが必要ですが、実行イメージにはJREだけ入っていれば十分です。つまり、ビルドの成果物だけをJREが入ったコンテナにコピーすればいいわけですが、これはDocker 17.05から追加されたマルチステージビルドを使うことで簡単に実現することができます。

ちなみに、マルチステージビルドを使えばビルド用のコンテナでどれだけレイヤーが作られても最終的な結果には影響がないため、先程のRUNをまとめるテクニックは使う必要がありません。

マルチステージビルドを使って書き直したDockerfileをビルドしてみたところ、イメージサイズは**126MB**になりました。マルチステージビルドすごい!!!

```dockerfile
FROM openjdk:8-jdk-alpine AS build-env

ENV SPIGOT_VER 1.12.2

WORKDIR /build
RUN apk --no-cache add git
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER
RUN mkdir minecraft
RUN cp spigot-${SPIGOT_VER}.jar minecraft/spigot.jar
RUN mkdir data
RUN echo "eula=true" > data/eula.txt


FROM openjdk:8-jre-alpine

WORKDIR /data
COPY --from=build-env /build/minecraft /minecraft
COPY --from=build-env /build/data /data

EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot.jar nogui
```

# まとめ

最終的にDockerイメージのサイズを**1.24GB**から**126MB**と大幅に小さくすることができました。同じような機能を持つDockerイメージでも、ちょっとした工夫によって劇的に軽量化できるので、ぜひ皆さん参考にしてみてください。