Docker初心者のDockerfileを理解する COPY, ADD, CMD, ENTRYPOINT, ENV, WORKDIR(Mac)

スポンサーリンク
Docker
この記事は約26分で読めます。

こんにちは!ともわん(@TomoOne4)です。

僕は、IT企業でマーケティングやセールスをやっています。

Dockerについての第8回目投稿です。

今回は、「Docker Instructionで覚えておくべきCOPY, ADD, CMD, ENTRYPOINT, ENV, WORKDIR」を扱います。

前の記事で、Docker InstructionのFROM、RUN、CMDについては取り上げました。今回はその続きになります。

ちょっと盛りだくさんかもです(^_^;)

今回のポイント

  1. COPY:
    build contextの中にあるDockerfile以外のファイルを、Docker Imageに組み込むみ、コンテナで使う事ができる
  2. ADDとCOPYの違い
  3. build contextにないDockerfileを利用するには
  4. ENTRYPOINT:
    docker run時に上書きできないようにするデフォルトコマンド指定
  5. ENTRYPOINTとCMDの違い
  6. ENV:
    環境変数を設定する
  7. WORKDIR:
    Docker Instructionの実行ディレクトリを変更

ここで書く内容は、Udemy で受講できる、
かめ れおんさんの米国AI開発者がゼロから教えるDocker講座

で学んだ内容を自分の中で定着するために調べたり考えたりした内容を踏まえて進めていきます。

導入は文章より動画のほうがすっと入ってくる人も多いと思いますので、少し記事を読んでいただき

「Docker面白そうだし、簡単そう」

と思った人はぜひUdemyで米国AI開発者がゼロから教えるDocker講座を受けてみてください。

Dockerやコンテナ、仮想化については第1回、第2回、第3回、第4回、第5回、第6回、第7回がありますのでこちらをみてみてください!

スポンサーリンク

COPY

【COPYの役割】
build contextの中にあるDockerfile以外のファイルやフォルダを、Docker Imageに組み込むみ、コンテナで使う事ができるDocker Instruction

使い方としてはこのようになります。

【COPYの書き方】
COPY <src(対象のファイル)> <destination(置き先の場所)>

例えば、以下のようなDockerfileにすると、COPY部分はどうなるでしょうか?

FROM ubuntu:latest
RUN mkdir /new_dir
COPY new_file /new_dir

build context内にあるnew_fileというファイルを、コンテナ内の/new_dir/配下にコピーするようにすることができます。

TomoOne
TomoOne

実は、ADDでもこのCOPYと同じようなことができるのですが、違いは?

ADDとCOPYの違い

【ADDとCOPYの違い】
・単純にファイルやフォルダをコピーする場合はCOPY
・tarの圧縮ファイルをコピーして解凍したいときにADD(ファイルの容量が大きい場合は、tarで圧縮する)

※つまり、ADDはCOPYよりも機能が多い

ADDの書き方はこちらです。

【ADDの書き方】
ADD <src(対象のファイル)> <destination(置き先の場所)>


※ADDだと、tarが解凍されてコンテナに入る

大きいファイルは圧縮して送るというのは基本なので、結構このADDも使いそうです。

試しにやってみます。

ディレクトリは、/Users/Username/docker/test を作ってやっていきます。

$ mkdir sample_folder

$ ll
drwxr-xr-x  2 Username  staff    64B Aug 18 13:32 sample_folder

$ cd sample_folder

$ echo 'hello japan' > hello-japan

$ ll
total 8
-rw-r--r--  1 Username  staff    12B Aug 18 13:33 hello-japan

$ cat hello-japan
hello japan

$ cd ../

$ ll
drwxr-xr-x  3 Username  staff    96B Aug 18 13:33 sample_folder

$ pwd
/Users/Username/docker/test

$ tar -cvf compressed-hello-japan.tar sample_folder
a sample_folder
a sample_folder/hello-japan

$ ll
total 8
-rw-r--r--  1 Username  staff   2.5K Aug 18 13:35 compressed-hello-japan.tar
drwxr-xr-x  3 Username  staff    96B Aug 18 13:33 sample_folder

ここまでで、sample_folderを作成し、そこに”hello japan”というテキストが書いてあるhello-japanというファイルを作成しました。

念の為、catで中身を確認し、/Users/Username/docker/testまで移動します。

