雑に知ってしまったDockerを知り直す ~Dockerfile編~

## おさらい
第3回です。
前回はnodeを実行できるコンテナを用意して、そこに`git clone`でアプリケーションを持ち込んで実行できるようにした。
下書きでずっと残してたのを公開。

## 今回やりたいこと
1. コンテナ起動/アプリケーションをclone/必要なモジュールをインストール/アプリケーション起動 をコマンド1発でできるようにする
2. 1発で起動できるようにしたコンテナを、他の人も1発で起動できるようにする。


## 調べる
要はベースとなるイメージがあって、更にそれに+αしたイメージを作るのかなとなんとなく思いました。
「Docker イメージ 作り方」で調べてみるとどうも2種類のアプローチがありそう。

- 1つ目
前回コンテナに入って`git clone`やら`npm install`やら実行しましたが、このコンテナを基にイメージを作成。作成したイメージをDocker Hubにアップロードする。Docker Hubに置いたイメージを他の人がpullする。

- 2つ目
イメージの設計図ファイル(Dockerfile)を作成して、それを他の人に渡す。
Dockerfileからイメージを作成する。

せっかくなので両方やってみます。

## 作成済みのコンテナからイメージを作ってDocker Hubへ登録する
おさらいがてらコンテナ作成してアプリケーションの起動してから、イメージを作成してみます。

```
[ホスト]
# node公式のイメージでコンテナ起動
$ docker run -itd -p 8080:3000  --name node-react node:12.16.0-buster

# コンテナの中に入る
$ docker exec -it node-react /bin/bash

[コンテナ]
# 適当なディレクトリへアプリケーションをclone
root@af16fea41c03:/# cd /usr/src/
root@af16fea41c03:/usr/src# git clone https://github.com/wol-827/first-step.git
root@af16fea41c03:/usr/src# cd first-step/

# React起動用のモジュールインストール
root@af16fea41c03:/usr/src/first-step# npm i

root@af16fea41c03:/usr/src/first-step# npm start

[ホスト]
# コンテナ停止
$ docker stop node-react

$ docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                            PORTS               NAMES
af16fea41c03        node:12.16.0-buster   "docker-entrypoint.s…"   6 minutes ago       Exited (137) About a minute ago                       node-react

# コンテナからイメージを作成
$ docker commit [コンテナ名] [お好みのイメージ名]
$ docker commit node-react image_node-react
sha256:6545cd03fcf1615f339f3176a25aa7eb28380ac2690f8eb34c0b2aa5f9825c2d

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
image_node-react    latest              6545cd03fcf1        26 seconds ago      1.06GB
node                12.16.0-buster      ce43ce61c1de        31 hours ago        882MB
```

`image_node-react `っていう名前のイメージが作成できた！
それにしてもサイズが大きいな・・・

作成したイメージを使って、my-imageという名前のコンテナを起動してみましょう。

```
[ホスト]
$ docker run -itd -p 8080:3000 --name my-image image_node-react
7116b515f71565b38cc86c399768ba43d918e7cea6b99a957dcd5a5121e46698

$ docker exec -it my-image /bin/bash

[コンテナ]
root@7116b515f715:/# cd /usr/src/first-step/
root@7116b515f715:/usr/src/first-step# npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278872/76f31a56-bd33-a5e8-456a-4b9badb58da2.png)
`git clone` と `npm install`をすっ飛ばすことができました。

が、しかし。今回は「コマンド1発でアプリケーションの起動まで」が目標。
これだと、コンテナに入ってアプリケーションがあるディレクトリまで移動して起動、というプロセスが残ってしまっている。。
以下のコマンドでコンテナ起動することで、初期ディレクトリは指定できたがそうじゃないんだよなぁ。。
`$ docker exec -it -w /usr/src/first-step my-image /bin/bash`

Dockerイメージは、環境は定義できるけどアプリケーションの起動まではやってくれない・・？
イメージの更新タイミング?みたいなものを把握できてないせいな気がする。
TODO: イメージの更新タイミング（？）みたいなものを調べる。あとcommitコマンドの動きも。

イメージの作成が上手くできなかったのでDocker Hubへのpushはまた別の機会に。
いったん`docker commit`を使うパターンはここまで。

## Dockerfileを作成する
そもそもDockerfileとはというのを調べてみたところ、コンテナの構成情報が記述されたファイルのことでした。
`$ docker build [Dockerfile]`で、Dockerfileに記述された通りにイメージを作成。
そして作成したイメージでコンテナ起動すると。

早速記述方法を調査。[日本語化されたリファレンス](http://docs.docker.jp/engine/reference/builder.html)を参考に書いていきます。
基本的には、`命令 引数`をいくつも書き並べてそれを上から実行していく、という流れみたいです。
コンテナの中に入って実行したコマンドを記述すれば良いハズ！

今回作ったものを基にコメント入れていきます。

```Dockerfile:Dockerfile
# ベースになるイメージをタグ付きで
FROM node:12.16.0-buster

