あなたのDockerfileはベストプラクティスに従っていますか？(ベストプラクティスとチェックツール)

# Dockerfileのベストプラクティス

## ベースイメージには公式のレポジトリを使用する

* FROM命令において、使用するベースイメージは公式のリポジトリのものを使用し、軽量なものが推奨されている。
* 例えば、Debian イメージなど。

```dockerfile
FROM [--platform=<プラットフォーム>] <イメージ名> [AS <名前>]
FROM [--platform=<プラットフォーム>] <イメージ名>[:<タグ>] [AS <名前>]
FROM [--platform=<プラットフォーム>] <イメージ名>[@<ダイジェスト>] [AS <名前>]
```


## 複雑なRUN命令はバックスラッシュを使い複数行に分割

* ここでは、RUN命令の書き方に言及しています。
* RUNの最も一般的な利用例はapt-getコマンドではないでしょうか。

**RUN apt-get updaate と apt-get install を使用した実行例**

```dockerfile
RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo
```

* RUN命令でapt-get updateだけを使うとキャッシュ問題を引き起こし、その後のapt-get install命令が失敗現象

=> これはイメージを構築すると、全てのレイヤがDockerにキャッシュされることが起因しています。

動きとしてはDockerが初めからファイルを読み込み、命令の変更を認識すると、前のステップで作成したキャッシュを再利用します。
そのため、apt-get updateは実行されず、キャッシュされたバージョンを使ってしまうのです。

**なので、最新バージョンを使用したい場合は、apt-get install -yを使用します。**

```dockerfile
RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo=1.3.*
```



## コンテナ実行時の初期設定の指定方法

* ここでは、コンテナ実行時の初期設定に使用する命令CMD命令について言及しています。
* CMD命令は３つの形式があります。

```dockerfile
CMD ["実行ファイル","パラメータ1","パラメータ2"]
CMD ["パラメータ1", "パラメータ2"] （ ENTRYPOIN 命令に対するデフォルトのパラメータとして扱う）
CMD コマンド パラメータ1 パラメータ2 （シェル形式）
```

* ただ、ベストプラクティスとしては、この３つの形式の最初の書き方を推奨しています。
* 例えば、イメージがApache、Rails等だった時に、CMD ["apache2","-DFOREGROUND"]のようにすべきということです。

## コンテナが接続用にリッスンするポートは、よく使われるものにする

* ここでは、EXPOSE命令の書き方に言及しています。
* EXPOSE命令とは、コンテナの実行時、指定したネットワーク・ポートをコンテナがリッスンするよう設定するものです。
* ベストプラクティスとしては、例えば、Apacheウェブ・サーバのイメージを使用していた場合はEXPOSE 80を使ったり、MongoDBを含むイメージを使用していた場合はEXPOSE 27017を使うというような形です。

## ADDとCOPYならCOPYを使おう

* ADD命令は、追加したいファイル、ディレクトリ、リモートファイルのURLを `<追加元>` に指定すると、これらをイメージのファイルシステム上のパス `<追加先>` に追加するものです。

* COPY命令は、追加したいファイル、ディレクトリを `<コピー元>` に指定すると、これらをイメージのファイルシステム上のパス `<コピー先>` に追加します。

### ADD命令

```dockerfile
ADD [--chown=<ユーザ>:<グループ>] <追加元>... <追加先>
ADD [--chown=<ユーザ>:<グループ>] ["<追加元>",... "<追加先>"]
```

### COPY命令

```dockerfile
COPY [--chown=<ユーザ>:<グループ>] <コピー元>... <コピー先>
COPY [--chown=<ユーザ>:<グループ>] ["<コピー元>",... "<コピー先>"]
```

* ADDとCOPYは機能的に似ているのですが、なるべくならCOPYを使おうというのがベストプラクティスになります。
* **COPYはローカルファイルをコンテナの中にコピーするという基本的な機能しかサポートしていません。**
* 一方のADDは複数の機能（ローカル上でのtar展開や、リモートURLのサポート）を持っており、ADDだけ見た時何をしているのか分かりづらいです。
* なので、COPYを使おうという訳です。


**じゃあ、ADDはいつ使うのかとなるのですが、ADDのベストな使い方はローカルのtarファイルをイメージに自動展開するような使い方をする時です。**

例

```dockerfile
ADD rootfs.tar.xz /
```


## リモートURL上のパッケージを取得には、curlやwgetを使う

* 例えば、リモートURL上のパッケージを取得するといったケースがあると思います。
* この場合は、ADD命令を使用するのではなく、curlやwgetを使うようにします。

=> なぜならば、展開後に不要となったファイルを削除することでき、イメージに余分なレイヤを増やさないためです。


ダメな例

```dockerfile
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
 RUN make -C /usr/src/things all
```

良い例

```dockerfile
RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all
```
## サービスは特権ユーザで実行しない

* ここでは、サービスは特権ユーザで実行せずに、USERを使い**非rootユーザ**で実行することを推奨しています。
* 利用するにはDockerfileでユーザとグループをRUN groupadd -r postgres && useradd -r -g postgres postgresのように作成します。

## 作業ディレクトリにはWORKDIRからの絶対パスを使う

* RUN、CMD、ENTRYPOINT、COPY、ADD命令の処理時に作業ディレクトリを指定する際にWORKDIR命令を使用します。
* ここでのベストプラクティスとしては、何かしらの命令時にはWORKDIRからの絶対パスを使うべきといっています。

=> どういうことかというと、例えば、WORKDIRをRUN cd ... && 何らかの処理のようにしてしまうと、その分命令が複雑になりますし読みにくくなってしまうからです。

---
* **今回紹介したのは、Dockerfileのベストプラクティスの中でもDockerfileの書き方に注目した項目のみをピックアップしたものです。** 
* より詳しく知りたい場合はDockerのドキュメントを一読することをオススメいたします。

# hadolint

* hadolintとは、Dockerfileの静的解析を行なってくれるlintツールです。
* hadolintを使用するとdockerfileのベストプラクティスに従っているかをチェックすることができます。
* また、VS Codeのエクステンションも存在します。

https://github.com/hadolint/hadolint


## 使用方法

* CLIでの利用については、brew経由でinstallできる。

```sh
brew install hadolint
```

* hadolintの実行コマンドは下記で行えます。

```sh
hadolint 対象のDockerfile
```

実行結果
![スクリーンショット 2022-06-19 16.26.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/4d319a7b-0bd4-3ee6-1aef-e5588375328e.png)


### VS Codeのエクステンション

* エクステンションを使用するには、hadolintのCLIのinstallも必要となります。

![スクリーンショット 2022-06-19 14.20.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/5e3a67e0-cf48-2496-d9bf-a5ff206dcef3.png)

* エクステンションのカスタマイズもできます。

```json
{
  "hadolintPath": "hadolint",
  "cliOptions": ["--no-color", "--ignore", "DL3000"],
  "maxNumberOfProblems": 100,
  "outputLevel": "warning"
}
```

実行結果

![スクリーンショット 2022-06-19 16.26.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/cd645a8e-d1c2-8159-831a-2515f305db5b.png)

# 参考

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

https://github.com/hadolint/hadolint