そこで、tarコマンドでsample_folderをcompressed-hello-japan.tarという名前で圧縮しました。
(オプション指定している-cvfの意味は、-c:アーカイブを新規に作成、-v:アーカイブ結果を表示する、-f:アーカイブファイル名を指定する)

404 Not Found - Qiita - Qiita

続いてDockerfileを書いていきます。

FROM ubuntu:latest
ADD compressed-hello-japan.tar /

それではbuildしてrunしていきましょう。

$ docker build .

Sending build context to Docker daemon  6.656kB
Step 1/2 : FROM ubuntu:latest
 ---> adafef2e596e
Step 2/2 : ADD compressed-hello-japan.tar /
 ---> 877f966c9fcb
Successfully built 877f966c9fcb
$ docker run -it --rm 877f966c9fcb bash

root@7990932cb510:/# ls -l
total 52
lrwxrwxrwx   1 root root       7 Jul  3 01:56 bin -> usr/bin
drwxr-xr-x   2 root root    4096 Apr 15 11:09 boot
drwxr-xr-x   5 root root     360 Aug 18 04:40 dev
drwxr-xr-x   1 root root    4096 Aug 18 04:40 etc
drwxr-xr-x   2 root root    4096 Apr 15 11:09 home
lrwxrwxrwx   1 root root       7 Jul  3 01:56 lib -> usr/lib
lrwxrwxrwx   1 root root       9 Jul  3 01:56 lib32 -> usr/lib32
lrwxrwxrwx   1 root root       9 Jul  3 01:56 lib64 -> usr/lib64
lrwxrwxrwx   1 root root      10 Jul  3 01:56 libx32 -> usr/libx32
drwxr-xr-x   2 root root    4096 Jul  3 01:57 media
drwxr-xr-x   2 root root    4096 Jul  3 01:57 mnt
drwxr-xr-x   2 root root    4096 Jul  3 01:57 opt
dr-xr-xr-x 164 root root       0 Aug 18 04:40 proc
drwx------   2 root root    4096 Jul  3 02:00 root
drwxr-xr-x   1 root root    4096 Jul  6 21:56 run
drwxr-xr-x   2  501 dialout 4096 Aug 18 04:33 sample_folder
lrwxrwxrwx   1 root root       8 Jul  3 01:56 sbin -> usr/sbin
drwxr-xr-x   2 root root    4096 Jul  3 01:57 srv
dr-xr-xr-x  12 root root       0 Aug 18 04:40 sys
drwxrwxrwt   2 root root    4096 Jul  3 02:00 tmp
drwxr-xr-x   1 root root    4096 Jul  3 01:57 usr
drwxr-xr-x   1 root root    4096 Jul  3 02:00 var
root@7990932cb510:/# cd sample_folder/
root@7990932cb510:/sample_folder# ll
total 12
drwxr-xr-x 2  501 dialout 4096 Aug 18 04:33 ./
drwxr-xr-x 1 root root    4096 Aug 18 04:40 ../
-rw-r--r-- 1  501 dialout   12 Aug 18 04:33 hello-japan
root@7990932cb510:/sample_folder# cat hello-japan
hello japan
root@7990932cb510:/sample_folder#

確認した結果、sample_folderの中に、先程作ったcompressed-hello-japan.tarが解答されてhello-japanというファイルとして存在していることがわかります。

catで中身を見ても、”hello japan”のテキストが確認できるので問題なく実行されたことがわかります。

Dockerfileがbuild contextにない場合

次は、Dockerfileがbuild contextにない以下の図のような場合です。

どうするかも書いてしまっているのですが、docker buildコマンドのオプションを使って、利用したいDockerfileの場所とファイル名を指定する事ができます。

【Dockerfileを指定してdocker buildする】
$ docker build -f <dockerfilename> <build context>


-f オプションで、Dockerfileの場所を指定して上げれば良いです。
build contextは、今の場所を示す. を使うことが多いかもしれません。

これは、どんな場合に使うのかというと、

Dockerfileを複数用意している場合です。

例えば、

開発用に作った、Dockerfile.dev

検証用に変更を加えた、Dockerfile.stg

テスト用に作った、Dockerfile.test

などが考えられますし、プロジェクトごとに別のDockerfileがあるということもあるでしょう。

なので、結構使う状況が多いです。

実際に確認としてやってみます。

build contextは、/Users/Username/docker/test/sample_folder にします。

そして、Dockerfileは、Dockerfile.devという名前にして、build contextの1つ上の階層においてあるとします。

Dockerfile.devの中身は何でもいいいのでこんな感じにしています。