# 作成したユーザ名(maintener)を記述 他にも色々ラベル付けできそう
LABEL maintainer=wol

# 作業ディレクトリを指定
WORKDIR /usr/src/app

# RUN: docker build時に実行するコマンド
RUN echo "=== build image ==="
RUN git clone https://github.com/wol-827/first-step.git .
RUN npm i

# CMD: docker run時に実行されるコマンド
CMD [ "npm", "start" ]
```

- FROM
ベースになるイメージを記述。
本来はlinux OSだけのシンプルなイメージを指定して、なるべく無駄ないイメージを目指すんでしょうが、今回は初めてなのでラクしてnodeのイメージを指定

- LABEL
buildするイメージに対してラベルをつける。key-value形式で何でも書けるみたいなので、案件によっては細かくラベル付けておくと管理がラクになるかも。
ちなみに公式ドキュメントにMAINTENERって命令もあったんですが、deprecatedだったので注意。こっちを使いましょう。

- WORKDIR
作業ディレクトリを指定する。そんなディレクトリなければ作ってくれる。
以降のRUNやCMDを基本的にこのディレクトリから行う。(自分で移動した場合は移動しっぱなし)

- RUN
イメージのbuild時に行うコマンドを記述。上から実行されていく。

- CMD
コンテナ起動ときに行うコマンドを記述。基本的にはアプリケーションとかサーバ起動を行うものになるハズ。
RUNと違って複数記述しても最後の一つが実行されるだけ。

他にも色々命令あったんですが、ひとまず今回はこれだけで出来そうなのでここまで。
TODO: 他の命令も見てみる。

## Dockerfileからイメージ作成 〜 コンテナ起動

```
# nodeのイメージのみ
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node                12.16.0-buster      ce43ce61c1de        3 days ago          882MB

# イメージをbuild
$ docker build [Dockerfileがあるディレクトリ]
-t イメージ名指定

$ docker build ./ -t dockerfile-image
Sending build context to Docker daemon  182.2MB
Step 1/7 : FROM node:12.16.0-buster
 ---> ce43ce61c1de
Step 2/7 : LABEL maintainer=wol
 ---> Using cache
 ---> 48452bf46eb7
Step 3/7 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 4a3c93571f61
Step 4/7 : RUN echo "=== build image ==="
 ---> Using cache
 ---> 882fdefc7439
Step 5/7 : RUN git clone https://github.com/wol-827/first-step.git .
 ---> Running in c7e52d0eb9ff
Cloning into '.'...
Removing intermediate container c7e52d0eb9ff
 ---> d4fd9e858fba
Step 6/7 : RUN npm i
 ---> Running in b0abff570fe1
Removing intermediate container b0abff570fe1
 ---> 35adcb5772be
Step 7/7 : CMD [ "npm", "start" ]
 ---> Running in 57c44837099c
Removing intermediate container 57c44837099c
 ---> 3b909a1f0824
Successfully built 3b909a1f0824
Successfully tagged dockerfile-image:latest

# イメージ確認
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
★ちゃんと出来てる
dockerfile-image    latest              3b909a1f0824        29 seconds ago      1.06GB 
node                12.16.0-buster      ce43ce61c1de        3 days ago          882MB

# buildしたイメージでコンテナ起動
$ docker run -p 8080:3000 --name node-react dockerfile-image
> sample-app@0.1.0 start /usr/src/app
> react-scripts start

Starting the development server...

Compiled successfully!

You can now view sample-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://172.17.0.2:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278872/5ecbb6b0-3a83-e241-2279-f11b4c13b7a4.png)
**出来た！**

作業としては、

1. Dockerfileを入手する
2. docker build
3. docker run

の簡単3ステップでかなり満足です！
build時のログを見てみると、RUNに渡したコマンドが上から実行されているのが分かりますね。
TODO: `Removing intermediate container ~`ってログが気になる。調べる

今回作ったDockerfileを試しにリポジトリに置いて、別PCでcloneしてコンテナ起動ちゃんとできました。ナイス。

## まとめ
自分の作成したイメージを他の人に渡す方法を2種類試しました。

1. docker commit
2. Dockerfile + docker build

個人的にはDockerfileを使った方法の方がしっくり来たかなぁと・・・
イメージそのものを作って渡してしまうと、どういう順番で何をして環境を構築しているのかよく分からないというのが正直な感想です。イメージの中身を覗く方法もそのうち調べてみます。

次はどうしようかな。
コンテナ間の通信かなぁ。先にアプリケーション作らないといかんな・・