Dockerfileを書くにあたって気をつけなきゃと思ったこと

## 想定読者
* Dockerは使ったことあるけど、Dockerfileを真面目に書いたことはない方
* 気にすべきことを1つでも知っておきたいと思っている方

## 気をつけなきゃと思ったこと

### 1. コマンドの微妙な違いを理解しておく

#### 1.1　```RUN cd <dir>``` と ```WORKDIR <dir>```

　どちらも作業ディレクトリを指定するという意味では同じですが、微妙な違いを認識しておらず少しハマりました。
例えば、centosのベースイメージにnginxをソースからビルドしてインストールする場合、深く考えずに

```Dockerfile:Dockerfile(抜粋)
RUN cd /usr/local/src
RUN curl -O https://nginx.org/download/nginx-1.18.0.tar.gz
RUN tar xvzf nginx-1.18.0.tar.gz
RUN cd nginx-1.18.0
RUN ./configure
RUN make
RUN make install
```
のようにかいて ```docker build``` しようとすると、

```bash
/bin/sh: ./configure: No such file or directory
```
となります。
はじめは、curlでファイル取れてない？tarで解凍失敗した？などなど考え検証するのですが、実際には4行目で /nginx-1.18.0 に移動できていないことが原因だったりします。

```RUN cd nginx-1.18.0```とだけ書いてしまうと、```/nginx-1.18.0```を一瞬覗いただけで実際には移動しておらず、その後のコマンドはカレントディレクトリで実行されてしまうため、./configureなんてないよ、と言われているという次第です。なので一行目のcdも機能しておらず、結果的に全てのコマンドが```/```で実行されている形になっています。

こんなときに、```WORKDIR```を用いて、

