DockerfileのCMDとENTRYPOINTを改めて解説する

# TL;DR

dockerで、コンテナ内で実行するプロセスを指定してのコンテナ起動方法は以下のとおり。

- `docker run <コンテナ指定> <プロセス指定> [ <プロセスに与える引数指定> ]`

docker runで、起動するプロセスを毎回指定するのが面倒で、決まった特定のプロセスを指定したいなら、以下のどれかを選択する。

- (ENTRYPOINTを指定しない場合)
   - DockerfileのCMD項目でプロセスおよびそれへの引数を指定する。この場合、`docker run <コンテナ指定> [RET]` のようにプロセス指定を省略して起動したときに、CMD項目で指定した内容が起動するプロセスのおよびそれへの引数の指定となる。

```
   docker run <コンテナ指定> <プロセス指定> [ <プロセスに与える引数指定> ]
                            <----------CMDで省略値を与えられる---------> 
```

- (ENTRYPOINTを指定する場合)
   - DockerfileのENTRYPOINT項目で決まった特定のプロセスを指定する。この場合、それ以外のプロセスは起動できなくなる。(もっとも/bin/shなどがENTRYPOINTで指定されているなら、/bin/shは任意のプロセスを起動する機能があるので、引数の与え型によっては任意のプロセスを起動できる。また、--entrypoint=引数で上書き指定ができる)。
      - ENTRYPOINTで指定したプロセスには次のように追加引数をあたえて起動することができる。`docker run <コンテナ指定> <追加引数指定> RET`
      - `docker run <コンテナ指定> RET`のように追加引数を指定しなかったときのデフォルトの追加引数をCMD項目から与えることができる。

```
   docker run <コンテナ指定> [ <プロセスに与える追加引数指定> ]
                            <--CMDで省略値を与えられる------> 
   #プロセス指定はENTRYPOINTで別途与えられている。
```

# なぜこの文章を書いたか

Dockerfileの設定項目にはENTRYPOINTとCMDという項目があるが、これらが対になる意味をもつかのような説明や、「CMD とENTRYPOINT」のように、同列で比較対照する記事を良く見る。

しかし両者は意味レベルが異なる存在であり、そのことを理解しないと良くわからないことになる(自分はなった)。また、CMDについてはもともとのDocker仕様としても、歴史的経緯っぽくてすっきりしないところがある。なので、整理してみた。

結論だけ書いておくと、「ENTRYPOINTの指定」に対になるのは、「runコマンドで実行プロセスを指定する」方法であり、CMDの指定ではない。CMDは両者に対して適用可能であり、それぞれ別の意味がある。詳しくは本文参照のこと。

# はじめに

CMDとENTRYPOINTは、いずれもDockerfileの設定項目であり、コンテナを「実行可能コンテナ」として使用する場合の設定。
「実行可能コンテナ」とは、他のコンテナのベースイメージとして利用するのではなく、```docker run```で直接実行することを目的としたコンテナイメージのこと。(もっとも、コンテナは実行可能かつベースイメージとなることもできるので、この2つの種別は排他的ではない。CMDとENTRYPOINTを持つイメージを元にしてビルドした場合の動作については、別途試す)。

# コンテナの実行と、起動プロセスの指定

構築されたDockerイメージに対して、コンテナを生成し、その中で **何か1つのプロセスを起動・実行** することで、意味のある動作をさせることができる(サーバとして継続的に実行したり、コマンドラインから1ショットで処理をするなど)。

コンテナ内で起動する(唯一の)プロセスの指定方法には以下の2つがある。

1. docker run の後にコマンドを記述
    * run時にイメージ内の任意のコマンドからプロセスを実行できる、自由度が高いコンテナ
    * 「docker run コマンド指定(+引数)」でプロセスを起動する
2. Dockerfile内のENTRYPOINT項目で指定
    * あらかじめビルド時に起動するプロセスが特定され、run時にはそのプロセスに対する引数だけが指定できる自由度の低いコンテナ
    * この場合でも実行するにはdocker runでイメージを起動する
    * この場合にdocker runの後に指定するのはプロセス指定ではなく、ENTRYPOINTで指定されるプロセスに与える引数

