Docker初心者が「なんとなく理解した」レベルになるまでの記事まとめ

「Docker？とりあえずdocker pullしてdocker runすればいいんだよ」ってドヤろうとしてインストールしてみたものの「おいおい何だよこれ……分からねぇ……おれのコンテナがこんな難しいわけがない」とかラノベタイトルみたいなことを言いたくなった人向け。

# Dockerの概念を知っておく
[【図解】Dockerの全体像を理解する -前編-](https://qiita.com/etaroid/items/b1024c7d200a75b992fc)
[【図解】Dockerの全体像を理解する -中編-](https://qiita.com/etaroid/items/88ec3a0e2d80d7cdf87a)
ぶっちゃけ、これを読めば「なんとなく理解した」レベルにはなる気がする。

[Dockerにおけるコンテナのライフサイクル](http://enakai00.hatenablog.com/entry/20140628/1403933390)
「とりあえずdocker run だけやっとけ、だと思ったらstopとかstartとかrmとか全然分からん！」って途方に暮れてる人向け。分かりやすい図解。

# で、Dockerって結局どんな感じで動いてるの？
根本原理を理解していれば当たり前の話なのだけど、「とりあえずdocker runだけやっとけ」って言っているとなかなかたどり着けない話。
結局、**Dockerの上で動く(＝コンテナイメージに入っている)のはOS＋ミドルウェア＋ソフトウェア**なのである。nginxやらtomcatやらをdocker runすると、単独のプロセスを上げている気分になってしまうのだけど、そうではなくて前述のソフトウェアスタックをセットで起動している。だから、どんな環境に持って行ってもホストのOSに関係なく同じ動作をさせることができるという可搬性を発揮するのだ。

例えば、

```shell-session
$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2"
ID="amzn"
ID_LIKE="centos rhel fedora"
VERSION_ID="2"
PRETTY_NAME="Amazon Linux 2"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
HOME_URL="https://amazonlinux.com/"
$ java -version
java version "1.7.0_241"
OpenJDK Runtime Environment (rhel-2.6.20.0.amzn2.0.2-x86_64 u241-b01)
OpenJDK 64-Bit Server VM (build 24.241-b01, mixed mode)
$
```
な環境でTomcatのDockerコンテナを起動したりした場合、

```shell-session
$ docker pull tomcat:latest
latest: Pulling from library/tomcat
～(中略)～
Status: Downloaded newer image for tomcat:latest
$ docker run -d --name tomcat tomcat:latest
d5c235c7b6538bf1cb5a7c587e1bff6a0825763242c8e5f60a465838c86ca9e7
$ docker exec -it tomcat bash
root@d5c235c7b653:/usr/local/tomcat# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@d5c235c7b653:/usr/local/tomcat# java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
root@d5c235c7b653:/usr/local/tomcat#
```
な感じで、Debianの上でJavaの1.8.0が動いていたりする。
同じように、nginxの中身を覗いてみると、

```shell-session
$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
～(中略)～
Digest: sha256:2539d4344dd18e1df02be842ffc435f8e1f699cfc55516e2cf2cb16b7a9aea0b
Status: Downloaded newer image for nginx:latest
$ docker run -d --name nginx nginx
f0bc10b6e7eb190b80b9b6c197aed24af99c0bd62a1f9980d4a40d580f818993
$ docker exec -it nginx bash
root@f0bc10b6e7eb:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@f0bc10b6e7eb:/# java -version
bash: java: command not found
root@f0bc10b6e7eb:/#
```
といった具合に、ネイティブで動くnginxのコンテナにはjavaが含まれていない。
小細工は必要だけど、nginx on CentOSなコンテナも存在するようだ。

Dockerで調べものしているとたまに出てくるAlpineは、コンテナ向けの軽量Linuxのこと。OpenJDKをAddすることもできるようなので、例えばSpringBootで作ったjarファイルを動かすには、Alpine Linux＋OpenJDKなコンテナに乗せるのが良いように思える。

# コマンドあれこれ
[よく使うDockerコマンド](https://qiita.com/tera_shin/items/8a43e904bd15990d3129)
[dockerチートシート](https://qiita.com/kouma1990/items/9b2c56b379ff58d67090)
困ったらこの辺見る。よく使うコマンドオプションは以下。docker stop/restart/rmは基本的にコンテナ名をオプションで指定するくらいか。

- ```docker run```
    - ```-d```: バックグラウンド起動
    - ```--name```: コンテナ名をつける。つけないと、勝手にデフォルトでpeaceful_pikeとかromantic_lamarrとかの謎の名前がふられる。docker stopとかdocker rmとかするときに、いちいちコンテナIDをコピペするのとか面倒すぎるので名前を付けておく方が良い
    - ```-it```: ```-i```はコンテナの標準入力を開く、```-t```はttyを使うの意味。要は、コンテナに潜り込んでbashとか起動してあれこれやる時に使う
    - ```-e```,```--env```: 環境変数を設定する。```-e```はホストOSの環境変数をそのまま渡す、```--env```は「環境変数名=値」で任意の環境変数設定が可能。他にも色々な方法で環境変数は渡せる。詳細は[この記事](https://qiita.com/KEINOS/items/518610bc2fdf5999acf2)を参照
    - ```-p```: 後述のポートフォワード参照
- ```docker exec```: 起動中のコンテナにアタッチする。```-it [コンテナ名] bash```とかと組み合わせて使う

# コンテナをアップデートする
「とりあえずdocker run -vでホストのディレクトリをマウントした状態で起動すればconfとかも余裕でコンテナに反映できるからな。よし、docker commit……って反映されてないよ！」ということが起きるので、docker runした後にdocker cpで必要なファイルをコンテナに持っていってから、

```shell
$ docker commit [起動中のコンテナ名] [イメージ名]:[タグ]
```

でコンテナイメージの更新ができる。あまりコンテナイメージの更新方法がググっても出てこなかったので、この方法がベストプラクティスかどうかは分からない。Dockerfileにファイルコピーまで含めて記述してビルドする方が簡単な気がする。

AWSなら、この後

```shell
$ docker tag [イメージ名]:[タグ] xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/[イメージ名]:[タグ]
$ docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/[イメージ名]:[タグ]
```

すればECRに反映することが可能(もちろん、ログインしてから)。タグは指定しないとlatestになる。上書きされたlatestは\<untagged\>な状態になる。コンテナ割り当てに\<untagged\>なイメージのURIが使えるかは試していないので不明。

# ポートフォワード
「くそっ！docker psしたらちゃんとポート待ち受けしてるように見えるのに繋がらないぞ！」ってなった人は↓これを読んでおく。あなたがdocker psの出力で見ているそのポートはコンテナの中身のポートでしかないのである。
[Dockerでポートフォワーディング解説](https://qiita.com/tatsuo-iriyama/items/e4bf2404411343116e3e)

# ホストとコンテナ間でのファイルのやりとり
「docker pullしてきたはいいんだけど、configファイルとか全部コンテナイメージの中に入っちゃってるからどうにもならないんだけど！docker execでコンテナ内に入って直接編集しようとしてもvi起動しないぞ！」って混乱している人は↓こちら。

[Dockerでホストとコンテナ間でのファイルコピー](https://qiita.com/gologo13/items/7e4e404af80377b48fd5)

ちなみに、docker stopでコンテナ停止している状態でもコピー可能。ディレクトリコピーも可能。便利。
ただし、docker cpでホストにコピって来てから編集してもう一度docker cpで書き戻してからdocker startしても、ポートバインドの設定とかは変わらなかった。docker run -v でボリューム指定して再起動するしかないのか。