②１日１０分で理解するコンテナ技術入門 - Dockerfileの書き方 -

# はじめに
[前回の記事](https://qiita.com/goroneko/items/3ccfcc2ab11bd2fac28d "")では、`nginx`のコンテナイメージを使用して`docker container run`などのコマンドの使用例を説明しました。ここで使用した`nginx`のイメージは、公式に配布されているイメージでしたが、Dockerfileを用いると自分好みにカスタマイズしたコンテナイメージを作成できるようになります。コンテナを使っていくうえで、Dockerfileはほぼ必ず使うものになるので、この記事でDockerfileの使い方とイメージをつかんでいただければと思います。
# Dockerfileとはどんなものか
Dockerfileは、dockerコンテナのイメージを作成するための設定ファイルです。アプリケーションに必要なパッケージをインストールしたり、必要なファイルをコピーしたりと、コンテナを作成して動作させるために必要な操作を記述します。

例えば、`nginx`を使って自分で作ったhtmlを公開したい場合、下記のイメージのように、自分で作ったhtmlファイルをnginxのベースイメージに加える必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/4aa12670-4777-0ff5-551f-4205af16d980.png)

そのため、Dockerfileには以下のようなことを記述します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/47dc35c2-a725-77b4-869b-3406123cb8c1.png)

## 例
`nginx`のイメージをベースに、自分で作ったhtmlファイルを表示するようなイメージを作成する例をお見せしたいと思います。nginxでは、デフォルトの状態では以下のようなページを表示するウェブサーバが起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/7358f0c6-9d17-a31c-9182-932eb1c7af1c.png)

起動した際に表示するページを、デフォルトのページから自分が作成したページに置き換えるためのDockerfileを作成してみます。
以下のようなページを表示することを目標にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532202/73e61eff-c948-9a25-fe8c-8db1f78bd696.png)

用意する必要があるのは、
1. カスタムイメージのためのDockerfile
1. 表示するhtmlファイル
の2点です。下記の様なディレクトリ構成でこれらのファイルを配置します。
```bash
.
├── Dockerfile
└── html
    └── index.html
```
Dockerfileは、概要で説明したような手順と同様で、以下のように記述します。
```Dockerfile
# ベースイメージとして公式のnginxイメージを使用
FROM nginx:latest

# カスタムのindex.htmlをnginxのデフォルトのドキュメントルートにコピー
COPY html/index.html /usr/share/nginx/html/index.html

# デフォルトのnginxコマンドを実行
CMD ["nginx", "-g", "daemon off;"]
```
htmlファイルは、サンプルとして以下のようなものを使用します（このファイルの内容は本記事の本筋とは関係ありません）。
```html
<!DOCTYPE html>
<html>

<head>
  <title>Custom Nginx Page</title>
  <style>
    body {
      width: 50em;
      margin: 0 auto;
      font-family: Arial, sans-serif;
      background-color: #f4f4f4;
    }

    header {
      background-color: #333;
      color: white;
      padding: 10px 0;
      text-align: center;
    }

    nav ul {
      list-style: none;
      padding: 0;
    }

    nav ul li {
      display: inline;
      margin: 0 10px;
    }

    nav ul li a {
      color: white;
      text-decoration: none;
    }

    .content {
      margin: 20px 0;
    }

    footer {
      text-align: center;
      padding: 10px 0;
      background-color: #333;
      color: white;
    }
  </style>
</head>

<body>
  <header>
    <h1>Welcome to My Custom Nginx Page!</h1>
    <nav>
      <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">About</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </nav>
  </header>
  <div class="content">
    <p>If you see this page, the nginx web server is successfully installed and working with a custom index.html file.
    </p>
  </div>
  <footer>
    <p><em>Thank you for using nginx.</em></p>
  </footer>
</body>

</html>
```

Dockerfileのあるディレクトリに移動し、以下のコマンドでコンテナイメージをビルドします。
```bash
docker build -t custom-nginx .
```
`-t`オプションを指定すると、作成するコンテナイメージの名前を指定することができます。この場合は`custom-nginx`という名前を指定しています。
`docker image ls`コマンドで作成したコンテナイメージを確認します。
```bash
$ docker image ls
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY               TAG         IMAGE ID      CREATED         SIZE
localhost/custom-nginx   latest      1c4d9598f126  37 seconds ago  192 MB
docker.io/library/nginx  latest      4f67c83422ec  11 days ago     192 MB
```
`localhost/custom-nginx`が、いま作成したカスタムのnginxイメージで、`docker.io/library/nginx`が、カスタムのnginxイメージを作成するうえでベースとして使用したコンテナイメージになります。
ではこのコンテナイメージから、`docker container run`コマンドでコンテナを起動します。
```bash
$ docker container run -d -p 10000:80 localhost/custom-nginx:latest
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
4586930a4c616e5afcc4f978b243bcf6aa06f8dd7bb1d0b14d1542d08e1e765d
```
`docker container ls`コマンドで、コンテナの起動を確認します。
```bash
$ docker container ls
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
CONTAINER ID  IMAGE                          COMMAND               CREATED         STATUS         PORTS                  NAMES
4586930a4c61  localhost/custom-nginx:latest  nginx -g daemon o...  41 seconds ago  Up 41 seconds  0.0.0.0:10000->80/tcp  nostalgic_kepler
```
無事に起動しているので、ブラウザで`http://localhost:10000`にアクセスすると、カスタムのページが表示されます。

