Dockerのライフサイクルがよくわからなかった

Docker、コンテナやらイメージやらという用語が混同してしまったり、データや構成がどこで保持されてどこで破棄されるのかといったライフサイクルが理解できていなかったのでまとめた。

イメージとコンテナ
=====
イメージはコンテナのもととなるデータ。[Docker Hub](https://hub.docker.com/) などのコンテナレジストリに公開されているイメージからダウンロードして使ったりDockerfileからbuildしたりする。一方のコンテナは、実際にイメージから起動したマシンを指す。当然ながら1つのイメージから複数のコンテナが起動できる。

Docker Hubから任意のイメージを取得（`pull`）し、内部構成をカスタマイズして保存 （`run` && `commit` もしくは `Dockerfile` を書いて `build`）、そのイメージからコンテナを起動（`run`）して利用、要らなくなったらコンテナを停止（`stop`）して破棄（`rm`）というのが一様のライフサイクルになる。まとめると、

* イメージはマシンではないため、状態は持たない。
* コンテナはマシンとして扱う。状態を持ち、停止（stop）しても状態は保持される。
* コンテナは明示的に破棄（rm）しない限り、停止しても存在し続ける（それなりにちゃんと消さないとどんどん容量を食う）。`docker ps`コマンドはオプション無しだと起動中のコンテナしか表示しないため、停止したコンテナが蓄積されていることを見逃しがち。

なお、コンテナは基本的に使い捨てるものであり、永続させたいデータを保存してはならない。設定ファイルのようなものであれば、コンテナ外にあるファイルをマウントさせて使うべきだし、アプリケーションのデータであれば外部のデータベースに保存する。ローカルでの開発用途の利用であれば、 `stop` したコンテナを再利用するケースはあるかもしれないが、 production においては例えばデプロイの際にはコンテナが削除されて新たなものに入れ替えられる形となるので、「コンテナの中のデータは消えるモノ」という意識は常にあったほうがよい。

以下、各ライフサイクルでのコマンドを見ていく。

イメージの取得
-----
```bash
$ docker search <keyword>
```
イメージをDocker Hubで検索する。例えば任意のミドルウェアを導入済みのイメージなど、そのミドルウェア名から検索できる。Docker Hubには誰でもホストできてしまうので、信頼のおけるイメージ（企業の公式イメージ等）を使うようにした方がよい。

近年は Docker Hub における pull 回数の制限を契機に、 [Amazon ECR Public](https://gallery.ecr.aws/) のように Docker Hub 以外の選択肢も増えてきている。イメージはこのコマンドを用いず、ウェブ上で探したほうがよいかもしれない。

```bash
$ docker pull <image name>
```
イメージをダウンロードする。

```bash
$ docker images
```

ローカルに存在するイメージのリストを表示する。

```bash
$ docker rmi <repository:tag>
```
要らなくなったイメージを削除するる。

コンテナの起動
-----
```
$ docker run <repository:tag> <command>
```
コンテナを起動する。

コマンドについては、必ずしも任意のものを実行できるわけではない。 Docker image には `CMD` と `ENTRYPOINT` という2つの手段でデフォルトのコマンドが指定されている場合がある。

`CMD` の場合は、 `docker run` の際に渡したコマンドで上書きすることができ、実質任意のコマンドで起動ができる。 `ENTRYPOINT` の場合は、渡したコマンドは `ENTRYPOINT` で指定されたコマンドの引数として処理される。この場合は `--entrypoint` オプションによって、任意のコマンドでの上書きが可能になる。ただし、イメージの作成者が ENETRYPOINT を指定している以上、任意のコマンド実行は想定されていない可能性もある。

他、よく使うオプションをいくつか。例えば `docker run hoge:latest -it /bin/bash` とすると、コンテナが起動して、その内部の端末が起動し、 bash が使える状態になる。

`-n` コンテナの名前
`-t` 擬似端末（pseudo-tty）
`-i` インタラクティブ実行
`-b` バックグラウンド
`-P` ポートフォワード（自動割り当てなので、任意のポート番号を指定したい場合は`-p 0000`）
`-v` ローカルディレクトリをマウント。絶対パスで\<local path\>:\<container path\>の形で指定。

```bash
$ docker ps
```
起動しているコンテナの一覧を表示する。

`-l` 最後に起動したコンテナを表示
`-a` 停止しているコンテナを含め表示
`-q` コンテナIDだけを出力

コンテナの運用
-----

```bash
$ docker stop
$ docker start
$ docker restart
```
それぞれコンテナの停止、開始、再起動。一度停止したら内部データは破棄されるかと思ってたのだが、そうじゃないっぽく、破棄されるまでコンテナに加えた変更は保持される。`docker run`で渡したコマンドの実行が終わればコンテナは終了するが、`docker start`で再度起動できる。

```bash
$ docker attach
```
アタッチ（コンテナ内部に入る）。デタッチはC-p C-q（`exit`するとコンテナが終了する）。

```bash
$ docker logs
```
バックグランド実行しているコンテナの標準出力を表示。`-f`オプションで`tail -f`と同様の効果。

```bash
$ docker inspect
```
dockerコンテナの情報をjsonで出力。

コンテナの破棄
-----
前述のとおり、コンテナの破棄は明示的なコマンド実行が必要。

```bash
$ docker rm
```
コンテナの削除。`rmi`がイメージ削除でちょっと紛らわしい。

イメージの作成
-----
イメージ作成の手段は主に2つ。

Dockerコンテナを起動して構成を変更した後、その状態をイメージとして保存したいとなれば、`commit`コマンドを使う。

```bash
$ docker commit -m <commit message> -a <user> <image name/ID> <image repository:tag>
```
ただ、実際にはコンテナの入って手作業で構成を変更して`commit`を行うより、構成をコード化したDockerfileを作成し、`build`コマンドでイメージ化することの方が多いし、再現性も高くなるのでそうするべき。

```bash
$ docker build -t <image repository:tag> <Dockerfile path>
```


その他tips
=====

dockerコマンドのsudoが面倒
-----
コマンド実行ユーザーを`docker`グループに追加すれば`sudo`要らなくなる。

`docker run`に複数コマンド渡したい
-----
`/bin/sh -c`の引数として渡せば良い。

```bash
$ docker run chroju/centos /bin/bash -c 'bundle install && bundle exec spec'
```

（参考）

* [Hello world in a container](https://docs.docker.com/engine/userguide/dockerizing/#a-daemonized-hello-world)
* [Why can't I cd to a directory with docker run? - Server Fault](http://serverfault.com/questions/634883/why-cant-i-cd-to-a-directory-with-docker-run)