Docker覚書

# はじめに
Dockerに関する覚書です。
Docker自体の説明等ではなく、実際にDockerを使うときに利用できるような情報に絞ってまとめています。

# コンテナの作成
```sh:nginxイメージのデフォルトコマンドで起動
$ docker container run nginx
```

# -it オプション
コンテナの制御端末を得る。
```sh:コンテナのbashを操作可能にする
$ docker container run -it nginx bash
```

# -d オプションとattach
-d（--detach(離す。コンソールを離してバックグラウンドで起動するの意。)）オプションをつければ、バックグラウンドで起動できる。
バックグラウンドで起動しているコンテナの端末に入るには`docker container attach <コンテナ名>`を使う。

# 稼働済みのコンテナでコマンドを発行
すでに動いているコンテナの設定ファイルを見たりする際に使う。
ただし、本番稼働しているコンテナを直接操作することはあまり望まれない。
リダイレクトやパイプをそのまま使うとホスト側に出力されてしまうので、コンテナ側に出力するには、-cオプションをつけ、実行したいコマンドを引用符で囲んで実行する。
```sh
$ docker container exec mynginx date
$ docker container exec mynginx ls /
$ docker container exec -it mynginx bash
$ docker container exec mynginx sh -c "echo 'hello docker' > /hello.txt"
```

# helpコマンド
```sh
$ docker help
```
で大まかな説明が表示される。
```sh
$ docker container help
```
などとすると、カテゴリレベルでのヘルプが表示される。
```sh
$ docker container run --help
```
などとすると、コマンドレベルでのヘルプが表示される。

# コンテナの一覧表示
```sh
$ docker container ls
$ docker container ls -a
```
`-a`オプションをつけると、停止しているコンテナも含めて表示される。

# 特定のコンテナ情報の確認
```sh
$ docker container inspect mynginx
```

# コンテナのログを確認
```sh
$ docker container logs mynginx
$ docker container logs -f mynginx
```
`-f`オプションで、継続して表示できる。

# コンテナのリソース情報の確認
```sh
$ docker container stats --no-stream
```

# コンテナの起動と停止
```sh
$ docker container start
$ docker container stop
```
一度停止してその後起動したコンテナでは、コンテナ上のデータは引き継がれる。
しかし、コンテナは使い捨てをするのが基本なので、停止して起動することはあまり推奨されない。

# コンテナの破棄
```sh
$ docker container rm mynginx
$ docker container prune
```
後者は、起動していないコンテナをまとめて削除するコマンド。

# Dockerでできること・やるべきでないこと
- できること
    - 環境のパッケージ化
    - インフラのコード化
    - サービスの分離
- やるべきでないこと
    - 特定環境に依存するイメージの作成。例｜IPアドレスをイメージにベタ書き
    - ブラックボックスなイメージの作成。例｜イメージの作成を手動でする->Dockerfileを使うこと！
    - 巨大なアプリを１つのイメージで構築する

# 同一名のイメージを区別するタグ
リポジトリには複数のイメージが登録されており、それらはタグという形で区別される。どのようなタグがあるかは、DockerHubのWebサイトなどで検索できる。

# イメージの検索
```sh
$ docker search python
```

# ホストとコンテナ間のファイルコピー
```sh
$ docker container cp <コピー元> <コピー先>
```
コンテナのパスは、「コンテナ名:コンテナ内のパス」という形式で指定する。

# 停止しているコンテナをイメージ化する
```sh
docker container commit <イメージ化したいコンテナ名> <イメージ化した後のイメージ名>
```

# イメージの階層構造
イメージから起動したコンテナに変更を加えた後、そのコンテナをイメージ化すると、元のイメージに差分を上乗せしたような形でイメージが作られる。このようにしてイメージは階層（レイヤー）構造となっている。イメージの階層構造を確認するには、次のコマンドを使う。
```sh
$ docker image history <イメージ名>
```

