Dockerfileの使い方について

このチュートリアルでは、**Alibaba Cloud**上での**Dockerfile**の使用方法について実践的な経験を積むことに焦点を当てています。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/getting-to-know-dockerfile-instructions-part-3_594332)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#Dockerfile RUN
参考情報：[https://docs.docker.com/engine/reference/builder/#run](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#run)

>RUNには2つの形式があります。

>RUN (シェル形式。コマンドはシェルで実行され、デフォルトでは /bin/sh -c)

>RUN "executeable", "param1", "param2"

RUN 命令は与えられた命令を実行し、その結果の出力を現在の画像の上に新しいレイヤーで作成します。

RUN は、おそらくイメージの最も大きな部分を作成するために使用されます。これは、サーバや vps を設定する際にシェルで使用するコマンドのほとんどを実行します。

例:

```
RUN mkdir ...
RUN useradd ..
RUN apt install ...
RUN copy ...
RUN unzip ...
```

使用中のexecフォームとshellフォームを表示する簡単なDockerfileを作成してみましょう。

execフォームはコマンドshellを呼び出しません。つまり、通常のshell処理が行われないことを意味します。

```
nano Dockerfile 
```

内容です。

```
FROM alpine:3.8
RUN echo $HOME
RUN ["echo", "$HOME”]
```

実行してください。

```
docker build --no-cache --tag tutorial:demo --file Dockerfile .
```

期待される出力 

```
Sending build context to Docker daemon    258kB
Step 1/3 : FROM alpine:3.8
 ---> 196d12cf6ab1
Step 2/3 : RUN echo $HOME
 ---> Running in f9aed6be9bb4
/root
Removing intermediate container f9aed6be9bb4
 ---> dd939eaa9e43
Step 3/3 : RUN ["echo", "$HOME"]
 ---> Running in 38e244f781bc
$HOME
Removing intermediate container 38e244f781bc
 ---> 3872c058a5bc
Successfully built 3872c058a5bc
Successfully tagged tutorial:demo
```

shell フォーム - ステップ 2 でシェル変数の置換が行われることに注意してください。

exec フォーム - ステップ 3 でシェル変数の置換が行われないことに注意してください。

#Dockerfile CMD
[https://docs.docker.com/engine/reference/builder/#cmd](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#cmd)

CMD と ENTRYPOINT の命令は、それらに特化した長いチュートリアルに値するものです。

ここでの演習は、これらのコマンドの5%の基本的な導入に過ぎません。

あなたのコンテナは、他の人にソフトウェアサービスを提供するために存在しています。CMD と ENTRYPOINT は、コンテナの起動時にどのようなソフトウェアを実行するかを定義する方法です。Linux の機能の全体像は広大です。残念ながら、CMD と ENTRYPOINT は、あらゆるユースケースを可能にするために、無限に強力な (いじれる) ものでなければならないことを意味しています。

この一連のDockerfileチュートリアルを終えた後に、[https://hub.docker.com/explore/](https://hub.docker.com/search?q=&type=image) にアクセスすることをお勧めします。ダウンロードされた上位10個のイメージのURLにアクセスしてください。一番上に掲載されているDockerfileをクリックしてください。CMDとENTRYPOINTの使い方を勉強してください。これがあなたをDockerfileのエキスパートにしてくれるものです：他の簡単なDockerfileの説明書の組み合わせではなく、CMDとENTRYPOINTの洞察力に富んだ使い方です。

エキスパートボーナスの洞察と加速された学習のために、これらのイメージをすべてダウンロードして、開発サーバーで実行してください。また、docker runを使用する場合は、CMDとENTRYPOINTをオーバーライドします。

アリババは、[https://www.alibabacloud.com/product?spm=a3c0i.7911826.1160486.82.7d90737b0qnGmg](https://www.alibabacloud.com/ja/product) で利用可能なクラウドサービスソフトウェアの広大なセットを持っています。

それらをどのように付加価値のある方法でマージし、組み合わせ、接続するかで成功が決まります。CMDとENTRYPOINTはDockerコンテナでソフトウェアを公開する方法です。

ここでは、私の一日目（一行のDockerfile）の初心者向けのエクササイズを紹介します。

残念ながらDockerfileにはCMD命令は一つしかありません。そのため、いくつかの小さなDockerfileを使ってCMDの違いをデモする必要があります。

[https://docs.docker.com/engine/reference/builder/#cmd](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#cmd) より

>CMD 命令には 3 つの形式があります。

>1、CMD "executable"、"param1"、"param2 "
>2、CMD "param1", "param2”
>3、CMD コマンド param1 param2 (シェル形式)

[https://docs.docker.com/engine/reference/builder/#cmd](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#cmd) より

>exec フォームは JSON 配列として解析されるので、単語の周りにはシングルクォート (') ではなくダブルクォート (") を使用しなければなりません。

nano Dockerfile

```
FROM alpine:3.8
CMD ["/bin/echo", "hello from CMD - using exec form”]
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
docker run --name tutorial tutorial:demo
```

期待される出力

```
hello from CMD - using exec form
```

今度はシェルフォームを使ってCMDします。

```
FROM alpine:3.8
CMD /bin/echo "hello from CMD using shell form”
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker run --name tutorial tutorial:demo
```


期待される出力は以下の通りです。

```
hello from CMD using shell form
```

exec フォームはコマンドシェルを呼び出しません - つまり、シェル変数の置換はありません。

シェル処理機能が必要な場合は、shellフォームを使用してください。

CMD /bin/echo $HOME は あなたの $HOME ディレクトリを表示します。

CMD [ "echo", "$HOME" ] を実行すると、$HOME ディレクトリは表示されませんが、$HOME はそのまま表示されます。

ここでは、shellフォームを使って$HOMEの内容を表示してみましょう。

実行してみましょう。

```
nano Dockerfile
```

このテキストを入力してください

```
FROM alpine:3.8
CMD /bin/echo $HOME 
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker run --name tutorial tutorial:demo
```

期待される出力。

```
/root
```
Linux shellでは、** /bin/echo $HOME **を使って$HOMEの値を表示します。DockerfileでCMD命令のshell構文を使うのと全く同じように動作します。

以下のようにしてイメージを実行していることに注意してください。

```
docker run --name tutorial tutorial:demo
```


CMD命令はイメージの実行時に実行するコマンドを定義します。dockerでの実行では、実行するコマンドを指定していません。CMD は、この場合 echo が実行されなければならないことを定義しています。

**RUN 命令**はイメージをビルドするために使用します。これはビルド時に実行されます。

**CMD命令**はビルド時には何も実行しませんが、docker run時に実行するコマンドを指定します。

Dockerfileで定義されているCMDをオーバーライドすることができます。これを行うには、イメージの実行時に実行するコマンドを指定します。例えば、

```
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker run -ti -d --name tutorial tutorial:demo /bin/sh -c 'echo ECHO text from run command'
```

期待される出力 .

```
ECHO text from run command
```

なお、DockerfileのCMDからのエコーは実行されません。docker runコマンドからのエコーテキストのみが表示されます。

#Dockerfile ENTRYPOINT
[https://docs.docker.com/engine/reference/builder/#entrypoint](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#entrypoint) より

>ENTRYPOINTには2つの形式があります。

>1、ENTRYPOINT [ "executable"、"param1"、"param2 "](https://www.alibabacloud.com/blog/getting-to-know-dockerfile-instructions-part-3_594332#)です。
>2、ENTRYPOINTコマンド param1 param2 (シェル形式)
>ENTRYPOINTでは、実行ファイルとして実行するコンテナを設定することができます。

>例えば、以下のようにすると、デフォルトの内容でnginxが起動し、ポート80でリッスンされます。

>docker run -i -t --rm -p 80:80 nginx

残念ながら、彼らが与えた例は間違っています: [https://github.com/nginxinc/docker-nginx/blob/a22b9f46fe3a586b02d974f64441a4c07215dc5d/mainline/stretch/Dockerfile](https://github.com/nginxinc/docker-nginx/blob/a22b9f46fe3a586b02d974f64441a4c07215dc5d/mainline/stretch/Dockerfile?spm=a2c65.11461447.0.0.11313212fowgAk) の公式 nginx Dockerfile には ENTRYPOINT がありません。

nginxのDockerfileの最後の行は

CMD ["nginx", "-g", "daemon off;"]

ということで、前に表示されたdocker runを実行すると、CMDはnginxを実行しますが、その特定のDockerfileにはENTRYPOINTがありません。

紛らわしいですね。

では、実際のENTRYPOINT命令を含むミニDockerfileを作成してみましょう。まずはシェルのフォームから。

```
FROM alpine:3.8
ENTRYPOINT /bin/echo ECHO from ENTRYPOINT - shell form
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker run --rm --name tutorial tutorial:demo
```

毎回実行後にコンテナを停止して剪定するのが面倒になってきました。

上記の --rm オプションを使用したことに注意してください。これで終了したコンテナが自動的に削除されます。

rm がどのように動作するかを確認するには、もう一度 docker を実行してください。

```
docker run --rm --name tutorial tutorial:demo
```

期待される出力。

```
ECHO from ENTRYPOINT - shell form 
```
また

```
docker run --rm --name tutorial tutorial:demo
```

同じ出力です。終了したコンテナをクリーンアップ/プルーニングする必要はありません。とても便利ですね。

ENTRYPOINTの実行形式の例。

nano Dockerfile

```
FROM alpine:3.8
ENTRYPOINT ["/bin/echo", "ECHO from ENTRYPOINT - exec form"]
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
docker run --rm --name tutorial tutorial:demo
```

期待される出力 .

```
ECHO from ENTRYPOINT - exec form
```

#Dockerfile VOLUME
[https://docs.docker.com/engine/reference/builder/#volume](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#volume) より

>VOLUME命令は、指定された名前のマウントポイントを作成し、ネイティブホストや他のコンテナから外部マウントされたボリュームを保持していることをマークします。

全くその通りです。VOLUME 命令は、指定された名前のマウント ポイントを作成します。

紛らわしいこと: ネイティブホストまたは他のコンテナから外部マウントされたボリュームを保持しているとマークされます。

これは外部マウントされたボリュームを保持しません - コンテナの内部に新しいボリュームを作成します。

Linuxにおけるボリュームという用語は、論理ボリュームマネージャ(LVM)に関連しています - VOLUME命令はそのような外部ボリュームをマウントしません。

**ネイティブホスト**または他のコンテナ88からの外部マウントされたボリュームを保持するようにそれをマークします。 VOLUME命令には他のコンテナはありません。

VOLUME命令はマウントポイントを作成し、それはDockerが管理する/var/lib/docker/volumes/にディレクトリを作成します。

準備:開発サーバーにボリュームがない場合や少ない場合に役立つでしょう。そこで、このコマンドを実行して、現在のボリュームのリストを確認してください。

```
docker volume list
```

リストが短ければ短いほど、これから作成するボリュームを見やすく、見つけやすくなります。私のリストは空なので、このチュートリアルでは任意の時点で存在するボリュームの完全なリストを表示します。

Dockerの公式ドキュメントにあるDockerfileを使って、何が起こるか見てみましょう。

nano Dockerfile

```
FROM alpine:3.8
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile  .
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker run --rm -ti -d --name tutorial tutorial:demo /bin/sh -c 'while true; do sleep 60; done'
docker exec -it tutorial /bin/sh
```

ls コマンドを入力します - myvol がコンテナ内に存在することを確認します。

cat myvol/greeting と入力します。

期待される出力 .

```
hello world
```

コンテナを終了します。

docker run コマンドは、新しく作成されたボリューム /myvol を、ベースイメージ内の指定された場所 (グリーティングファイル) に存在する任意のデータで初期化します。

myvol という名前は、このコンテナ内にのみ存在します。この名前は他のコンテナでは使用できません。

このDockerfileの結果、docker runが/myvolに新しいマウントポイントを作成し、グリーティングファイルを新しく作成されたボリュームにコピーするイメージになります。

myvolはコンテナ内でのみボリュームの名前とマウントポイントになります。

他のコンテナが myvol 名 / マウントポイントを参照することはできません。

重要: お使いのコンピュータで生成されたボリューム名は異なります。このチュートリアルに従うときは、あなたのボリューム名を使用してください。

```
docker volume list
```

期待される出力 .

```
DRIVER              VOLUME NAME
local               de82d92daf539b7147770877704ed438b053c50744e874e7a6b86655cd50cf44
```

実行します。

```
docker volume inspect de82d92daf539b7147770877704ed438b053c50744e874e7a6b86655cd50cf44
```

期待される出力 .

```
[
    {
        "CreatedAt": "2018-10-23T15:19:14+02:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/de82d92daf539b7147770877704ed438b053c50744e874e7a6b86655cd50cf44/_data",
        "Name": "de82d92daf539b7147770877704ed438b053c50744e874e7a6b86655cd50cf44",
        "Options": null,
        "Scope": "local"
    }
]
```

データがあるマウントポイントをメモしておきます。

```
ls /var/lib/docker/volumes/de82d92daf539b7147770877704ed438b053c50744e874e7a6b86655cd50cf44/_data/
```

グリーティングファイルを表示しています。

今、このコンテナを停止すると（--rmで実行されているので）、コンテナが削除されたときに、Dockerはコンテナに関連付けられた匿名のボリュームも削除します。

そうすると、

```
docker stop -t 0 tutorial ; docker container prune -f;docker ps -a
docker volume list
```

ボリュームがなくなっていることに気づくでしょう。

#anon ボリュームの詳細
再び /myvol でコンテナを実行してみましょう。

```
docker run -ti -d --name tutorial tutorial:demo /bin/sh -c 'while true; do sleep 60; done'
```

docker ボリュームリストには anon ボリューム - ランダムなボリューム名が表示されているはずです。

他にもanonボリューム/myvolを使って3つのAlpineコンテナを起動してみましょう。

```
docker run -d --name alpine1 -v /myvol alpine:3.8  /bin/sh -c 'while true; do sleep 60; done'
docker run -d --name alpine2 -v /myvol alpine:3.8  /bin/sh -c 'while true; do sleep 60; done'
docker run -d --name alpine3 -v /myvol alpine:3.8  /bin/sh -c 'while true; do sleep 60; done'
```

実行します。

```
docker volume list
```

もう一度。

```
Expected output : 

 DRIVER              VOLUME NAME
local               0a43eb2703d4a1638e1a466a9cbae5aba1071939a3b047dc7c27d383b83cebea
local               8edd9ff9f14a6038df3013ea7b581aeda41e6045584b6c350ba4b20e60e6f6a2
local               b02d0f7b3c5be418718b1732be0e46c735806b22791dd70fa44036bb2ffa520e
local               f5a2bb741141616255333c54c421926a8a06ac59f5b054e4619a1dfef4da2fbc
```

4 つのコンテナにはそれぞれ /myvol という名前の自分のボリュームがあります。

ここで実行します。

```
docker stop -t 0 tutorial
docker stop -t 0 alpine1
docker stop -t 0 alpine2
docker stop -t 0 alpine3
```

このチュートリアルでは、anonのボリュームのみを使用しています。

実行します。

```
docker container prune -f;docker ps -a
```

は4つのコンテナが削除されたことを示しています。

実行します。

```
docker volume list
```

4つのボリュームがすべて残っていることを示します。

これらのボリュームを削除するには

```
docker volume prune
```

プロンプトでyを答える。

期待される出力 .

```
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
51742ef4c728f01870e49edcf29ffa0407eee3363c21fd90493a9c786b36f430
0670752370af7bc92e4fbf1ce513dc8b5472169c4d7e8023e6dc207c510a023f
0c797c8a480e12c450870a78a3cc3de3c22e483162882e1972ed399148e3c00e
0e36fa721df75a9bbcf16fbe2aa809845aee016f7f00c2c6bebefbc27b2832c0

Total reclaimed space: 11B
```

ここで学んだ教訓。

1、AnonボリュームはDockerfileのVOLを使って作成されます。
2、Anonボリュームはコンテナを実行しているときに-volumeを使って作成することもできます。
3、Anonボリュームはコンテナの外では友好的な名前はありません。
4、コンテナの実行時に --rm を指定すると、Anon ボリュームは自動的に削除されます。
5、docker volume prune を使用して、少なくとも 1 つのコンテナで使用されていないすべてのボリュームを削除してください。

**名前付き**ボリュームはコンテナの外に作成され、名前を付けて一つずつ意図的に削除しなければなりません。名前付きボリュームは長期的なデータのために使用します。

名前付きボリュームはコンテナの数に制限なくマウントして共有することができます。

Dockerボリュームは膨大なトピックです - 詳しくは [https://docs.docker.com/storage/volumes/](https://docs.docker.com/storage/volumes/?spm=a2c65.11461447.0.0.11313212fowgAk) をご覧ください。

このチュートリアルでは、DockerfileでVOLを使って作成されるanon / anonymousボリュームについて簡単に紹介しました。

anonボリュームの名前を変更する方法はありません - おそらくこれからもありません - [https://github.com/moby/moby/issues/31154](https://github.com/moby/moby/issues/31154?spm=a2c65.11461447.0.0.11313212fowgAk) を参照してください。

きれいな名前のボリュームが欲しい場合は、名前付きボリュームが必要になります。

他のコンテナがこのボリュームを使用したい場合は、長い無名の名前を使用しなければなりません。明らかにこの醜い長い名前は理想的ではないので、コンテナの外に 名前付きボリュームを作成するのが好ましい方法です。そうすれば、すべてのコンテナはこの簡単で記述的なボリューム名を使うことができます。

#Dockerfile LABEL
[https://docs.docker.com/engine/reference/builder/#label](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#label)

画像には無制限にラベルを付けることができます。これらのラベルは画像を説明するために使用できます。

また、ラベルを使って特定のラベルを持つ画像だけを選択的にフィルタリングすることもできます。それでは実際に見てみましょう。

```
nano Dockerfile 
```

入力:

```
FROM alpine:3.8
LABEL version="demo label"
LABEL description="This text illustrates \
that label values can span multiple lines."
CMD echo "container with labels”
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile .
```

イメージのすべてのラベルを表示するには、次のように実行します。

```
docker inspect tutorial:demo
```

そのコマンドからスニペット出力を抽出しました。


```
        "Labels": {
                "description": "This text illustrates that label values can span multiple lines.",
                "version": "demo label"
            }
```

また、実行します。

```
docker images --filter "label=version=demo label”

```

期待される出力 .

```
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
tutorial            demo                16d6a13eb022        3 minutes ago       4.41MB
```

そのデモラベルが付いたイメージだけが表示されました。

docker images コマンドにはラベル欄がないことに注意してください。

(--format) オプションを指定した **docker images** コマンドにもラベルを印刷する機能はありません。

[https://docs.docker.com/engine/reference/commandline/images/#format-the-output](https://docs.docker.com/engine/reference/commandline/images/?spm=a2c65.11461447.0.0.11313212fowgAk#format-the-output)

公式Dockerハブ [https://hub.docker.com](https://hub.docker.com/?spm=a2c65.11461447.0.0.11313212fowgAk) にある公式イメージでラベルを使用しているものはほとんどありません。

しかし、すべての画像に膨大なメタ情報(ラベル)の構造を作成することができます。画像を選択したり分類したりする唯一の方法は、画像が持つことのできる単一のタグを使用することです。

#Dockerfile ARG
[https://docs.docker.com/engine/reference/builder/#arg](https://docs.docker.com/engine/reference/builder/?spm=a2c65.11461447.0.0.11313212fowgAk#arg) より

>ARG 命令は、ユーザがビルド時に --build-arg = フラグを使って docker ビルドコマンドでビルダーに渡すことができる変数を定義します。

ARG 命令を定義する際にデフォルト値を含めることができます。

引数は通常、ユーザー名、ディレクトリ名、ソフトウェアパッケージのバージョン番号などをビルド時に渡すために使われます。

簡単なDockerfileを作成して、実際にARGが使われているのを見てみましょう。

```
nano Dockerfile 
```

これを入力してください。

```
FROM alpine:3.8
ARG target_dir=dir1
WORKDIR /root/${target_dir}
RUN pwd
RUN echo  ${target_dir}
COPY gamefile /root/${target_dir}
COPY anothergame /root/${target_dir}
```

実行します。

```
docker build --tag tutorial:demo --file Dockerfile  .
```

期待される出力 .

```
Sending build context to Docker daemon    258kB
Step 1/7 : FROM alpine:3.8
 ---> 196d12cf6ab1
Step 2/7 :  ** ARG target_dir=dir1 88
 ---> Running in 15ef296ddf95
Removing intermediate container 15ef296ddf95
 ---> fe6be5de15bb
Step 3/7 : ** WORKDIR /root/${target_dir} 88
 ---> Running in a64dbe343a98
Removing intermediate container a64dbe343a98
 ---> e41509ae6f7b
Step 4/7 : ** RUN pwd 88
 ---> Running in 3b886f0637b4
88 /root/dir1 88
```

WORKDIR /root/${target_dir}が${target_dir}をdir1に置き換えることに注意してください。 ステップ7 RUN pwdは.../root/dir1を示しています。

```
Removing intermediate container 3b886f0637b4
 ---> 08468a2fec81
Step 5/7 : ** RUN echo  ${target_dir} 88
 ---> Running in a6a8a8be8252
88  dir1 88
Removing intermediate container a6a8a8be8252
 ---> 48a9ffe90e28
Step 6/7 : COPY gamefile /root/${target_dir}
as ---> a09e827ec1c7
Step 7/7 : COPY anothergame /root/${target_dir}
 ---> d14edc1f440d
Successfully built d14edc1f440d
Successfully tagged tutorial:demo
```

ステップ5 RUN echo ${target_dir}はdir1も表示します。

ステップ6とステップ7では、${target_dir}も同様にdir1に置換されていると考えてよいでしょう。

プログラムを書いている人ならば、変数の置換がどのように行われるか知っているでしょう。ARGも全く同じように動作します。

ARGはビルド時にのみ存在します。環境変数ではありません。bash シェルで PRINTENV を実行しても ARG は表示されません。

これでパート3の4は終了です：Dockerfileのインストラクションをすべて知ってください。もっと詳しく知りたい方は[パート4](https://www.alibabacloud.com/blog/getting-to-know-dockerfile-instructions-part-4_594333?spm=a2c65.11461447.0.0.11313212fowgAk)を読んでください。

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*