<Dockerfile.dev>
FROM ubuntu:latest
RUN apt-get update && apt-get install -y \
  curl \
  cvs \
  nginx
CMD ["/bin/bash"]

それでは、-fオプションをつけて、Dockerfileの場所とbuild contextの場所を指定して docker buildしてみます。

docker build -f ../Dockerfile.dev .

(結果前半部分省略)
・
・
・
Running hooks in /etc/ca-certificates/update.d...
done.
Removing intermediate container 5e1d20761147
 ---> 704bc0f64dc5
Step 3/3 : CMD ["/bin/bash"]
 ---> Running in a866a86830b1
Removing intermediate container a866a86830b1
 ---> 6936f6cd25fe
Successfully built 6936f6cd25fe

ということで、問題なくSuccessfully builtになったので、Dockerfileとbuild contextを分けて管理することができました!

CMDとENTRYPOINTの違い

CMDは、デフォルトのコマンドを設定できるDocker Instructionで、

ENTRYPOINTはも、デフォルトのコマンドを指定できるDocker Instructionです。

でも、大きな違いがあります。

【CMDとENTRYPOINTの違い】
・ENTRYPOINTは、docker run時に上書きできない
→docker runのときに上書きしてほしくないコマンドはENTRYPOINTにする

・ENTRYPOINTがある場合は、CMDは [“param1”, “param2”, …]の形をとる
→ENTRYPOINTで指定したコマンドの引数

・docker run時に上書きできるのはCMDの部分のみ

・コンテナをコマンドのようにして使いたいときに使う(CMDでは、オプション部分の指定にすれば、上書きできないENTRYPOINTで指定したコマンドに対して変更できるCMDで指定したオプションという構図になる。)

つまり、docker run時に上書きできるかどうかで使い分けるということになります。

図と同じことを試しにやってみます。

Dockerfileは以下にしています。

FROM ubuntu:latest
RUN touch test
ENTRYPOINT [ "ls" ]
CMD [ "--help" ]

docker build と docker runをします。

$ docker build .

Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu:latest
 ---> adafef2e596e
Step 2/4 : RUN touch test
 ---> Using cache
 ---> b458baab3c30
Step 3/4 : ENTRYPOINT [ "ls" ]
 ---> Running in 356c8926adac
Removing intermediate container 356c8926adac
 ---> fd7368c45754
Step 4/4 : CMD [ "--help" ]
 ---> Running in cedad605f363
Removing intermediate container cedad605f363
 ---> 686b8355c0e6
Successfully built 686b8355c0e6


$ docker run 686b8355c0e6

Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.