# イメージにタグをつける
DockerHubレジストリに登録するには、`ユーザー名/イメージ名:タグ名`という形式にしなければならない。
イメージ名とタグの変更には、次のコマンドを使う。
```sh
$ docker image tag <元の名前> <新しい名前>
```
このとき、新しくできたイメージは、元のイメージと同じimage idを持つ。（つまり、実体は同じもの。）

# レジストリへのPush（登録）
先にDockerHubへログインをしておく。
```sh
$ docker login
```
ログインした状態で次のコマンドを使って登録する。このとき、すでにリポジトリが存在していれば**新しいタグのイメージ**がそのリポジトリに**追加**される。まだ存在しない名前の**イメージ**であれば、**新規に**リポジトリが作成される。
```sh
$ docker image push <イメージ名>
```
アップロードされるのは、ベースとなったイメージからの**差分のみ**となる。

# コンテナを終了させないためのテクニック
コンテナ起動時に次のようにする。コマンド`tail -f /dev/null`コマンドは、Linuxのtailコマンドで何も描かれることのないスペシャルファイルの更新を標準出力させている。
```sh
$ docker container run example tail -f /dev/null
```

# 汎用性を持たせるための環境変数の利用
`-e`オプションを利用して、例えば次のようにする。
```sh
$ docker container run --name example_container -p 8080:80 -d -e MESSAGE="Hello, Docker!" -e PORT=80 example_image -it bash
```
なお、環境変数はコンテナ起動後は変更できない。変更したければ、イメージから新しいコンテナを作り直す必要がある。

# 環境変数を使って設定ファイルを作成する
設定ファイルのテンプレートを用意し、その中の一部を置き換えるようなスクリプトを用いて、環境変数を使った設定ファイルを力技で作成することがある。テンプレートの置き換え部分には`{{EXAMPLE}}`といったような波括弧２つで囲む**マスタッシュ記法**を用いるとよい。
例えば、nginxをリバースプロキシとして用いるようにするための設定ファイルを作成する方法と、イメージの作成は、次のとおり。
1. 設定ファイルのテンプレートを用意する
```text
events{
    worker_connections 1024;
}
http{
    server{
        server_name localhost;
        listen {{PORT}};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        location / {
            proxy_pass {{APP_SERVER}};
        }
    }
}
```
1. テンプレートに環境変数を埋め込むスクリプトを作成する
```sh
#!/bin/sh
sed -e "s/{{PORT}}/$PORT/g" /etc/nginx/nginx.tpl > /etc/nginx/nginx.conf
sed -i -e "s^{{APP_SERVER}}^$APP_SERVER^g" /etc/nginx/nginx.conf
exec nginx -g "daemon off;"
```
ここで、`^`を区切り文字に使用しているのは、URLの`/`と衝突させないため。
また、`exec`を用いてnginxを起動しているのは、シェルスクリプトが持つPID1をnginxに引き継がせるためである。
1. イメージの作成
```sh
$ docker container run --name c2env1_we_base -d nginx:1.26.2-alpine tail -f /dev/null
$ docker container cp start.sh c2env1_we_base:/
$ docker container exec c2env1_we_base chmod +x /start.sh
$ docker container cp nginx.tpl c2env1_we_base:/etc/nginx
$ docker container stop c2env1_we_base
$ docker container commit c2env1_we_base c2env1_web
```
1. コンテナの起動
```sh
$ docker container exec c2env1_app hostname -i
172.17.0.2
$ docker container run --name c2env1_web -p 8080:80 -d -e APP_SERVER="http://172.17.0.2:80" -e PORT=80 c2env1_web /start.sh
```
アプリケーションサーバのIPアドレスを確認した後、それを環境変数に入れてリバースプロキシサーバのコンテナを起動している。

