初心者向け DockerFile の基礎知識

# 背景
環境構築を楽にするために、docker の勉強をしております。
今回は DockerFile について勉強したので記事にしました。
似たような命令の違いなど記述しているので、参考になれば嬉しいです。

# DockerFile
以下を参考にして記事を作成しております。

https://docs.docker.jp/engine/reference/builder.html

## DockerFile とは
イメージを作成するために実行するコマンドを記述するファイルです。
Docker は Dockerfile から命令を読み込んで、自動でイメージをビルドします。

## DockerFile を利用するメリット
イメージの作り方としては DockerFile から作成するか、コンテナをカスタマイズしてイメージ化するかの2つがあります。

コンテナをカスタマイズしてイメージ化するのは、DockerFile を学習するコストはありませんが、どのようなイメージなのかが分かりにくいです。

DockerFile を使うことでイメージで実行された命令が DockerFile を見ることで簡単にわかるため、どのようなイメージなのか分かります。
さらに GitHub などで DockerFile を管理すると変更点も分かります。

## 記述書式
### コメントの付け方
`#`をつけることで`#`以降の同行の文字列をコメントとして記述することができます。

```
# Apache のインストール
RUN yum install -y httpd
```

### パーサ・ディレクティブ
`#`をつけるとコメント化されると書きましたが、例外があります。
以下のように DockerFile 内のエスケープ文字を指定する場合のみ、コメントとして扱いません。

```
# escape=\
```

現在(2021/06/26)では、パーサディレクティブとして扱うのは`escape`のみです。

### 命令（instruction）
大文字、小文字の区別はしませんが、大文字での記述を推奨しております。

#### ベースイメージの指定（FROM）
イメージのベースとなるイメージを指定します。
DockerFile は基本的に`FROM`命令から始まります。

記述は以下のように行います。

```
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
```

記述例

```
FROM centos:centos7
```

#### コマンドの実行（RUN）
様々なコマンドの実行ができます。
頻繁に使う命令です。

記述は以下のように行います。

```
RUN <command> （シェル形式、コマンドはシェル内で実行される、シェルとはデフォルトで Linux なら /bin/sh -c、Windows なら cmd /S /C）
```

デフォルト以外のシェルを使用したいときは以下のように記述します。

```
RUN /bin/bash -c 'echo hello'
RUN ["/bin/bash", "-c", "echo hello"]
```

記述例

```
RUN yum install -y httpd
```

exec形式で記述する場合は、`"`を使いましょう。
json配列扱いなので、`'`は使えません。

#### コンテナ実行時の処理の記述（CMD, ENTRYPOINT）
コンテナの実行時の処理を設定することができます。
`CMD`はデフォルト処理の設定、`ENTRYPOINT`は必ず実行される処理の設定です。
言葉だけでは分かりにくいと思うので、具体例を交えて説明していきます。

`CMD`の記述は以下のように行います。

```
CMD ["executable","param1","param2"] (exec 形式、この形式が推奨される)
CMD ["param1","param2"] ( ENTRYPOINT のデフォルト・パラメータとして)
CMD command param1 param2 (シェル形式)
```

`ENTRYPOINT`の記述は以下のように行います。

```
ENTRYPOINT ["executable", "param1", "param2"] (exec 形式、この形式が推奨される)
ENTRYPOINT command param1 param2 （シェル形式）
```

ここから、`CMD`と`ENTRYPOINT`の違いについて説明します。

以下の命令を持つコンテナを作成した場合を考えます。

```
ENTRYPOINT ["wget"]
CMD ["https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.25.tar.gz"]
```

コンテナ起動時に mysql のソースコードをダウンロードするコンテナが作成されます。
この場合、`ENTRYPOINT`で指定している`wget`は必ず実行されます。
しかし、`CMD`で指定されているURLは`docker run`時に引数が指定されなかった場合のみ適用されます。

```
# この場合は wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.25.tar.gz が実行される。
docker run イメージ名 

# この場合は wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.20.tar.gz が実行される。
docker run イメージ名 https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.20.tar.gz
```

このようにデフォルト処理か必ず実行される処理かの違いがあります。

#### イメージにメタ情報付与（LABEL）
イメージに対してメタデータを付与します。
`LABEL`ではキーバリューペアによる記述を行います。 
値に空白などを含める場合は、クォートとバックスラッシュを用います。