Mandatory arguments to long options are mandatory for short options too.
  -a, --all                  do not ignore entries starting with .
  -A, --almost-all           do not list implied . and ..
      --author               with -l, print the author of each file
  -b, --escape               print C-style escapes for nongraphic characters
      --block-size=SIZE      with -l, scale sizes by SIZE when printing them;
                               e.g., '--block-size=M'; see SIZE format below
  -B, --ignore-backups       do not list implied entries ending with ~
  -c                         with -lt: sort by, and show, ctime (time of last
・
・
・

docker runしたところ、ls –helpが実行されました。

そこで、CMD部分を変更してみます。

docker run 686b8355c0e6 -la

total 56
drwxr-xr-x   1 root root 4096 Aug 18 11:21 .
drwxr-xr-x   1 root root 4096 Aug 18 11:21 ..
-rwxr-xr-x   1 root root    0 Aug 18 11:21 .dockerenv
lrwxrwxrwx   1 root root    7 Jul  3 01:56 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15 11:09 boot
drwxr-xr-x   5 root root  340 Aug 18 11:21 dev
drwxr-xr-x   1 root root 4096 Aug 18 11:21 etc
drwxr-xr-x   2 root root 4096 Apr 15 11:09 home
・
・
・

lsコマンドのオプション部分が上書きされ、 ls -la の結果が出せました。

ENV

【ENVの特徴】
環境変数を設定する

環境変数は、ユーザーやプログラムから参照や設定ができるようにしたもので、OSが保存します。

環境変数とは - IT用語辞典 e-Words
環境変数とは、オペレーティングシステム(OS)が設定値などを永続的に保存し、利用者や実行されるプログラムから設定・参照できるようにしたもの。プログラムの実行時などに必要となる、利用者やコンピュータごとに内容が異なる設定値などを記録するために...

例えば、以下のコマンドを実行すると確認できます。

$ echo $SHELL
/bin/bash

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/opt/fzf/bin

Dockerfileでの書き方は2つあります。(どちらでも大丈夫)

【DockerfileでのENVの書き方】
ENV <key> <value>

ENV <key>=<value>…

WORKDIR

【WORKDIRの特徴】
Docker Instructionの実行ディレクトリを変更する

これは結構大事で、実は、Docker Instructionは全てroot直下にて実行されます。

以下のDockerfile例を見てみます。

FROM ubuntu:latest
RUN mkdir sample_folder
RUN cd sample_folder
RUN touch sample_file

こうしたときに、

4行目に書いたsample_fileはどこに作られるでしょうか?

実は、これがこのままでは、sample_fileはsample_folderの中ではなく、root直下に作成されるのです。

論より証拠、見てみます。

$ docker build .

Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu:latest
 ---> adafef2e596e
Step 2/4 : RUN mkdir sample_folder
 ---> Running in baf5e8e0fd4c
Removing intermediate container baf5e8e0fd4c
 ---> 7632dd036d25
Step 3/4 : RUN cd sample_folder
 ---> Running in 7d1408e880a8
Removing intermediate container 7d1408e880a8
 ---> e17f2b8eb892
Step 4/4 : RUN touch sample_file
 ---> Running in 935e8d6b9bea
Removing intermediate container 935e8d6b9bea
 ---> 664c29434ae1
Successfully built 664c29434ae1


$docker run -it --rm 664c29434ae1 bash

root@10e9146978b0:/# ll
total 60
drwxr-xr-x   1 root root 4096 Aug 18 11:35 ./
drwxr-xr-x   1 root root 4096 Aug 18 11:35 ../
-rwxr-xr-x   1 root root    0 Aug 18 11:35 .dockerenv*
lrwxrwxrwx   1 root root    7 Jul  3 01:56 bin -> usr/bin/
drwxr-xr-x   2 root root 4096 Apr 15 11:09 boot/
drwxr-xr-x   5 root root  360 Aug 18 11:35 dev/
drwxr-xr-x   1 root root 4096 Aug 18 11:35 etc/
drwxr-xr-x   2 root root 4096 Apr 15 11:09 home/
lrwxrwxrwx   1 root root    7 Jul  3 01:56 lib -> usr/lib/
lrwxrwxrwx   1 root root    9 Jul  3 01:56 lib32 -> usr/lib32/
lrwxrwxrwx   1 root root    9 Jul  3 01:56 lib64 -> usr/lib64/
lrwxrwxrwx   1 root root   10 Jul  3 01:56 libx32 -> usr/libx32/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 media/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 mnt/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 opt/
dr-xr-xr-x 168 root root    0 Aug 18 11:35 proc/
drwx------   2 root root 4096 Jul  3 02:00 root/
drwxr-xr-x   1 root root 4096 Jul  6 21:56 run/
-rw-r--r--   1 root root    0 Aug 18 11:35 sample_file
drwxr-xr-x   2 root root 4096 Aug 18 11:35 sample_folder/
lrwxrwxrwx   1 root root    8 Jul  3 01:56 sbin -> usr/sbin/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 srv/
dr-xr-xr-x  12 root root    0 Aug 18 04:41 sys/
drwxrwxrwt   2 root root 4096 Jul  3 02:00 tmp/
drwxr-xr-x   1 root root 4096 Jul  3 01:57 usr/
drwxr-xr-x   1 root root 4096 Jul  3 02:00 var/

root@10e9146978b0:/# cd sample_folder/

root@10e9146978b0:/sample_folder# ll
total 8
drwxr-xr-x 2 root root 4096 Aug 18 11:35 ./
drwxr-xr-x 1 root root 4096 Aug 18 11:35 ../

わかりましたか?root直下のsample_folderの上にsamplef_fileができてしまっています。

本来作りたいsample_folderを見てみると、sample_fileは存在していません。

そこで、DockerfileにWORKDIRで作業用ディレクトリを指定してあげるということをします。

Dockerfileを修正します。

FROM ubuntu:latest
RUN mkdir sample_folder
WORKDIR /sample_folder
RUN touch sample_file

こうすると、WORKDIRで指定したディレクトリ内で、次以降のDocker Instructionが実行されるようになります。

docker build .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu:latest
 ---> adafef2e596e
Step 2/4 : RUN mkdir sample_folder
 ---> Using cache
 ---> 7632dd036d25
Step 3/4 : WORKDIR /sample_folder
 ---> Running in 254edc90f641
Removing intermediate container 254edc90f641
 ---> 38c3fb630ed3
Step 4/4 : RUN touch sample_file
 ---> Running in 5866369b8072
Removing intermediate container 5866369b8072
 ---> 503b159ffd13
Successfully built 503b159ffd13


docker run -it 503b159ffd13 bash

root@200cf3e27add:/sample_folder#

root@200cf3e27add:/sample_folder# ll
total 8
drwxr-xr-x 1 root root 4096 Aug 18 11:39 ./
drwxr-xr-x 1 root root 4096 Aug 18 11:39 ../
-rw-r--r-- 1 root root    0 Aug 18 11:39 sample_file

WORKDIRで指定したままなので、コンテナに入ったときのディレクトリは指定したsample_folder になっています。

そのsample_folderの中を見てみると、できていますね!sample_fileが!

それでは、root直下にsample_fileが無いことも確認してみます。

root@200cf3e27add:/sample_folder# ll ../
total 60
drwxr-xr-x   1 root root 4096 Aug 18 11:39 ./
drwxr-xr-x   1 root root 4096 Aug 18 11:39 ../
-rwxr-xr-x   1 root root    0 Aug 18 11:39 .dockerenv*
lrwxrwxrwx   1 root root    7 Jul  3 01:56 bin -> usr/bin/
drwxr-xr-x   2 root root 4096 Apr 15 11:09 boot/
drwxr-xr-x   5 root root  360 Aug 18 11:40 dev/
drwxr-xr-x   1 root root 4096 Aug 18 11:39 etc/
drwxr-xr-x   2 root root 4096 Apr 15 11:09 home/
lrwxrwxrwx   1 root root    7 Jul  3 01:56 lib -> usr/lib/
lrwxrwxrwx   1 root root    9 Jul  3 01:56 lib32 -> usr/lib32/
lrwxrwxrwx   1 root root    9 Jul  3 01:56 lib64 -> usr/lib64/
lrwxrwxrwx   1 root root   10 Jul  3 01:56 libx32 -> usr/libx32/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 media/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 mnt/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 opt/
dr-xr-xr-x 164 root root    0 Aug 18 11:40 proc/
drwx------   2 root root 4096 Jul  3 02:00 root/
drwxr-xr-x   1 root root 4096 Jul  6 21:56 run/
drwxr-xr-x   1 root root 4096 Aug 18 11:39 sample_folder/
lrwxrwxrwx   1 root root    8 Jul  3 01:56 sbin -> usr/sbin/
drwxr-xr-x   2 root root 4096 Jul  3 01:57 srv/
dr-xr-xr-x  12 root root    0 Aug 18 04:41 sys/
drwxrwxrwt   2 root root 4096 Jul  3 02:00 tmp/
drwxr-xr-x   1 root root 4096 Jul  3 01:57 usr/
drwxr-xr-x   1 root root 4096 Jul  3 02:00 var/

sample_fileが無いので、想定通りの挙動にすることができています。

TomoOne
TomoOne

ちなみに、Dockerfileを

FROM ubuntu:latest

WORKDIR /sample_folder

RUN touch sample_file

と書いても同じ結果になります。

※WORKDIRで指定したフォルダがなければ、作ってくれるという機能付き。

まとめ:ここまでのDocker Instructionで大体なんとかなる?

お疲れさまでした。

ここまでで、FROMからWORKDIRまでのDocker Instructionを学んできました。ここまでわかればDocker HubでいろいろなDocker ImageのDockerfileを見てみると、解読ができるようになってくるはずです。

試しに今までめちゃくちゃ使ってきたUbuntuのDockerfileを見てみてみるとやっていることがなんとなくわかると思います。

docker-brew-ubuntu-core/bionic/Dockerfile at 3d44d0b838eeb78c5baee976a4a529976b326878 · tianon/docker-brew-ubuntu-core
DEPRECATED; see - tianon/docker-brew-ubuntu-core

自分でもDockerfileを作って色々コンテナ作っていけば上達していきますのでぜひどんどん作っていきましょう。

教材と参考資料

米国AI開発者がゼロから教えるDocker講座
AI開発のプロが徹底的に現場目線でわかりやすく教えます.Dockerを使って超本格的なデータサイエンスやWeb開発の環境が作れるようになります.

タイトルとURLをコピーしました