# Dockerfileでよく使うコマンド
よく使うコマンドは以下のようなものになります。
| コマンド      | 説明                                                                                        | 使用例                                                      |
|---------------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------|
| `FROM`        | ベースとなるイメージを指定します。                                                            | `FROM ubuntu:20.04`                                         |
| `RUN`         | イメージのビルド時に実行するコマンドを指定します。                                            | `RUN apt-get update && apt-get install -y nginx`            |
| `CMD`         | コンテナ起動時に実行するデフォルトのコマンドを指定します。                                     | `CMD ["nginx", "-g", "daemon off;"]`                        |
| `ENTRYPOINT`  | コンテナ起動時に実行するメインのコマンドを指定します。                                         | `ENTRYPOINT ["docker-entrypoint.sh"]`                       |
| `COPY`        | ホストシステムからコンテナにファイルまたはディレクトリをコピーします。                         | `COPY myapp/ /usr/src/myapp/`                               |
| `ADD`         | `COPY` と似ていますが、URLからのダウンロードやアーカイブの自動解凍もサポートします。           | `ADD myapp.tar.gz /usr/src/myapp/`                          |
| `ENV`         | 環境変数を設定します。ビルド時と実行時に使用されます。                                         | `ENV APP_ENV=production`                                    |
| `EXPOSE`      | コンテナがリッスンするネットワークポートを指定します。メタデータの一種で、実際に何か操作を行うわけではなく、コンテナがリッスンするポートを分かりやすくしています。                                        | `EXPOSE 80`                                                 |
| `WORKDIR`     | 作業ディレクトリを設定します。この命令以降の、`RUN`コマンド等のコマンドが実行されるディレクトリを指定します。                                                                 | `WORKDIR /usr/src/myapp`                                    |
| `USER`        | コンテナ内でコマンドを実行するユーザーを指定します。                                           | `USER appuser`                                              |
| `VOLUME`      | ホストとコンテナ間でマウントされるボリュームを指定します。                                     | `VOLUME /var/lib/mysql`                                     |
| `ARG`         | ビルド時に渡される引数を指定します。                                                           | `ARG build_env=production`                                  |
| `LABEL`       | メタデータをイメージに追加します。バージョン情報やメンテナンス情報を含めるのに便利です。       | `LABEL version="1.0" description="A simple example"`        |
| `ONBUILD`     | このイメージが他のイメージのベースとして使用されたときに実行するコマンドを指定します。         | `ONBUILD ADD . /app/src`                                    |

# Dockerfileの使用例
上記で紹介したコマンドを使って、もう少し発展的なDockerfileを作成してみます。次に作成するイメージは、`ubuntu:20.04`をベースに、`nginx`をインストールし、カスタムhtmlファイルを表示します。また、環境変数の設定、コンテナイメージにラベルを追加、コンテナが使用するポートの明示などを行います。
```Dockerfile
FROM ubuntu:20.04

# メタデータの追加
LABEL maintainer="yourname@example.com" version="1.0"

# 環境変数の設定
ENV APP_ENV=production

# 作業ディレクトリの設定
WORKDIR /usr/src/app

# パッケージのインストール
RUN apt-get update && apt-get install -y nginx

# アプリケーションコードのコピー
COPY html/ /usr/share/nginx/html/

# ポートの公開
EXPOSE 80

# デフォルトのコマンド
CMD ["nginx", "-g", "daemon off;"]
```
`docker image build -t custom-nginx-from-ubuntu .`コマンドを実行して、上記のDockerfileからコンテナイメージをビルドします。