# 環境変数ファイルの利用
環境変数をまとめてファイルに設定しておき、`--env-file <ファイル名>`オプションで利用することができる。
環境変数ファイルは、「.env」記法（上から順番に「環境変数=環境変数の値」を並べたもの）で書く。ファイル名は、慣習的に「.env」にする。
前節の例を環境変数ファイルで実行する際は、次のとおりになる。
```text:.envファイル
APP_SERVER="http://172.17.0.2:80"
PORT=80
```
```sh
docker container run --name c2env1_web -p 8080:80 -d --env-file .env c2env1_web /start.sh
```

# Dockerのネットワーク
### 主要コマンド
```sh:存在するネットワークの確認
$ docker network ls
```
```sh:コンテナが所属するネットワークの確認
$ docker network inspect <ネットワーク名>
$ docker container inspect <コンテナ名>
```
ネットワークをアプリごとに分離したいなどの場合は、ネットワークを作成することもできる。コンテナのネットワーク指定は、`run`時にオプション`--network <ネットワーク名>`で行う。
```sh:ネットワークの作成
$ docker network create <ネットワーク名>
```


### ネットワークの種類
- bridge
    デフォルトのネットワークの種類。NAT用のネットワークである。Dockerエンジンがデフォルトゲートウェイやネームサーバの役割を持つ。コンテナの名前解決を管理する。コンテナ自身にもネームサーバがあり、最初はそこに問い合わせる。これにより、同ネットワーク内のコンテナ同士は名前解決ができる。よって、ホスト名ベースで連携するのが良い（IPアドレスはコンテナ作成時に決まるので、IPアドレスベースだと、手動で確認後、環境変数にセットするなどの作業が発生してしまうから。）
- none
    文字通り、コンテナが通信不可能な状態にする
- host
    ホストのネットワークをそのまま使う。なので、ホストが使っているポート番号と衝突したりしないように注意

# データ揮発性とデータ永続化
### コンテナ上にデータは書き出さないのが基本
イメージから展開したコンテナに加えられた変更は、コンテナが破棄されると失われる。先に述べたレイヤー構造のうち、コンテナは「変更可能な最上部の差分データ」として実現されているので、コンテナで行なった変更は全てこのレイヤーでの差分データとして実現される。よって、コンテナの破棄とともにデータも消滅することとなる。
そこで、次の事項を意識すべきである。
- ファイルにデータを書き出さない
- アプリのログは、標準出力・標準エラー出力に書き出す
- イメージ作成に必要となった中間ファイル（yumのキャッシュなど）は消す
例えば、nginxのイメージでは、ログの出力先を、エイリアスを用いてファイルではなく標準出力と標準エラー出力にしている。（スペシャルファイルである`/dev/stdout`と`/dev/stderr`）

### データ永続化の方法
- DBだけ普通のVMを使うなど、コンテナでデータの管理をしない
- コンテナとして動くOSが外部ストレージ上に直接データを置く
- Dockerのデータ永続化手法を使う（BindかVolume）
複雑な運用が必要であれば、上２つの方法が良い。比較的シンプルな構成であれば３つ目のDockerを使った永続化手法の導入が簡単。

#### Bind方式
ホスト側にディレクトリを作成しておく。Docker for Desktopの場合は、Settingからマウント設定をしておく。`run`時に次のオプションをつける。
```sh:
--mount type=bind,source=<ホスト側のディレクトリ>,target=<コンテナのディレクトリ>
```
なお、最後に`,readonly`と付けると、コンテナからはRead-onlyでBindできる。

#### Volume方式
Dockerが管理するデータ領域をコンテナに提供する。ユーザーがホスト側でデータ操作をしないデータベースなどの用途であれば、データ領域を名前で管理できるVolumeの方が運用が簡単。
次のようにVolumeを作成し、コンテナの`run`時に次のオプションをつけると利用できる。なお、typeはデフォルトでVolumeなので省略可。
```sh:ボリュームの作成
$ docker volume create myvolume
```
```sh:オプション
--mount source=<ボリューム名>,target=<コンテナのディレクトリパス>
```

