---
title: "[Docker] Dockerfileを見てみよう。"
type: blog
date: 2022-07-13
weight: 3
comments: true
---
## 😡 皆さん、不便を感じているのは私だけ?
DockerのCLIを少し試してみた方は、コンテナを作成して管理することがとても面倒だと感じたかもしれません。

私は簡単にしたいので、コンテナを作るたびに多くのコマンドを入力しなければなりません。

実際、それ以上に大きな問題があるとすれば、イメージをビルドする際、容量が驚くほど大きくなる可能性があるということです。

非常に大きくはないにしても、少なくとも100MBから、1〜2GBに達するものまで様々です。

![img.png](https://blog.kakaocdn.net/dn/dg7HAJ/btq0ZLhsh0x/RXZPbihsD3h9ou7NviGfM1/img.png)

多くのコンテナを管理する会社であれば、このようなことはさらなる災難として訪れる可能性があります。

容量はさておき、イメージを転送し、これを受け取るのにかかる時間は非常に長くなるでしょう。

## 📄 Dockerfile

そのためDockerには新たにコンテナを作る方法が提案されています。 

作成済みのイメージではなく、**イメージを作るスクリプトを共有する**ことが主旨です。

### 🚫 Don't Repeat Yourself

どうせ一度は作らなければなりません。 ~~これを自動化することは後にAIがするのでしょうか..?~~
> _でも一度作ればいいのです。_

これはDockerの内部機能であるため、コマンドを作ってテキストファイルとして保存するよりもはるかに簡単で、形式的で、構造的です。

また、結論として`Dockerfile`も結局はテキストファイルなので、保存する際に大容量を占めることはありません。

では本題に入りましょう。

### では、どうやって実行するのか?
`Dockerfile`を作成すると、ファイルの仕様に従ってイメージを読み込み、コマンドを実行します。

コンテナがDockerに表示されると、そのイメージはユーザーのローカルに保存されます。

この過程を`build`と呼び、コンテナをイメージにする`commit`とは異なります。

### `build [BUILD_PATH]`

`build`コマンドは指定したパスの下位にある`Dockerfile`を基にコンテナを作成するコマンドです。

このコマンドは再帰的に実行されるため、絶対にルートディレクトリをパスとしてはいけません。

```shell
$ docker build / # (X)

通常、プロジェクトのルートディレクトリにDockerfileを置くため、以下のコマンドが慣用句のように使われます。

$ docker build .

-f [DOCKER_FILE_PATH] オプション

伝統的にDockerfileはビルドするプロジェクトのルートパスにありますが、-fオプションを使ってルートパスとDockerfileのパスが異なっても接続できます。

$ docker build -f /path/to/a/Dockerfile .

これはDockerfileが現在のディレクトリ(.)にあるかのように振る舞うことを意味します。

-t オプション

ビルドが成功すると、新しいイメージを保存するリポジトリとタグを-tオプションを使って指定できます。

$ docker build -t shykes/myapp .

同じイメージを複数のリポジトリに配置したり、複数のタグを保存したい場合、複数の-tを指定して同じイメージでビルドできます。

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

Dockerfileの作成

Dockerfileを作成する方法は簡単です。Dockerfileを作成するだけです。

さあ、Dockerfileを作成するための膨大な設定を見ていきましょう。

ARG

Dockerfile内で変数の再利用性を最大化するために使用される変数宣言文です。

# 使用方法
ARG [KEY]=[value]

# 呼び出し
${[KEY]}

# 例
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

FROM

# - 使用方法
FROM [--platform=<platform>] <image> [AS <name>]
# または
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
# または
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

作業する基になるイメージを記載します。

前の章のruncreateコマンドで見たIMAGE部分に記載する内容と見なされ、記載されたイメージは最初にローカルで探し、その後なければ、Docker Hubからイメージを取り込むことになります。

もちろん、アクセス権限のないリポジトリにアクセスしたり、イメージ名を間違えて記載した場合は取得できません。

WORKDIR

コンテナ内で作業するパスを指定します。 前の章のruncreateコマンドで見た-w--workdirと同じ役割を果たします。

以後、COMMANDWORKDIRで実行され、この部分を指定しない場合はホームディレクトリ(~)として設定されます。

RUN [COMMAND]

前の章のrunコマンドとは別物です。 むしろCOMMAND部分に属し、コンテナ上での様々なコマンドをこのキーワードを通じて入力することができます。

この命令はイメージのデフォルトの命令とは独立して(まるでexecに入力した命令のように)動作します。

RUNは2つの入力形式をサポートしています。

# 方法1
RUN <command>
# 方法2
RUN ["executable", "param1", "param2"]

方法2の方式は複雑そうに見えるだけで、単にコマンドをスペースで区切って書いたものに過ぎません

例えば下記の2つのコマンドは完全に同一の動作を行います。

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

注意点

リスト形式で命令を実行したい場合、バックスラッシュ文字(\)を使ってパスを入力することがあります。

実際のコマンドはJSON形式で実行されますが、バックスラッシュ文字はJSONで認められる文字ではないため、特にWindowsでは\が区切り文字として使用される際、バックスラッシュ(\)を2回入力してエスケープする必要があります。

RUN ["c:\windows\system32\tasklist.exe"] # (X)
RUN ["c:\\windows\\system32\\tasklist.exe"] # (O)

ENTRYPOINT

コンテナが実行された際にスクリプトやコマンドを実行します。

このファイルをコンテナに作った場合、コンテナをスタートさせるとき(start)、生成と同時に実行する際(run)に行われると考えればわかります。

実行中のコンテナにただコマンドを送る場合(exec)には実行されません。

もう一つの特徴は、ENTRYPOINTDockerfile内で1回しか宣言できないことです。

CMD

RUNと似た役割を果たしますが、docker run実行時に特別なCOMMANDがない場合に基本的に実行されるCOMMANDという点で差があります。

# 方法1
CMD ["executable","param1","param2"] # exec形、推奨
# 方法2
CMD ["param1","param2"]
# 方法3
CMD command param1 param2

方法1でexecutableを省略することが可能ですが、この場合、ENTRYPOINTが定義されている必要があります。

ENV

コンテナの環境変数を指定する役割を果たし、runcreateコマンドで見た-eオプションと同じ役割を果たします。

# 使用方法
ENV [KEY]=[VALUE]

# 例
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

LABEL

コンテナのメタデータを追加する際に使用されます。

# 使用方法
LABEL <key>=<value> <key>=<value> <key>=<value> ...

key-valueペアとして保存され、例えば下記のように記述できます。

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."
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

もしメタデータを確認したい場合、以下のようなコマンドを通じて特定のイメージ(myimage)のメタデータを確認できます。

$ docker image inspect --format='' myimage
{
  "com.example.vendor": "ACME Incorporated",
  "com.example.label-with-value": "foo",
  "version": "1.0",
  "description": "This text illustrates that label-values can span multiple lines.",
  "multi.label1": "value1",
  "multi.label2": "value2",
  "other": "value3"
}

EXPOSE [PORT]/[PROTOCOL]

EXPOSEはどのポートを待機するかを示す部分です。

しかしDockerfileではホストのフォワードを意味するものではありません。

ただホストからContainerにデータを送信する際にどのPORTで送るか指定するためのものです。

したがって、-pオプションはDockerfileだけでは自動化できません。

[PROTOCOL]はデータの転送にどのプロトコルを使うかに関する仕様です。

tcpudpから一つを選びます。デフォルト値はtcpであり、特別な場合を除いて変更しないことをお勧めします。

TCPはパケット送信において信頼性を保証するためです。

COPY [OPTION] [HOST_PATH] [CONTAINER_PATH]

HOST_PATHにあるファイルをCONTAINER_PATHにコピーするコマンドです。 HOST_PATHはパスだけでなく、ファイル名も含めることができ、? (一文字), * (複数文字) などのワイルドカードも使用可能です。

相対パス

PATHに相対パスを入力する場合、HOST_PATHはファイルの位置を基準とし、CONTAINERWORKDIRが基準となります。

ワイルドカード例

  • home*: homeで始まるすべてのファイルとパス
  • *home*: homeを含むすべてのファイルとパス
  • ?home: homeの前に1文字あるすべてのファイルとパス(例:1homeahome

--chown

--chownオプションを使ってCONTAINER_PATHの所有者を決定できます。

内部的に実際のchownコマンドを通じて操作するため、Linuxベースのコンテナでのみ実行可能です。

ADD [HOST_PATH] [CONTAINER_PATH]

ADDはほぼCOPYと同じ機能を果たすと考えられます。

COPYのオプションと内容が適用され、ADDだけの追加機能がいくつかあります。

  1. コピー対象が圧縮ファイル(.tar, .tar.gz)の場合、該当ファイルの圧縮を解除してコピーします。
  2. wgetなどを使ってリモートのファイルをコピー対象として指定できます。

ただしリモートファイルは600権限(ユーザーのみが読み取り可能)を持ちます。

オプションを自動化する?

この時点でこの文章を読む人が疑問を持つべき部分があります。

果たしてDockerfileCONTAINERの作成過程を全て明記していると言えるか?

答えはもちろん「いいえ」。

Dockerfileがコンテナを明記することはしますが、コンテナを作成するオプションを自動化する目的ではありません。

createrunコマンドで使用する-pオプションを見てもそうです。

-pオプションはHost OSのポートとContainerのポートを明記する必要がありますが、EXPOSEは単にコンテナの開放ポートだけを明記するに過ぎません。

その以外のHost OSの多くのオプションをDockerfileが処理できません。

これはbuildがコンテナに関する仕様であり、Hostに関する仕様ではないためです。

当然このような不便さは、後に投稿するDocker Composeが登場するきっかけとなりました。

Docker Ignore File

gitを使った経験があるならば、.gitignoreを一度くらい使った経験があるでしょう。

.dockerignoreファイルも同様に、ファイルやフォルダーのパスを定義する役割を果たします。

.gitignoreファイルはコミットしないファイルを設定することが多いですが、ここでは、ADDCOPYを通じてHost OSからコピーされるべきでないファイルやフォルダのパスを定義します。

# コメント
*/temp*
*/*/temp*
temp?

参考