これら2つの指定方法は基本的に排他[^1]である。つまり、ENTRYPOINTを指定したときは、runの後の記述は起動するプロセスを特定する指示としては機能しないし、```docker run```で起動するプロセスを指定できるのは、DockerfileでENDTRYPOINTを指定せずに構築されたコンテナに対してのみである。

ENTRYPOINTでの指定は、ビルド時に確定してイメージに封入されてしまうことも留意。(必要ならrun --entrypoint=""で上書きすることもできるとのこと)

[^1]: よく考えると、実はENTRYPOINT項目を設定しなかった場合のデフォルト値が/bin/sh -cだと言う話で両者を説明しつくせる気がしてきた。
## ENTRYPOINTの意味

ENTRYPOINTは、前項「2.」の方法において、実行可能コンテナを利用する際の特定の「主たるプロセス」を起動するコマンドの指定である。「任意のコマンド」でコンテナの内容物を利用できるようにしたい場合、ENTRYPOINTを指定する必要はないし、指定すると任意のコマンドは実行できなくなる(run --entrypoint=""で上書すればできるが)。

ENTRYPOINTの良くある用例として、シェル(例えば```/bin/bash -c```など)を指定することもできる。この場合、「インタラクティブシェルから様々な操作をしたり-cオプションで任意のコマンドを実行を指定して起動プロセスとして実行できる、任意のコマンドを実行できるコンテナ」になる。たとえば、```docker run <コンテナ指定> --rm -it '(cd /app; ls -la)'```など。

シェル以外の「主たるプロセス」の起動を指定する用例としては、例えば、「gitのコンテナ」を作る場合には、```/usr/bin/git```をENTRYPOINTに固定登録する、などである。その際には、```docker run```コマンド実行時の引数もしくはCMD指定によって、ENTRYPOINTで指定したプログラムに引数を追加して起動することができる。たとえば、```docker run <コンテナ指定> log```で```/usr/bin/git log```を実行するなどである。このようにENTRYPOINTで指定したプログラムにrun時に追加する引数を、本文書では「追加引数」と呼ぶ(一般的ではない)。

# CMD指定の2つの意味

結局のところ、CMD指定は全く異なる2つの異なる意味をもつ。

1つ目の意味は、前項「1.」の方法(docker runでコマンドを指定する)において、「docker run」で実際のコマンドを何も指定しなかったとき **実行するコマンド(と引数)のデフォルト値**である。

```sh
# ENTRYPOINT項目が指定されていないとして
  docker run <コンテナ指定> ls -la [enter] # 「ls -la」がコンテナ内で実行される
  docker run <コンテナ指定> [enter]  # <この場合に実行される「コマンドと引数」をCMD項目に設定する>
```

2つ目の意味は、前項「2.」の方法(DockerfileのENTRYPOINT項目でコマンドを指定する)において、ENTRYPOINTに指定したコマンドの**追加引数**の、コマンドラインから指定しなかった場合の**デフォルト値**である。(「CMD」だからコマンドかと思うが、この場合はそうではない。引数なのである。ここがわかりにくい!)

```sh
# ENTRYPOINT項目に["/usr/bin/git"]が指定されているとして
  docker run <コンテナ指定> status[enter]  # 「/bin/git status」がコンテナ内で実行される
  docker run <コンテナ指定> [enter]  # <この場合に実行される/bin/gitへの「引数」をCMD項目に設定する>
```

いずれの場合でも、CMDはrunの後に続ける引数のデフォルト値指定であると言える。

## ENTRYPOINTと追加引数

以上について、いくつかの例を通じて見ていく。

### 追加引数をdocker runコマンドの引数から与える

```
ENTRYPOINT: /usr/bin/git
```

して