### データ永続化と永続化領域のバックアップとリストア
WordPressとMySQLを例にとる。
次のように、MySQLコンテナにはデータベースのデータ領域を、ボリュームとして、`/var/lib/mysql`にマウントする。また、バックアップデータを置く領域を、バインドとして、コンテナの`/mysqlbackup`としてマウントする。
次で行っている作業は次のとおり。
1. ネットワークの作成
1. MySQLコンテナの作成
1. WordPressコンテナの作成
```sh
$ docker network create -d bridge wp-net
$ docker container run -d --network wp-net --name mysql --mount source=mysqlvolume,target=/var/lib/mysql --mount type=bind,source=/<HostPath>/mysqlbackup,target=/mysqlbackup -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=password mysql
$ docker container run -d --network wp-net -p 8080:80 -e WORDPRESS_DB_HOST=mysql:3306 -e WORDPRESS_DB_NAME=wordpress -e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=password --name wordpress wordpress
```
バックアップとリストアについては、おおまかに次の２つの手法に分かれる。
- ボリューム領域を丸ごとzipやtarで固めてバックアップとして保存する
- アプリが持つバックアップ機能をコンテナ上で実行する
前者は、バックアップしている最中にデータ変更があってはならないため、次のような手順でバックアップを行う。ちなみに、2020年時点では、サーバー仮想化におけるストレージスナップショットのような機能はないので、本格的なデータベース運用が必要ならサーバー仮想化のほうが良いかもしれないとのこと。
1. 一旦コンテナを停止する
1. 別コンテナで同じ領域をマウントしてバックアップを取る
1. 停止させていたコンテナを起動してサービスを再開する
本例では、MySQLに`mysqldump`というツールが備わっているので、それを用いてデータベースバックアップを行う。バックアップを取得するコマンドは次のとおり。
```sh
docker container exec mysql bash -c "mysqldump -u root -ppassword -A > /mysqlbackup/20241111.sql"
```
バックアップ作業は、cronで自動化するのが良い。次のような手順を自動化して必要な時に復元できないという状況を避けられるようにすべき。
1. バックアップの作成
1. 確認としての標準出力など
1. バックアップデータをテスト用コンテナに渡して復元できるかをチェック
本例でのバックアップとリストアの確認手順は次のとおり。
1. WordPressで記事の新規作成（記事Aとする）
1. MySQLでバックアップをとり、Bindされたバックアップ領域に書き出す
1. WordPressで記事の新規作成（記事Bとする）
1. MySQLとWordPressのコンテナとボリュームを削除
1. MySQLのコンテナを再作成
1. バックアップから復旧
1. WordPressのコンテナを再作成
1. バックアップ時点の記事のみがあることを確認
実行したコマンドを次にまとめる。
```sh
$ docker container exec mysql bash -c "mysqldump -u root -ppassword -A > /mysqlbackup/20241111.sql"
$ docker container exec mysql cat /mysqlbackup/20241111.sql

$ docker container stop mysql wordpress
$ docker container rm mysql wordpress
$ docker volume rm mysqlvolume

MySQLコンテナの再作成は、最初のコンテナ作成時と同じコマンド
$ docker container exec mysql bash -c "mysql -u root -ppassword < /mysqlbackup/20241111.sql"
WordPressコンテナの再作成は、最初のコンテナ作成時と同じコマンド
```
なお、実際の運用時には、オリジナルのコンテナはキープしたまま、別の新規に作成したコンテナ上でリストアを適用する手法が良い。

# Dockerfile
### 概要
イメージを作成するための手順書（設計図）。その通りに自動でイメージ作成を実施する（ビルドする）手法。
例えば、開発環境をきれいな状態に保ち、プロジェクトを複数進めるうちにアプリが多数インストールされた汚い状態を防ぎ、本番環境でサービスを更新する際にトラブルが起きる可能性を低めるといった効果がある。
Dockerfileに書かれたとおりにイメージが自動で新規に作成されるため、手動で手順書のとおりに環境構築するときに発生する曖昧な記述・オペミスによる構築失敗などを防ぐことができる。

