【Docker】 `CMD`と`ENTRYPOINT`の違いについて理解した

## h
現在、本業でPythonをdockerで動かしています。
その中で、コンテナ起動時にpythonを実行する仕様にしている中で`CMD`と`ENTRYPOINT`の違いについて調べたのでまとめました。

## 基本概念
`docker run`でコンテナを起動するときに使用します。

| 指令 | 役割 | 上書き方法 |
|------|------|------------|
| CMD | デフォルトの引数定義 | `docker run`の引数で直接変更 |
| ENTRYPOINT | コンテナのメインプロセス定義 | `--entrypoint`フラグが必要 |

1つのDockerfile内で`CMD`,`ENTRYPOINT`をそれぞれ一度しか使用できないことに注意してください。
※複数記載した場合、最後に定義したものが実行されます。

## `CMD`と`ENTRYPOINT`は何が違うか
結論、CMDは柔軟な設定が可能で、ENTRYPOINTは必ず実行させたいコマンドが設定可能です。
故に、それぞれ単体で使用するケースと、二つを柔軟に組み合わせて使用するケースがあります。
以下にチートシートを作成したので、参考にしてみてください。

### 各コマンドの使い分け
| ケース                               | 推奨                   | 理由                    |
| --------------------------------- | -------------------- | --------------------- |
| デフォルトの引数だけ指定したい（簡単に上書きさせたい）       | `CMD` 単体             | 実行時の引数で簡単に変更可能        |
| 実行させたいコマンドを固定したい（常に実行させる）         | `ENTRYPOINT` 単体      | ユーザーが指定した引数が付け加えられるだけ |
| 実行コマンドを固定しつつ、引数はデフォルトを与えつつ変更も許したい | `ENTRYPOINT` + `CMD` | 実行ファイル＋その引数として動作する    |

### 上書きの挙動
| 使い方                | docker run時の上書き可否                   | 説明                |
| ------------------ | ----------------------------------- | ----------------- |
| CMDのみ              | `docker run イメージ名 新しい引数` で上書き可能     | 柔軟に変えたい時に便利       |
| ENTRYPOINTのみ       | `docker run`の引数はすべてENTRYPOINTの引数になる | コマンドは変わらない        |
| ENTRYPOINT + CMD   | CMD部分だけ上書き可能                        | コマンドは固定・引数だけ変えられる |
| ENTRYPOINTを変更したい場合 | `--entrypoint` を明示的に指定              | CMDだけでは変更不可       |

### 使い所
| シーン             | 使い方              | 理由                              |
| --------------- | ---------------- | ------------------------------- |
| デバッグや試行錯誤       | CMDのみ            | 柔軟性が高い。runの都度挙動を変えやすい           |
| スクリプト実行ツールとして配布 | ENTRYPOINTのみ     | 実行内容を強制できる（例：CLIツール）            |
| 初期化スクリプト＋引数指定   | ENTRYPOINT + CMD | ENTRYPOINTでスクリプト、CMDで引数をデフォルト指定 |

## ハンズオン環境
実際に再現してみました。
簡単なので、眺めるだけでもある程度理解できると思います。
もし、ハンズオン的に触ってみたい方は GitHub にソースがあります。
https://github.com/shunyonai1996/docker-cmd-entrypoint-demo

```bash
$ tree
.
├── article.md
├── Dockerfile-both
├── Dockerfile-cmd
└── Dockerfile-entrypoint
```

## `CMD`
一応概要から。。。
`CMD`はコンテナを実行したときの「デフォルトのコマンドや引数」を指定するものです。
特徴として、`docker run`実行時、引数を上書きできます。

> CMD の主な目的は、コンテナ実行時のデフォルト（初期設定）を指定するためです 。
https://docs.docker.jp/engine/reference/builder.html#cmd

`Dockerfile-cmd`
```dockerfile
FROM alpine:latest
CMD ["echo", "Hello CMD"]
```

```bash
# build
$ docker build -t cmd-demo -f Dockerfile-cmd .
[+] Building 2.0s (5/5) FINISHED

# run
$ docker run cmd-demo
Hello CMD

# 実行時、コマンドを上書き
$ docker run cmd-demo echo "Overridden CMD"
Overridden CMD
```

## `ENTRYPOINT`
こちらも概要から。
`ENTRYPOINT`はコンテナを「実行ファイル」として動かすため、常に実行されるコマンドを指定します。
`docker run`の引数はENTRYPOINTへの引数として追加される。
ENTRYPOINT自体を変えたい場合は--entrypointオプションが必要。

ちなみに「コンテナを実行ファイルとして動かす」という表現が当初よくわからなかったので、補足します（飛ばしてもいいです）。
難しく考えなくて良くて、`Dockerfile`の`ENTRYPOINT`に`["echo"]`と記載し、`docker run Hello`を実行したとしましょう。
```bash
docker run my-image Hello World
# => Hello World
```
上記は以下と全く同じ意味合いです。
```bash
echo Hello World
```
つまり、`docker run コンテナ名 引数`という形で、あたかもコマンドに引数を渡すようなインターフェースに見えるということを指しています。

> ENTRYPOINT は、コンテナを 実行ファイルexecutable として処理するように設定できます。

https://docs.docker.jp/engine/reference/builder.html#entrypoint


`Dockerfile-entrypoint`
```dockerfile
FROM alpine:latest
ENTRYPOINT ["echo", "Hello ENTRYPOINT"]
```

```bash
# build
$ docker build -t entrypoint-demo -f Dockerfile-entrypoint .
[+] Building 2.1s (5/5) FINISHED

# run
$ docker run entrypoint-demo
Hello ENTRYPOINT

# 実行時、コマンドを上書き
$ docker run --entrypoint="" entrypoint-demo echo "Overridden ENTRYPOINT"
Overridden ENTRYPOINT
```

## `CMD` & `ENTRYPOINT`
ENTRYPOINTが「実行するコマンド」、CMDが「そのコマンドのデフォルト引数」になる。

`Dockerfile-both`
```dockerfile
FROM alpine:latest
ENTRYPOINT ["echo"]
CMD ["Hello ENTRYPOINT & CMD"]
```


```bash
# build
$ docker build -t both-demo -f Dockerfile-both .
[+] Building 0.9s (5/5) FINISHED

# run
$ docker run both-demo
Hello ENTRYPOINT & CMD

# 実行時、引数を変更
$ docker run both-demo "Custom Argument"
Custom Argument
```