## LABELでイメージをフィルタリング
`LABEL`コマンドでラベル付しているので、フィルタリングすることができます。ここでは、`docker image ls`コマンド実行時にフィルタリングする方法を紹介します。
まず、いまあるイメージを`docker image ls`コマンドで確認します。
```bash
$ docker image ls
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY                          TAG         IMAGE ID      CREATED        SIZE
localhost/custom-nginx-from-ubuntu  latest      49badc0cc0e3  2 minutes ago  188 MB
docker.io/library/ubuntu            20.04       5f5250218d28  6 days ago     75.2 MB
```
`localhost/custom-nginx-from-ubuntu`と、`docker.io/library/ubuntu`の二つのイメージがあることが分かります。ラベルでフィルターして、`localhost/custom-nginx-from-ubuntu`のみを表示してみます。
フィルタリングする場合、`--filter`オプションを使います。今回作成したカスタムイメージには、`maintainer`というラベルをキーとして付与したので、このキーを持つイメージを絞り込みます。`docker image ls --filter "label=maintainer"`コマンドを使います。
```bash
$ docker image ls --filter "label=maintainer"
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY                          TAG         IMAGE ID      CREATED        SIZE
localhost/custom-nginx-from-ubuntu  latest      49badc0cc0e3  2 minutes ago  188 MB
```
このように、該当のラベルを持つイメージだけが表示されます。
また、ラベルはKey-Valueの形で指定しているので、Key-Valueの形でフィルタリングすることもできます。`LABEL`を同じKey、違うValueで作成したDockerfileから、`custom-nginx-from-ubuntu2`というイメージをビルドしました。ラベルが`maintainer`のキーを持つものでイメージを絞り込むと、以下のように二つのイメージが絞り込まれます。
```bash
$ docker image ls --filter "label=maintainer"
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY                           TAG         IMAGE ID      CREATED        SIZE
localhost/custom-nginx-from-ubuntu2  latest      3fd56fc54470  2 minutes ago  188 MB
localhost/custom-nginx-from-ubuntu   latest      49badc0cc0e3  9 minutes ago  188 MB
```
ここから、Valueの値をもとに絞り込んでみます。`maintainer=yourname@example.com`のものを絞り込むと、
```bash
$ docker image ls --filter "label=maintainer=yourname@example.com"
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY                          TAG         IMAGE ID      CREATED         SIZE
localhost/custom-nginx-from-ubuntu  latest      49badc0cc0e3  11 minutes ago  188 MB
```
このように最初に作成したほうのカスタムイメージが表示されます。また、`maintainer=yourname2@example.com`で絞り込むと、
```bash
$ docker image ls --filter "label=maintainer=yourname2@example.com"
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
REPOSITORY                           TAG         IMAGE ID      CREATED        SIZE
localhost/custom-nginx-from-ubuntu2  latest      3fd56fc54470  4 minutes ago  188 MB
```
このように後に作成したイメージが表示されます。

## 環境変数を確認
稼働しているコンテナに接続して、環境変数が正しく設定されているか確認してみます。
まずはコンテナを`docker run`コマンドで実行します。
```bash
$ docker run -d -p 10000:80 custon-nginx-from-ubuntu 
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
a90bcbc31d3567e658a20ac2f84060cfb4fe0dcdefbe8d4d5eb72ff576a44a1e
```
コンテナに入る（コンテナで新しいシェルセッションを開始する）には、`docker exec`コマンドを`-it`オプションを合わせて実行します。今回起動したコンテナのIDは、`docker run`コマンドの時の出力から`a90~`であることが分かったので、`a`をコンテナIDとして指定しています。また、シェルを実行したいため、実行するコマンドには`/bin/bash`を指定しています。
```bash
$ docker exec -it a /bin/bash
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
root@a90bcbc31d35:/usr/src/app# 
```
それでは、Dockerfile内で指定した環境変数を確認してみます。
```bash
root@a90bcbc31d35:/usr/src/app# echo $APP_ENV
production
```
このように、正しく設定されていることが分かります。

## ENTRYPOINTとCMDの違い
ぱっと見よく似た`ENTRYPOINT`と`CMD`ですが、当然異なる使い道で使われます。よくあるのは、
- `ENTRYPOINT`でデフォルトで実行するコマンドを指定する
- `CMD`でデフォルト引数を指定する

という使われ方があります。例えば、
```Dockerfile
FROM ubuntu:20.04

ENTRYPOINT ["echo"]
CMD ["hello"]
```
のようなDockerfileをビルドすると、デフォルトでは以下の様に実行されます。
```bash
$ docker build -t entry-cmd .
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
STEP 1/3: FROM ubuntu:20.04
STEP 2/3: ENTRYPOINT ["echo"]
--> 52812f7b33e5
STEP 3/3: CMD ["hello"]
COMMIT entry-cmd
--> 021f720c1602
Successfully tagged localhost/entry-cmd:latest
021f720c1602a360bcedb797e22350746198086982311c678e4065e021ffd6fd

$ docker run entry-cmd
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
hello # CMDで指定したデフォルト引数のhelloが出力される
```
`docker run`のときの引数として異なる値を渡すと、以下のようになります。
```bash
$ docker run entry-cmd world
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
world
```
`ENTRYPOINT`で指定されたコマンドは、`docker run`時に`--entrypoint`オプションをつけて上書きしない限り必ず実行されます。`CMD`で指定されたものは、`docker run`時に引数として与えることでそのまま上書きされます。そのため、このような使いわけを行います。
ちなみに、下記ように`ENTRYPOINT`と`CMD`を入れ替えて実行することはできません（`ENTRYPOINT`で指定されたものがコマンドとして認識されるため）。
```Dockerfile
FROM ubuntu:20.04

CMD ["echo"]
ENTRYPOINT ["hello"]
```
```bash
$ docker run entry-cmd
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
Error: runc: runc create failed: unable to start container process: exec: "hello": executable file not found in $PATH: OCI runtime attempted to invoke a command that was not found
```

# まとめ
以上、Dockerfileの基本的な使い方でした。次回はシンプルなウェブアプリを実装し、Dockerで実行する場合とそうでない場合を比較したいと思います。
[次の記事](https://qiita.com/goroneko/items/8024bee8e8f58da49101 "")