```
docker run <コンテナ指定> checkout
```
とすれば、ENTRYPOINT指定に追加引数として「checkout」が追加されて、run時のコンテナ内でのプロセス起動のためのコマンドラインとしては「```/usr/bin/git checkout```」が実行される。

### 追加引数のデフォルト値をCMDで与える

```
ENTRYPOINT: /usr/bin/git
CMD: ["status"]
```

して

```
docker run <コンテナ指定>
```
とすれば、コンテナ内でのプロセス起動のためのコマンドラインとしては、追加引数のデフォルト値として```CMD: ["status"]```が使用されて、「```git status```」が実行される。
CMDはあくまで「```docker run```↵」で追加引数が省略されたときのデフォルト値の指定なので、

```
docker run <コンテナ指定> checkout
```

のように、明示的にrunコマンドで追加引数を与えた時には(```CMD: ["status"]```の指定は無視されて)、run時のコンテナ内でのプロセス起動のためのコマンドラインとしては「```/usr/bin/git checkout```」が実行される。

# 文字列指定と配列指定

前項では、CMD項目は、```CMD: "status"```のような**空白区切り文字列形式**ではなく、```CMD: ["status"]```のような**配列形式**で設定していた。この理由を説明する。

ENTRYPOINT、CMDともに、「空白区切りの文字列」で指定すると「```/bin/sh -c```」が付与されてシェル経由、「文字列の配列」で指定するとexecシステムコールがプロセスの起動に使用される。
CMDに関して、「```/bin/sh -c```」の付与は、ENTRYPOINTの「追加引数」として使用される場合も区別されずに適用され、「["/bin/sh","-c"]」が引数に含まれるようになる。この動作は意図しないものである可能性が高いので、一般には、ENTRYPOINTに対するCMDは文字列の配列形式で指定することが多いだろう。(このへんでも、CMDの仕様は、つきつめて考え抜かれている、というより、機能拡張の歴史的経緯に引きずられてるっぽい感じがするぜぇー)

# まとめ

- 実行可能コンテナ内で起動するプロセスを指定する方法は以下の1,2のいずれか
  1. docker runコマンドの一部で指定
      - ```docker run <コンテナ指定> XXX YYY...```↵の形式
          - 例: ```docker run <コンテナ指定> /bin/sh -c echo hoge```↵
      - XXXは任意のプロセスを起動するコマンド
          - YYY...はそのコマンドラインに与える引数
          - 「```docker run <コンテナ指定>```↵」のみで「XXX YYY...」を省略した場合の「XXX YYY...」部分のデフォルト値をDockerfileのCMD項目で与えることができる
          - 例： 
                ```CMD: /bin/sh -c echo hoge``` <br /> のとき、「```docker run <コンテナ指定>```↵」の実行は先述の<br/>```docker run <コンテナ指定> /bin/sh echo hoge```↵<br/>と同じ。
  2. ENTRYPOINTで指定
      - ENTRYPOINT: XXX YYY...の形式
          - 例: ```ENTRYPOINT: /usr/bin/git```
      - XXXは固定の主たるプロセスを起動するコマンド(/usr/bin/git)
          - YYY...はそのコマンドに与える引数(もしあれば)
      - docker run ZZZで、追加引数を与えることができる
          - 例: ```docker run <コンテナ指定> --version```↵
          - つまり「XXX YYY... ZZZ」というコマンドラインで起動できる
              - 上の例の場合： ```/usr/bin/git --version```の「--version」が追加引数ZZZ
          - 「```docker run <コンテナ指定>```↵」のみで「ZZZ」を省略してrunした場合の、追加的引数ZZZのデフォルト値をCMDで与えることができる
              - 例: ```CMD: status```<br/> のとき、「```docker run <コンテナ指定>```↵」で```/usr/bin/git status```が実行される。

# docker-composeについて(追記)

Docker Composeのdocker-compose.ymlファイルでは、entrypointはentrypointのまま、CMDはcommand項目に対応する。

- http://docs.docker.jp/compose/compose-file.html#entrypoint
- http://docs.docker.jp/compose/compose-file.html#command