### Using cache
Dockerfileを使ったビルドはイメージの差分ファイルを積み上げる（つまり、１階層ごとにコンテナの起動と停止（イメージ化）を繰り返す）ことに相当する。イメージの変更は、コンテナに展開して処理を施し、可変な状態にして、その後結果をイメージ化するということをコマンド実行のステップごとに繰り返すこととなる。
したがって、これまでのイメージと同じ部分はキャッシュを利用して高速に更新できる。一方、前回と違う部分は改めて実行して差分を更新する。その後の差分は、ベースが変更されたことになるので、全て改めて実行される。
これを踏まえると、ビルド時間を短縮するには頻繁に変更が発生する箇所はDockerfileの後半に持ってくるのが良いということとなる。

### ディレクトリのコピーと.dockerignore
ホストからコンテナのファイルコピーを一つずつ行うとかなりの手間。よって、ディレクトリごとホストからコピーをするという方法を取る。このとき、コピーしたくない余計なファイルを指定する方法が.dockerignoreというファイル。これをDockerfileが存在するディレクトリに配置しておくと、コピー命令の対象から指定した形式のファイルが除外される。次のようにアスタリスク２つとスラッシュを用いて記述すると、階層構造を無視してそのファイル名のファイルとディレクトリ（およびその配下のすべてのファイル）を取り込まなくなる。
```dockerfile:.dockerignore
**/__pycache__
**/.DS_Store
**/Thumbs.db
```

### Dockerfileを用いたビルドの例
ファイルの名前は基本的に`Dockerfile`とする（`build`コマンドのオプション`-f`で別のファイル名を指定することが可能。例えば、コンパイルの最適化レベルやデバッグレベルの変更といった開発用と本番用で異なるビルド方法を取りたい場合に用いる）。Dockerfileには１行ずつ命令を書く。上から下にビルド処理が実行されていく。各行は命令から始まる。
イメージのビルドは、次のようなコマンドで行う。
一般的には、Dockerfileがあるディレクトリに必要なファイルをまとめて置いておき、そのディレクトリに移動した上でbuildコマンドを発行する。
```sh
$ docker image build -t <イメージ名> <Dockerfileのあるディレクトリのパス>
```
```dockerfile:Dockerfileの例
From python
Label author="ramgap@example.com"
RUN pip install flask
COPY ./server.py /server.py
ENV PORT 80
CMD ["python", "-u", "/server.py"]
```
```sh:ビルドコマンドの例
docker image build -t exampleimage ./
```

# Dockerで使うイメージは「サービスに必要なデータのみを乗せた軽量なもの」がよい
イメージはネットワーク越しに取得や登録を行うので、数ギガもあるイメージを作るとレジストリとDockerホストとのやりとりに時間がかかってしまう。したがって、Dockerでは超軽量なLinuxが好まれて使われる。例えば、「Alpine Linux」や「Busy Box」が有名。Alppine Linuxを用いることが多い（ある程度現代的なLinuxらしい操作が可能でありながら、５〜６MBと軽量なため）。