```Dockerfile:Dockerfile(抜粋)
WORKDIR /usr/local/src
RUN curl -O https://nginx.org/download/nginx-1.18.0.tar.gz
RUN tar xvzf nginx-1.18.0.tar.gz
WORKDIR /usr/local/src/nginx-1.18.0
RUN ./configure
RUN make
RUN make install
```
のようにに書きかえてあげるとちゃんとディレクトリを渡り歩いてうまく動いてくれます[^1]。
[^1]: 本題から逸れるので省きましたが、実際にはこの前に[ここ](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#installing-nginx-dependencies)に記載のある依存ライブラリ(とgccとmake)のインストール処理が必要です。
ちなみに、```RUN cd```を使って4行目以降を

```Dockerfile
RUN cd /usr/local/src/nginx-1.18.0 && ./configure && make && make install
```
のように書くこともできます。
というか、イメージ容量削減の観点では後者の方が望ましいようです。(説明は3.2にて)

#### 1.2　```ARG``` と ```ENV```

変数を定義するという意味では同じですが、ARGは頻繁には使われないイメージです。違いは以下。

* ```ENV```　→　コンテナ内に定義される環境変数
* ```ARG```　→　Dockerfile内でのみ使用できる変数

私は ```ARG``` は主に、

```Dockerfile:Dockerfile(抜粋)
ARG NGINX_VERSION

RUN curl -O https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
```

```bash
docker build --build-arg NGINX_VERSION=1.18.0 --tag=sample_image:sample_tag
```
のようにビルド時に ```--build-arg``` で渡したバージョンをDockerfile内で参照する形で使っています。


#### 1.3　```VOLUME``` と```docker run -v```

これは、Dockerfileのコマンドというより、Dockerのマウントの種類の違いに関するものなので少し逸れますが、若干ハマったのでメモ的に記載します。マウントの種類とその違いについては各自調査いただければと思います。

　Dockerfile内に ```VOLUME <path>``` とかくと、```docker run -v <path>:<path>``` と書いたのと同等なのかと思っていましたが、前者は マウントの種類のうち「(Docker)ボリューム」によるマウントで、後者は「バインドマウント」によるマウントであって、挙動が異なるということを書いておきます。(詳細は長くなりそうなので別記事で書くかもしれません。)

### 2. 認証情報等をイメージに残さないようにする

社内環境でDockerを使うととにかくプロキシの壁にはまります。

* ```docker build``` しようとしたら、初っ端からエラー　=>　ビルド時にプロキシ認証情報を渡す
* ```yum install``` / ```wget``` / ```curl```しようとしたら名前解決エラー　=>　プロキシ認証情報を設定ファイルに書き込んでから実行[^2] or 実行時に引数で渡す[^3]

などなど。
ビルドするときだけでこれだけプロキシ認証情報を入れ込むタイミングがあるので、気をつけないとイメージの中のどこかに残ったままになってしまう恐れがあります。考えられるbetterな対応としては、

##### ビルド時にプロキシ認証情報を渡すとき  

```bash
docker build --build-arg HTTP_PROXY=${HTTP_PROXY} --build-arg HTTPS_PROXY=${HTTPS_PROXY} --tag=sample_image:sample_tag
```
のように、ホストに事前に設定した環境変数```HTTP_PROXY```, ```HTTPS_PROXY```を--build-argで渡す[^4]


##### プロキシ認証情報を設定ファイルに書き込んだ場合

Dockerfileのなかで、書き込んだプロキシ情報を```sed```などで一通り削除しておく[^4]


### 3. イメージの容量がなるべく軽くなるようにする

#### 3.1　なるべくオフィシャルイメージを使う

オフィシャルイメージは熟練者が書いたDockerfileで作られており無駄がないので、特別な理由がない限りはオフィシャルイメージを使うことをおすすめします。

以下は、nginx(latest)のイメージを例にイメージを比較した表です。

||容量|ベースイメージ|nginxインストール方法|
|:----|:----|:----|:---|:---|:---|
|Official Image|126 MB|debian:buster-slim|不要|
|自作1|328 MB|centos 8|```yum install nginx```|
|自作2|487 MB|centos 8|```ソースからビルド```|

「現実にはcentosあたりをベースに自作するケースが多いのでは」という仮説から自作のものはcentos 8をベースにしているので熟練者の削減術の成果がわかるものではありません。あくまで参考数値とご認識ください。centosはalpineやdebianと比べるとだいぶ容量をとるようですね。

#### 3.2　RUNコマンドは極力まとめる

依存ライブラリがたくさんあるときに、

```Dockerfile:Dockerfile(抜粋)
RUN yum install -y gcc
RUN yum install -y pcre pcre-devel
RUN yum install -y zlib zlib-devel
RUN yum install -y openssl openssl-devel
RUN yum install -y make
```
のように一つ一つ```RUN``` する形で書くと、その都度イメージのレイヤが追加されてイメージの容量の増加に繋がってしまうようです。こういうときは、

```Dockerfile:Dockerfile(抜粋)
RUN yum install -y gcc \
    && yum install -y pcre pcre-devel \
    && yum install -y zlib zlib-devel \
    && yum install -y openssl openssl-devel \
    && yum install -y make
```
のように一つの ```RUN``` でまとめてみましょう。

#### 3.3　マルチステージビルドを活用する

[Vue.js アプリケーションを Docker 化する](https://jp.vuejs.org/v2/cookbook/dockerize-vuejs-app.html#%E7%8F%BE%E5%AE%9F%E3%81%AE%E4%BE%8B) にあるように、1つのDockerfileの中で

* アプリのビルドイメージ作成ステージ
* ビルド成果物デプロイ用イメージ作成ステージ

のように複数のステージを定義し連携することで、アプリのビルド時のみ必要であったファイル等を最終的なイメージに含めなくて済み、イメージの軽量化につながるというものです。


そのほかにも、イメージの軽量化につながる工夫が公式ページ(以下)等にたくさん書かれているので、Dockerfileの熟練者を目指す方は熟読と実践をしてみていただければと思います。

https://docs.docker.jp/engine/articles/dockerfile_best-practice.html


## 終わりに

Dockerはなかなか慣れるまで時間がかかります。次はdocker-composeについてかけたらと思います。

[^2]: yum → ```/etc/yum.conf```, wget → ```~/.wgetrc```, curl → ```~/.curlrc```
[^3]: wget → ```-e http_proxy={プロキシ認証情報}```, curl → ```-x {プロキシ認証情報}``` (yumの場合は ```--setopt=proxy={プロキシ認証情報}```でできるとの情報もあるが動作未確認)
[^4]: これで完全にプロキシ認証情報が残らないと言い切れるほど検証ができておりませんゆえ、その点ご容赦ください。