以下のように記述します。

```
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
```

`FROM`で使用しているベースイメージのラベルのメタ情報も作成するイメージに付与されます。
ラベルのキーが一致した場合は新しく指定したメタ情報に書き換えられます。

`docker inspect`でメタ情報を見ることができます。

#### コンテナがリッスンするポートの指定（EXPOSE）
コンテナの実行時に、所定ネットワーク上のどのポートをリッスンするかを指定します。
`EXPOSE`だけでは意味がなくて、`docker run` 時に `-P` オプションを記述することによって、`EXPOSE`で指定したコンテナ側のポートとホスト側の空いているポートがポートフォワーディングされます。

以下のように記述します。

```
EXPOSE <port> [<port>...]
```

個人的には`-p ホスト側のポート:コンテナ側のポート`で指定して起動することが多いです。

#### 環境変数、変数の定義（ENV, ARG）
`ENV`はコンテナの環境変数 `<key>` に `<value>` という値を設定します。
`ARG`はDockerFile内の変数 `<name>` に `<default value>` という値を設定します。
`ENV`と`ARG`の違いとして、`ENV`はコンテナ作成後も環境変数として定義されますが、`ARG`はイメージをビルドする際に一時的に定義される変数であるため、作成後のコンテナには定義されません。

以下のように記述します。

```
ENV <key> <value>
ENV <key>=<value> ...

ARG <name>[=<default value>]
```

記述例

```
ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

# 定義してdocker run で値を設定することもできる。
ARG user
ARG user=hoge
```

#### コンテナ内にファイルを配置（ADD, COPY）
`<src>` に示されるファイル、ディレクトリをコピーして、イメージ内のファイルシステム上のパス`<dest>` に加えます。

DockerFile を配置しているディレクトリ（コンテキストディレクトリ）、またはコンテキストディレクトリ配下にあるディレクトリのファイルを配置することができます。

以下のように記述します。

```
ADD <src>... <dest>
ADD ["<src>",... "<dest>"] （この書式はホワイトスペースを含むパスを用いる場合に必要）

COPY <src>... <dest>
COPY ["<src>",... "<dest>"] （パスにホワイトスペースを含む場合にこの書式が必要）
```

`ADD`と`COPY`の違いは以下の通りです。

`ADD`は以下の書式でリモートURLからファイルをダウンロードできます。

```
ADD https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.25.tar.gz 配置先
```

また、`ADD`は `<src>` がローカルのtarアーカイブであり、認識できるフォーマット（gzip、bzip2、xz）である場合、tarアーカイブが自動で展開されます。
リモートURLからのダウンロードである場合は展開されません。

#### ボリュームを作成後、マウント（VOLUME）
指定された名前を使ってマウントポイントを生成し、ホスト側に作成されたボリュームにマウントします。

以下のように記述します。

```
VOLUME ["/data"]
VOLUME /var/log /var/db 
```

個人的な感想ですが、このような形でボリュームを作成すると`VOLUME NAME`がハッシュ値になってしまい、よくわからなくなってしまうのでボリュームを作成して`docker run -v`を使ってマウントしたほうが良いかと思います。

#### 命令実行を行うユーザの指定（USER）
イメージが実行される際に`USER`以降の命令の実行ユーザを指定します。

以下のように記述します。

```
USER <user>[:<group>]
USER <UID>[:<GID>]
```

#### ワークディレクトリの指定（WORKDIR）
コマンドを実行するディレクトリ(ワークディレクトリ)を指定します。
相対パス、絶対パス、どちらでも指定可能ですが、相対パスで指定した場合は、指定前のワークディレクトリからの相対パスになります。

以下のように記述します。

```
WORKDIR /path/to/workdir
```

## DockerFile 記述例
Apache をインストールして実行するイメージの記述例です。

```
# ベースイメージの指定
FROM centos:centos7

# Apache のインストール
RUN yum install -y httpd

# Apache の起動
ENTRYPOINT ["/usr/sbin/httpd", "-DFOREGROUND"]
```

# 最後に
冒頭にもお話ししましたが、DockerFile は非常に便利です。
この記事は知識定着のためのアウトプットですが、私のような docker 初心者の方の助けになると嬉しいです。


最後まで読んでいただきありがとうございました。