# ステージングビルド
イメージ作成に必要な作業を別のコンテナで実施し、作業用コンテナで作成したイメージから本番用イメージで使うデータをコピーしてくるといったようなことができる。
例えば、go言語のビルド環境を備えたalpineでgoのプログラムをビルドし、その後バイナリのみを別のalpineのみが含まれるイメージにコピーするといったことができる。
これによって、イメージのサイズを小さく留めておくことができる。
さらに、AlpineなどのLinux OSのうえにアプリのバイナリを乗せて動かすのではなく、OSを搭載しないイメージ（Scratchと呼ばれる）の上にバイナリを乗せてそれを起動することができる。ただし、OSの機能が使えないのでトラブル発生時の調査などが面倒になるため、たまに用いられるくらい。
ステージングビルドの例は次のとおり。前半の`From`で一つ目の作業イメージに`as builder`として名前を与えている。成果物となる２つ目のイメージ作成
（後半）の`COPY`命令では、`--from=builder`として、Dockerホストではなく一つ目のイメージで作成したバイナリファイルをイメージ内部にコピーしている。
```dockerfile:ステージングビルドのDockerfileの例（OSを備えるイメージ）
From golang:alpine3.20 as builder
WORKDIR /src
COPY ./main.go /src
RUN go build -o start_appserver main.go

From alpine:3.20.3
COPY --from=builder /src/start_appserver /bin/start_appserver
CMD ["/bin/start_appserver"]
```
```dockerfile:ステージングビルドのDockerfileの例（Scratch）
From golang:alpine3.20 as builder
WORKDIR /src
COPY ./main.go /src
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o start_appserver main.go

From scratch
COPY --from=builder /src/start_appserver /start_appserver
CMD ["/start_appserver"]
```

