Docker幼稚園

新しい端末を買うたびにDockerを導入しようとして、毎回苦戦する赤ちゃんなので、Docker幼稚園に入ります。

# Dockerコンテナって？

Docker Desktopを開くとこんな感じ。
![Docker Desktop Top](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/c9066db9-2f0a-780c-ab06-daf8c8fccd2d.png "Docker Desktop Top")
この左の
- Container
- Images
- Volumes

の関係をまずまとめる。

<dl>
    <dt>Container</dt>
    <dd>
仮想の動作環境（＝ひとつのサーバーみたいな感じ）。<br>
例えばPCのローカル環境にpython2とpython3を同居させてると、パッケージのインストールや動作時にどっちで動かしてるんだ…？ってなるときがある。<br>
このようにひとつの動作環境にいろいろソフトウェアを入れていく（＝環境が汚れる）と、お互いが思いもよらぬ形で影響してトラブる可能性がある。<br>
そこで仮想の動作環境（＝Container）を作り、それぞれ独立させてやることで無用なトラブルを防止することができる。</dd>
    <dt>Image</dt>
    <dd>
Containerの設計図、雛形。<br>
これを共有することで、誰もが同じ仮想環境を構築することができる。<br>
OSの違いや種々のライブラリの導入状況の違いを無視し、環境構築をすっ飛ばしてメインの開発から着手できるのはとてもメリット。<br>
そしてこのImageの共有プラットフォームがDocker Hub。<br>
</dd>
    <dt>Volume</dt>
    <dd>
複数のコンテナが共有できるデータ領域みたいなもの（まだ使っていないので曖昧）。<br>
後述する各ContainerがマウントするVolumeとは別なのでそこだけ注意。
    </dd>
</dl>

Containerは使い捨ての環境。
都度、作ったり消したり、起動したり停止したりするもの。
Imageはあくまで設計図なので、そのなかで作業したりはしない、ということだけまず理解する。
![Image, Container, Volume](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/64fa6da4-4e31-4efd-3d0e-2f654353d2cf.png "Image, Container, Volume")

# Dockerイメージってどこからくるの？

Imageは、Dockerfileというファイルをもとに作られる。
[公式Dockerfileリファレンス](https://docs.docker.jp/engine/reference/builder.html)に記載のサンプルを見てみよう。

```docker:Dockerfile
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
```

ひとつひとつ紐解いていく。
FROM句はDockerfileには必須の要素で、これから作るImageの**ベースとなるImage**を指定している。
":"以降は**Tag**と呼ばれ、Imageのバージョンのようなもの。

```docker:Dockerfile
# FROM <Image名>:<Tag>
FROM ubuntu:18.04
```

“ubuntu”はDocker Official Imageとして配布されているImage。
![Docker Hub](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/745e4447-2912-b128-fbe1-7eb6e2e76713.png "Docker Hub")

このubuntu ImageからContainerを作成すると、まっさらなubuntuの環境が作成される。
その環境をもとにして、少しカスタマイズしたImageを誰かが作る。
それをもとに、また別の誰かが少しカスタマイズしたImageを作る。
このようにImageが数珠つなぎになっているというアイデアは、まさしく「巨人の肩に乗る」発想だと思った。
![Dockerfile](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/78ce578a-0ae5-a5ef-0a1e-e6a790923d15.png "Dockerfile")

実際、Docker Desktopで”node” Imageを見てみると、”debian” Imageを出発点として、計4つのImageの上に”node” Imageが成り立っていることが分かる。
![Image Hierarchy](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/ddad17e9-c01c-ff99-7dd1-09fb74d4aece.png "Image Hierarchy")

***

##### 余談
ちなみに本当に空っぽの状態からImageを作成したい場合は、次のように指定する。
```docker:Dockerfile
# 特定のImageを指定せず、
# まっさらな状態からImageを作成する
FROM scratch
```

***

残りのコマンドは簡単なので、一気に。
```docker:Dockerfile
# カレントディレクトリの中身を、コンテナ内の/appディレクトリにコピー
COPY . /app
# コンテナ内で、/appに対してmakeコマンドを実行
RUN make /app
# コンテナ内の/app/app.pyを実行
CMD python /app/app.py
```
パッと見、RUNとCMDってどちらもシェル形式でコマンド実行しているだけで、何が違うの？ってなると思う。
[Dockerfileの書き方, 利用する命令, 作成手順](https://www.wakuwakubank.com/posts/270-docker-build-image/#index_id1)を参考にすると、

<dl>
    <dt>RUN</dt>
    <dd>
DockerfileからImageを作成する際に実行されるコマンド。<br>
Dockerfile内に複数記載可能。
    </dd>
    <dt>CMD</dt>
    <dd>
ImageからContainerを起動する際に実行されるコマンド。<br>
Dockerfile内に1度しか記載できない。<br>
    </dd>
</dl>
というざっくりした違いがある。


つまり、作成する環境にパッケージをいろいろインストールしておきたい場合はRUN、
コンテナ立ち上げ時にMySQLを起動しておきたい、というような場合はCMD、
ということになる。

CMDと同じくDockerfile内に一度しか記載できず、コンテナ起動時に実行されるコマンドを指定するENTRYPOINTというものがあるが、ここは幼稚園なのでいったん割愛。

参考になるサイト
[(Docker) CMDとENTRYPOINTの「役割」と「違い」を解説](https://hara-chan.com/it/infrastructure/docker-cmd-entrypoint-difference/)

# Githubから落としてきたら、docker-compose.ymlがついてきたけどこれは？

docker-compose.ymlはざっくりいうと、ImageからのContainerの起動、あるいはDockerfileからImageのビルドも含めて、自動化するファイル。

メリットは少なくとも２つあって、

- コマンド入力が楽できる
- 複数のContainerを一度に起動できる

![dokcer-compose](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521715/2add93e7-a741-393d-5e16-986819344e59.png "dokcer-compose")

docker-compose.ymlに指定するオプションや環境変数を記載しておけば、毎回コマンドでわざわざ指定しなくても、docker-composeファイルを読み込むだけで済むようになる。
またWebサーバーとDBサーバーでそれぞれContainerを起動しようとする際、通常であればそれぞれのContainerに対して起動のコマンド入力をする必要があるが、これもdocker-compose.ymlで一発呼び出しで済む。

```bash:bash
# こんなに長いコマンドが
docker run -dp 3000:3000 \
  -w /app -v "$(pwd):/app" \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:18-alpine \
  sh -c "yarn install && yarn run dev"
docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:5.7
```
```bash:bash
# これで済むようになる
docker-compose up -d
```

docker-compose.ymlの中身はこんな感じ

```yaml:docker-compose.yml
version: "3.7"

services:
  app:
    image: node:18-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:
```

# リンクまとめ
* [公式Dockerfileリファレンス](https://docs.docker.jp/engine/reference/builder.html)
* [Dockerfileの書き方, 利用する命令, 作成手順](https://www.wakuwakubank.com/posts/270-docker-build-image/#index_id1)
* [(Docker) CMDとENTRYPOINTの「役割」と「違い」を解説](https://hara-chan.com/it/infrastructure/docker-cmd-entrypoint-difference/)
* [よく使うDockerコマンド集](https://zenn.dev/ttani/articles/docker-command-list)
* [Docker利用時のソースコードの変更反映](https://qiita.com/tearoom6/items/326895f0a35774d70729)