# Dockerfileを作るコツ
まず手動で展開してみながら、少しずつdockerfileを育てていく。
無駄に差分イメージが増えることを防ぐために、「複数のコマンドを見やすく同時に発行」したり、「コピーはファイルごとではなく、ディレクトリごとに行う」ことを意識する。例えば、yumでのパッケージ導入であれば、「最初にupdateして、パッケージをまとめてインストールし、最後に不要なキャッシュを消す」ということを一つのコマンドで行うことが一般的。このとき、改行してもコマンドを継続する`\`や、前のコマンドが成功したら次のコマンドを発行する`&&`を利用する。
```dockerfile:例
RUN yum update \
    && yum install -y \
       package-bar \
       package-baz \
       package-foo \
    && rm -rf /var/cache/yum/* \
    && yum clean all
```
実行コストが高いコマンド（大きいパッケージのインストールなど）は、そのゴミ処理コマンドなどを除けば他のコマンドから独立させ、前方の方で実行すべき。そうしなければ、一つのコマンドを変更するだけで他のコマンドもビルド時にすべて再実行されてしまい、キャッシュが効かなくなってしまう。

# コンテナ上でのファイル削除
コンテナ上で下のレイヤーにあったファイルの削除は、「イメージにおけるこのファイルを消しましたよ、この部分は消えてますよ」という情報を上書きする形で行われるため、不要なファイル（キャッシュなど）の削除は、一つの`RUN`内で行うようにする。
そうしなければ、イメージに無駄なデータが残っていくこととなる。

# コンテナ上でのアプリの動かし方・止め方
### 動かし方
Dockerでは、`RUN`時のコマンドがPID1のプロセスとして動く。そして、PID1のプロセスが止まった時にコンテナもストップする。
一方、通常のOS（ベアメタルか仮想マシン）では、すべての原点となるプロセス`init`プロセスがPID１として起動されOSとして動作する。そして、アプリケーションはデーモンとして別のPIDで動かす。
したがって、Dockerでは、アプリケーションをデーモンとしてではなく、直接PID1として起動する必要がある。
例えば、nginxをプロキシとして起動するためにシェルスクリプトで設定をした後シェルスクリプトから起動する場合、そのままではシェルスクリプトがPID1となってしまうため、`-g "daemon off;"`オプションを用いてシェルスクリプトのPID1を引き継いでnginxをPID1にして起動する必要がある。

### 止め方
Dockerでは、stop処理を行った時、プロセスにシグナル（プロセスに対して停止しろ・割り込みが発生などの命令を伝える仕組み）を送ってコンテナを停止させている。
具体的には、シグナル`SIGTERM`を送って停止処理をしている。そして、プロセスがSIGTEM時の停止処理を実装している場合はそれを行い、していない場合は１０秒後に改めてDockerがシグナル`SIGKILL`を送って強制停止する。
よって、雑な停止によるデータベースを使うアプリ上のデータ不整合などを防ぐために、プロセスの安全な停止（Graceful shutdown）が必要となる。
上述のように、シェルスクリプトから起動した場合の止め方は、trap関数、&によるバックグラウンドプロセス化、シェルスクリプトを`wait`させるという方法が用いられる。
個別具体的なアプリにおいては、シグナルへの対応がそれぞれ異なる場合があるため、それぞれのアプリで安全停止の処理を行う必要がある。
どちらにおいても、やるべきことは、まずシグナル受信後の処理を関数にまとめておき、それをシグナル受信時の処理として登録し、安全に停止させる、ということである。

# Docker Compose
### 概要
Composeを用いると、複数コンテナで構成されるアプリケーション全体のイメージをどのように展開するかということを構成ファイル（形式はYAML。）で定義できる。
Dockerfileで単体イメージの開発コストを下げて、Composeでアプリ全体としての開発コストと運用コストを大幅に下げることができる。
Composeを用いることで、ビルドだけでなく、複数のコンテナで構成されるアプリの設計やライフサイクル（ビルド・起動・停止）、ネットワークやボリューム、アプリを構成するデータベースなどもDockerに管理させることができる。
Composeのデフォルトの定義ファイル名は「docker-compose.yml」である

###  Composeによるイメージ、コンテナ操作
#### イメージのビルド
```sh
$ docker compose build
```

#### コンテナの起動
docker-compose.ymlの例を次に示す。これは一つのコンテナを起動する簡単な例である。
```yaml:docker-compose.ymlの例
services:
  nginx:
    image: nginx:1.17.6-alpine
    ports:
    - 8080:80
    environment:
      MYENV: "hello, compose!"
```
docker-compose.ymlを使ってコンテナを起動するには、このファイルがあるディレクトリに移動し、`docker compose up`コマンドを使う。
```sh:例
$ docker compose up -d
```
オプションでよく利用されるのは`-d`を使ったバックグラウンド実行と、`--build`を使った実行時のイメージを必ずビルドするというもの。
ネットワーク名やコンテナ名を指定しなければ、Composeファイルが格納されるディレクトリ名にサービス名をくっつけたものが名前となる。
Composeファイルに加えた更新を適用するには、もう一度このコマンドを実行する。実行すると、今動いているコンテナを停止して新しいコンテナを再度立ち上げる。ただし、名前を変更した場合は、昔のコンテナは残る。
`--build`をつけない場合は、イメージが存在しない場合を除いて、ビルドは自動では実行されない。つまり、ソースコードに変更を施していても、事前にビルドしてから`up`を行うか、`--build`オプションをつけるかしなければ、古いイメージでコンテナが起動される。常にビルドオプションを付けるのも良い（ソースコードに変更がなければビルドはスキップされる）。

#### 確認
```sh
$ docker compose ps
```
定義ファイルに定義されていない他のコンテナはこのコマンドでは出力されない。

#### 停止と再起動
```sh:停止
$ docker compose stop
```
```sh:再起動
$ docker compose start
```

#### 破棄
```sh
$ docker compose down
```
ボリュームは破棄されない。別途手動で破棄する必要がある。

### ネットワークを介した複数コンテナの連携とデータ永続化手法
```yaml:docker-compose.ymlの例
services:
  mysql:
    image: mysql:5.7.28
    restart: unless-stopped
    networks:
    - wp_net
    volumes:
    - mysql_volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: password

  wordpress:
    image: wordpress:5.2.3-php7.3-apache
    restart: unless-stopped
    depends_on:
    - mysql
    networks:
    - wp_net
    ports:
    - 8080:80
    environment:
      WORDPRESS_DB_HOST: mysql:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: password

networks:
  wp_net:
    driver: bridge
volumes:
  mysql_volume:
    driver: local
```
depends_onは、「このコンテナが指定したコンテナに依存している」という設定。これが設定されていると、依存しているコンテナが起動するまで起動されない。

### Dockerfileを用いたCompose
Composeの定義内で、イメージが置かれるディレクトリとDockerfile名を指定すれば、`docker compose`コマンドの発行時にDockerfileによるビルドも自動で実施する。注意点として、**docker-compose.yml内の`context`で指定したディレクトリを基準にした相対パスで、Dockerfileの自体のパス指定やDockerfile内でのパス指定が行われる**点が挙げられる。
```yaml:Dockerfileを用いた複数ディレクトリ・複数コンテナの場合のdocker-compose.ymlの例
services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    ports:
    - 8081:80
  web:
    build:
      context: ./web
      dockerfile: Dockerfile
    image: c5dev1_nginx
    container_name: c5dev1_nginx
    ports:
    - 8080:80
```
`build`がビルド指定である。
dockerfileの名前がデフォルトのDockerfileであれば、省略できる。
ビルド指定がある場合の`image`は、ない場合とは異なり、作成するイメージ名の指定を意味する（ない場合は、コンテナを起動する元のイメージの指定を意味する）。
ビルドコンテキストは、イメージごとに作成するのが一般的。よって、docker-compose.ymlファイルがあるディレクトリの配下にそれぞれのイメージごとのディレクトリを作成することとなる。このとき、サービス名とコンテキスト名を一致させる方が良い。
```dockerfile:appのDockerfile
From python:3.7.5-slim
RUN pip install flask
COPY ./src/server.py /server.py
ENV PORT 80
CMD ["python", "-u", "/server.py"]
```
```dockerfile:webのDockerfile
From nginx:1.17.5-alpine
COPY ./html/ /usr/share/nginx/html/
CMD ["nginx", "-g", "daemon off;"]
```

### その他
リモートのホストを`docker compose`コマンドで操作できる。これによって、ソースコードなどを一つのDockerホストに集約しておき、そこからさまざまな別のDockerホストに構成を送ることができる。
環境変数をdocker-compose.ymlに埋め込むことができる。`${環境変数名}`とする。もとの環境変数のデフォルトファイル名は、`.env`である。この機能は、一つのcomposeファイルを複数人でさまざまな環境で利用する場合などに便利である。それぞれの利用者は、環境変数ファイルの内容（ユーザ名など）を修正するだけで良い。

# 開発サイクルの効率化
Dockerを利用した開発では、ソースコードを変更して結果を確認したい場合に、イメージの再ビルド・コンテナの再デプロイのステップを挟まなければならないため、Dockerを利用しない場合と比べて手間になる。
これを解決するために、ソースコードの領域をBindを用いてホスト領域で上書きするという使い方をする。これによって、イメージの再ビルド・コンテナの再デプロイのステップがなくなるため、開発サイクルを効率化することができる。
ソースコードが完成した後に、完成版のイメージを最後に作成することで足りるわけである。
ただし、上記の方法は、ソースコードが静的（HTML、CSSなど）である場合やスクリプト言語（Python（でのFlaskのデバッグモード等）である場合等に有効であって、コンパイルが必要な言語（c言語、go言語など）ではメリットが少ない。後者の場合は、Composeの`--build`オプション付き`up`コマンドでビルドと実行を一度に実施する方が良い。
```yaml:開発環境でのdocker-compose.yml（実際は別の名前にしてup時に名前指定する）の例
services:
  web:
    build:
      context: ./web
      dockerfile: Dockerfile
    image: c5dev2_nginx
    container_name: c5dev2_nginx
    ports:
    - 8080:80
    volumes:
    - ./web/html:/usr/share/nginx/html
```
このとき、volumesがトップレベル定義にないので、Bindとなる。前章の例では、トップレベル定義にvolumesがあるので、Volumeとなる。
なお、長い書式の方が不意なエラーを減らせるかもしれない。
参考｜

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#volumes

https://zenn.dev/sarisia/articles/0c1db052d09921

本番環境時のdocker-compose.ymlも用意しておいて、そちらではvolumes以下の記述を削除しておく。

# 参考文献
たった1日で基本が身に付く！Docker/Kubernetes超入門　伊藤裕一　氏　著　技術評論社