docker.svg

【Docker】Dockerfileの基本

Docker

Dockerfileとは

Docker ファイル Docker イメージ コンテナ 停止 起動 build pull rm run rm commit push start stop restart Docker Hub

DockerfileはDockerイメージを作成するための設計図のようなものです。 前回記事では、docker image pullでレジストリからイメージを取得していましたが、実際のユースケースではDockerfileから作成したイメージを利用するのがほとんどです。 Dockerfileには、ビルド時に実行する処理や配備するファイルの管理、環境変数などの設定が可能です。

イメージのビルド

Dockerfileの記述については後述しますが、Dockerfileからイメージを作成するにはdocker image buildコマンド実行します。 以下はカレントディレクトリのDockerfileからイメージを作成するコマンドの例です。 -t(--tag)オプションはタグ名を指定するもので、指定がない場合はリポジトリとタグは<none>になります。

$ docker image build -t myimage:1.0 . 
ビルド コンテキスト REST API Docker デーモン CLI docker image build コピー 読み込み 作成 クライアント サーバー

ビルドコンテキスト

上記コマンドでカレントディレクトリを指定しましたが、これはDockerfileのあるパスを指定しているわけではなく、ビルドコンテキストのパスを指定しています。 ビルドコンテキストが何かという前に、Dockerデーモンについて簡単に説明をしておきます。

DockerデーモンはDockerのコアとなる部分で、イメージやコンテナを作成したり、またその管理を行います。 今までコマンド(CLI)で実行したものは、REST APIを通してDockerデーモンが処理をしています。 REST APIを通す理由は、Dockerデーモンを別の環境からでも操作できるようにしているからです。 Dockerデスクトップでは端末内にCLIとDockerデーモンが同居していますが、サーバーにあるDockerデーモンをクライアントのCLIで操作するといったことができます。

イメージのビルドもDockerデーモンが処理するのですが、その際にビルドに使用するファイル群をコピーして使用します。 このファイル群をビルドコンテキストといいます。 上記のコマンドでは、ビルドコンテキストのルートディレクトリとしてカレントディレクトリを指定したことになります。 よってカレントディレクトリ以下の階層はそのディレクトリ構造のままDockerデーモンにコピーされます。 つまりイメージに使用したいファイルがある場合は、ビルドコンテキスト内に含める必要があります。

Dockerfileの読み込み

Dockerfileについてもビルドコンテキスト同様にDockerデーモンにコピーされたものが読み込まれます。 デフォルトでは、ビルドコンテキストのルートディレクトリにあるDockerfileがコピーされます。 重要なのは、Dockerfileとビルドコンテキストはそれぞれでコピーされるということです。 つまり、Dockerfileはビルドコンテキストに含まれる必要はありません。

例えばカレントディレクトリにDockerfile/appディレクトリがあったとします。

root/
  ├ app/
  └ Dockerfile

ビルドコンテキストのパスを/appとする場合、デフォルトの位置である/appにはDockerfileが存在しないためエラーとなります。 このような場合は、-fオプションでDockerfileのパスを明示的に指定します。

$ docker image build -f ./Dockerfile ./app
[+] Building 0.0s (5/5) FINISHED                                                                         docker:desktop-linux
 => [internal] load .dockerignore                                                                                        0.0s
 => => transferring context: 2B                                                                                          0.0s
 => [internal] load build definition from Dockerfile                                                                     0.0s
 => => transferring dockerfile: 49B                                                                                      0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest                                                         0.0s
 => CACHED [1/1] FROM docker.io/library/ubuntu                                                                           0.0s
 => exporting to image                                                                                                   0.0s
 => => exporting layers                                                                                                  0.0s
 => => writing image sha256:1dc34101d4972c0f97856d9020fec40a0d03d2d9d13caf8c3bf1f100a60c5104                             0.0s

実行結果(4,6行目)にtransferringとあるのは、Dockerfileとビルドコンテキストがそれぞれ転送(コピー)されていることを表しています。

.dockerignore

イメージをビルドするのにビルドコンテキスト内のすべてのファイルが必要とは限りません。 しかし、ビルドコンテキストのためにディレクトリ構造を変更するのはあまり好ましくありません。

.dockerignoreは、ビルドコンテキストから不要なファイルを除外するための仕組みです。 ビルドコンテキストのルートディレクトリに配備することでビルド時に読み込まれて適用されます。

以下のようにワイルドカードを組み合わせて、除外するファイルを設定します。 注意点としては、.dockerignoreではパスを明確にする必要があることです。 *.txtのように単純にファイルを指定するだけではルートディレクトリ内に限定されてしまいます。 ビルドコンテキスト内のすべてのテキストファイルを除外したい場合は**/*.txtとするように**を利用します。

.dockerignore
testA.txt     ## ルートディレクトリのtestA.txt
*.txt         ## ルートディレクトリのすべてのテキストファイル
test?.txt     ## ルートディレクトリのtest+1文字のテキストファイル
/dirA/*.txt   ## ルートディレクトリ配下のdirAディレクトリにあるすべてのテキストファイル
**/*.txt      ## すべてのテキストファイル

Dockerfileの書式

Dockerfileでは、命令 引数という形でイメージ作成時に何(引数)をどうするか(命令)を記述していきます。 以下よりその命令について説明をしていきます。

FROM

FROMは、作成するイメージのベースとなるイメージを指定します。 例えばイメージのベースとしてubuntu:20.04を使用したい場合は、以下のように記述します。

Dockerfile
FROM ubuntu:20.04

これをビルドすると、以下のようにイメージ作成の結果が表示されます。

ビルド
$ docker image build .
[+] Building 4.5s (5/5) FINISHED                                                                         docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                     0.0s
 => => transferring dockerfile: 55B                                                                                      0.0s
 => [internal] load .dockerignore                                                                                        0.0s
 => => transferring context: 2B                                                                                          0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                          2.8s
 => [1/1] FROM docker.io/library/ubuntu:20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8    1.7s
 => => resolve docker.io/library/ubuntu:20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8    0.0s
 => => sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8 1.13kB / 1.13kB                           0.0s
 => => sha256:080169816683e6f063d3903434565624287828ecfd06bd2f813b30325e8b1eca 424B / 424B                               0.0s
 => => sha256:fde9c12d7d3f936f56d545cc36391de434bbe311fd9d60f98e496c527cf58f21 2.32kB / 2.32kB                           0.0s
 => => sha256:d519a3a2a796a075e4e40e5c4a1513aa8db8f8fdf009662bf6858f0149143b28 25.97MB / 25.97MB                         1.0s
 => => extracting sha256:d519a3a2a796a075e4e40e5c4a1513aa8db8f8fdf009662bf6858f0149143b28                                0.6s
 => exporting to image                                                                                                   0.0s
 => => exporting layers                                                                                                  0.0s
 => => writing image sha256:4163c658a39788fb6f6a0a602c6c1738b3488c20a1d50988ea794252fffe348f  
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
<none>       <none>    4163c658a397   6 weeks ago    65.7MB

RUN

RUNは、ビルド時に実行するシェルコマンドを指定します。 以下例は、ubuntuにNode.jsをインストールしています。 RUNの注意点として、標準入力が必要になる場合はエラーとなります。 以下であれば、apt install-yオプションを指定しなかった場合、インストールの確認のためにy/nの入力が求められエラーとなります。

Dockerfile
FROM ubuntu:20.04

RUN apt update              #パッケージの更新
RUN apt install -y nodejs   #Node.jsのインストール
RUN node -v                 #nodeの確認
ビルド
$ docker image build .
[+] Building 7.8s (8/8) FINISHED                                                                         docker:desktop-linux
 => [internal] load .dockerignore                                                                                        0.0s
 => => transferring context: 2B                                                                                          0.0s
 => [internal] load build definition from Dockerfile                                                                     0.0s
 => => transferring dockerfile: 110B                                                                                     0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                          0.7s
 => [1/4] FROM docker.io/library/ubuntu:20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8    0.0s
 => CACHED [2/4] RUN apt update                                                                                          0.0s
 => [3/4] RUN apt install -y nodejs                                                                                      6.8s
 => [4/4] RUN node -v                                                                                                    0.2s
 => exporting to image                                                                                                   0.2s 
 => => exporting layers                                                                                                  0.2s 
 => => writing image sha256:2c776530f3502d2d0efa939b3b6757c90f8b7298720a3ea3a4a4404e47e27933                             0.0s

COPY

COPYは、ファイルやディレクトをイメージにコピーします。 以下は、カレントディレクトリのhello.jsをコンテナの/app/にコピーする例です。 /app/はubuntuのルートディレクトリに存在しませんが、COPYによって作成されます。

前述のように、コピーするファイルはビルドコンテキストに含まれている必要があります。

Dockerfile
FROM ubuntu:20.04

RUN apt update
RUN apt install -y nodejs
RUN node -v

COPY ./hello.js /app/
ビルド
$ docker image build .
[+] Building 0.7s (10/10) FINISHED                                                                       docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                     0.0s
 => => transferring dockerfile: 131B                                                                                     0.0s
 => [internal] load .dockerignore                                                                                        0.0s
 => => transferring context: 2B                                                                                          0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                          0.6s
 => [internal] load build context                                                                                        0.0s
 => => transferring context: 29B                                                                                         0.0s
 => [1/5] FROM docker.io/library/ubuntu:20.04@sha256:f2034e7195f61334e6caff6ecf2e965f92d11e888309065da85ff50c617732b8    0.0s
 => CACHED [2/5] RUN apt update                                                                                          0.0s
 => CACHED [3/5] RUN apt install -y nodejs                                                                               0.0s
 => CACHED [4/5] RUN node -v                                                                                             0.0s
 => [5/5] COPY ./hello.js /app                                                                                           0.0s
 => exporting to image                                                                                                   0.0s
 => => exporting layers                                                                                                  0.0s
 => => writing image sha256:cbe854731014d530c2565af7343b03ad0669e52074db366385c7b9cc4d3201ba                             0.0s
結果
$ docker container run -it cbe854731014 bash
root@7e6a614f96d8:/# ls
app  bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@7e6a614f96d8:/# ls app
hello.js

ADD

ADDCOPYと同じくファイルやディレクトリをイメージにコピーします。 COPYとの違いは、圧縮ファイルを指定した場合に、解凍されます。

Dockerfile
FROM ubuntu:20.04

ADD ./test.tar.gz /app/
ビルド
$ docker image build .
[+] Building 0.3s (7/7) FINISHED                                                                               docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                           0.0s
 => => transferring dockerfile: 80B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                              0.0s
 => => transferring context: 2B                                                                                                0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                                0.3s
 => [internal] load build context                                                                                              0.0s
 => => transferring context: 33B                                                                                               0.0s
 => [1/2] FROM docker.io/library/ubuntu:20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4e548636e54          0.0s
 => [2/2] ADD ./test.tar.gz /app/                                                                                              0.0s
 => exporting to image                                                                                                         0.0s
 => => exporting layers                                                                                                        0.0s
 => => writing image sha256:a8354aa990a8264834bba21e19ae65485e146acecfdbeaf270c629ecedc92a3a                                   0.0s
結果
$ docker container run -it a8354aa990a8 bash
root@0f68b7bf7cdf:/# ls app
test

CMD

CMDはコンテナ起動時に実行するデフォルトのコマンドを指定します。 書式は[コマンド, 引数(オプション), 引数, …]の形となります。

Dockerfile
FROM ubuntu:20.04

CMD ["ls", "-l"] 
実行結果
$ docker container run --rm 57a96d4b9a34
total 48
lrwxrwxrwx   1 root root    7 Jan 23 02:57 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
drwxr-xr-x   5 root root  340 Feb  6 12:59 dev
drwxr-xr-x   1 root root 4096 Feb  6 12:59 etc
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
lrwxrwxrwx   1 root root    7 Jan 23 02:57 lib -> usr/lib
drwxr-xr-x   2 root root 4096 Jan 23 02:57 media
drwxr-xr-x   2 root root 4096 Jan 23 02:57 mnt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 opt
dr-xr-xr-x 229 root root    0 Feb  6 12:59 proc
drwx------   2 root root 4096 Jan 23 03:01 root
drwxr-xr-x   5 root root 4096 Jan 23 03:01 run
lrwxrwxrwx   1 root root    8 Jan 23 02:57 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jan 23 02:57 srv
dr-xr-xr-x  12 root root    0 Feb  6 12:59 sys
drwxrwxrwt   2 root root 4096 Jan 23 03:01 tmp
drwxr-xr-x  10 root root 4096 Jan 23 02:57 usr
drwxr-xr-x  11 root root 4096 Jan 23 03:01 var

docker container runの引数としてコマンドを指定した場合は、そちらが優先されます。

実行結果
$ docker container run --rm -it 57a96d4b9a34 bash
root@16a08b167ade:/# 

ENTRYPOINT

ENTRYPOINTCMDと同様にコンテナ起動時に実行するコマンドを指定します。 指定方法も同じです。

Dockerfile
From ubuntu:20.04

ENTRYPOINT ["ls", "-l"]
実行結果
$ docker container run --rm 57a96d4b9a34
total 48
lrwxrwxrwx   1 root root    7 Jan 23 02:57 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
drwxr-xr-x   5 root root  340 Feb  6 12:59 dev
drwxr-xr-x   1 root root 4096 Feb  6 12:59 etc
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
lrwxrwxrwx   1 root root    7 Jan 23 02:57 lib -> usr/lib
drwxr-xr-x   2 root root 4096 Jan 23 02:57 media
drwxr-xr-x   2 root root 4096 Jan 23 02:57 mnt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 opt
dr-xr-xr-x 229 root root    0 Feb  6 12:59 proc
drwx------   2 root root 4096 Jan 23 03:01 root
drwxr-xr-x   5 root root 4096 Jan 23 03:01 run
lrwxrwxrwx   1 root root    8 Jan 23 02:57 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jan 23 02:57 srv
dr-xr-xr-x  12 root root    0 Feb  6 12:59 sys
drwxrwxrwt   2 root root 4096 Jan 23 03:01 tmp
drwxr-xr-x  10 root root 4096 Jan 23 02:57 usr
drwxr-xr-x  11 root root 4096 Jan 23 03:01 var

CMDとの違いは、docker container runの引数としてコマンドを指定してもENTRYPOINTのものが実行されます。

実行結果
$ docker container run --rm 57a96d4b9a34 bash
total 48
lrwxrwxrwx   1 root root    7 Jan 23 02:57 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
drwxr-xr-x   5 root root  340 Feb  6 12:59 dev
drwxr-xr-x   1 root root 4096 Feb  6 12:59 etc
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
lrwxrwxrwx   1 root root    7 Jan 23 02:57 lib -> usr/lib
drwxr-xr-x   2 root root 4096 Jan 23 02:57 media
drwxr-xr-x   2 root root 4096 Jan 23 02:57 mnt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 opt
dr-xr-xr-x 229 root root    0 Feb  6 12:59 proc
drwx------   2 root root 4096 Jan 23 03:01 root
drwxr-xr-x   5 root root 4096 Jan 23 03:01 run
lrwxrwxrwx   1 root root    8 Jan 23 02:57 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jan 23 02:57 srv
dr-xr-xr-x  12 root root    0 Feb  6 12:59 sys
drwxrwxrwt   2 root root 4096 Jan 23 03:01 tmp
drwxr-xr-x  10 root root 4096 Jan 23 02:57 usr
drwxr-xr-x  11 root root 4096 Jan 23 03:01 var

またCMDENTRYPOINTは併用でき、CMDENTRYPOINTのデフォルト引数として扱います。

Dockerfile
FROM ubuntu:20.04

ENTRYPOINT ["ls"]
CMD ["-l"]
実行結果
$ docker container run --rm 57a96d4b9a34
total 48
lrwxrwxrwx   1 root root    7 Jan 23 02:57 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
drwxr-xr-x   5 root root  340 Feb  6 12:59 dev
drwxr-xr-x   1 root root 4096 Feb  6 12:59 etc
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
lrwxrwxrwx   1 root root    7 Jan 23 02:57 lib -> usr/lib
drwxr-xr-x   2 root root 4096 Jan 23 02:57 media
drwxr-xr-x   2 root root 4096 Jan 23 02:57 mnt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 opt
dr-xr-xr-x 229 root root    0 Feb  6 12:59 proc
drwx------   2 root root 4096 Jan 23 03:01 root
drwxr-xr-x   5 root root 4096 Jan 23 03:01 run
lrwxrwxrwx   1 root root    8 Jan 23 02:57 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Jan 23 02:57 srv
dr-xr-xr-x  12 root root    0 Feb  6 12:59 sys
drwxrwxrwt   2 root root 4096 Jan 23 03:01 tmp
drwxr-xr-x  10 root root 4096 Jan 23 02:57 usr
drwxr-xr-x  11 root root 4096 Jan 23 03:01 var

docker container runの引数はENTRYPOINTの引数を渡すことになります。

実行結果
$ docker container run --rm test -lr 
total 48
drwxr-xr-x  11 root root 4096 Jan 23 03:01 var
drwxr-xr-x  10 root root 4096 Jan 23 02:57 usr
drwxrwxrwt   2 root root 4096 Jan 23 03:01 tmp
dr-xr-xr-x  12 root root    0 Feb  6 13:15 sys
drwxr-xr-x   2 root root 4096 Jan 23 02:57 srv
lrwxrwxrwx   1 root root    8 Jan 23 02:57 sbin -> usr/sbin
drwxr-xr-x   5 root root 4096 Jan 23 03:01 run
drwx------   2 root root 4096 Jan 23 03:01 root
dr-xr-xr-x 232 root root    0 Feb  6 13:15 proc
drwxr-xr-x   2 root root 4096 Jan 23 02:57 opt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 mnt
drwxr-xr-x   2 root root 4096 Jan 23 02:57 media
lrwxrwxrwx   1 root root    7 Jan 23 02:57 lib -> usr/lib
drwxr-xr-x   2 root root 4096 Apr 15  2020 home
drwxr-xr-x   1 root root 4096 Feb  6 13:15 etc
drwxr-xr-x   5 root root  340 Feb  6 13:15 dev
drwxr-xr-x   2 root root 4096 Apr 15  2020 boot
lrwxrwxrwx   1 root root    7 Jan 23 02:57 bin -> usr/bin

まとめると、ENTRYPOINTでコンテナ起動時のコマンドを固定することができ、CMDで可変部分を補うといった感じです。

WORKDIR

RUN, COPY, ADD, CMD, ENTRYPOINTの実行の起点となるディレクトリを指定します。

Dockerfile
FROM ubuntu:20.04

WORKDIR /usr
ビルド
docker image build .                           
[+] Building 0.7s (6/6) FINISHED                                                                    docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                0.0s
 => => transferring dockerfile: 69B                                                                                 0.0s
 => [internal] load .dockerignore                                                                                   0.0s
 => => transferring context: 2B                                                                                     0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                     0.7s
 => CACHED [1/2] FROM docker.io/library/ubuntu:20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4  0.0s
 => [2/2] WORKDIR /usr                                                                                              0.0s
 => exporting to image                                                                                              0.0s
 => => exporting layers                                                                                             0.0s
 => => writing image sha256:fe4f4faddfef25047cd6d7137209cd71e1f3fc45b38af7562222c0ab0c5f324e                        0.0s
実行結果
$ docker container run -it fe4f4faddfef
root@611438cda16e:/usr# 

実行結果から分かるように、bash起動時のディレクトリがWORKDIRで指定した/usrになっています。 上述のようにRUNCOPYADDといったビルド時の命令にも影響があるため、特に相対パスを利用する場合には注意が必要です。

ENV

ENVは環境変数を設定するためのものです。 {キー}={値}の形で環境変数を指定します。

Dockerfile
FROM ubuntu:20.04

ENV hello="Hello World!"
RUN echo $hello > hello.txt
ビルド
$ docker image build .
[+] Building 0.8s (6/6) FINISHED                                                                    docker:desktop-linux
 => [internal] load .dockerignore                                                                                   0.0s
 => => transferring context: 2B                                                                                     0.0s
 => [internal] load build definition from Dockerfile                                                                0.0s
 => => transferring dockerfile: 111B                                                                                0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                     0.7s
 => CACHED [1/2] FROM docker.io/library/ubuntu:20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4  0.0s
 => [2/2] RUN echo Hello World! > hello.txt                                                                         0.1s
 => exporting to image                                                                                              0.0s
 => => exporting layers                                                                                             0.0s
 => => writing image sha256:a2e9674bf6672c5f46d997cd40b63a01d36de266ac3dade830b6c1bfe3ea6259                        0.0s
実行結果
$ docker container run -it a2e9674bf667
root@9723bd7c3cf3:/# cat hello.txt 
Hello World!
root@9723bd7c3cf3:/# echo $hello
Hello World!

RUN実行時に環境変数として設定した$helloが利用されていることが、実行結果からわかると思います。 またENVで指定した環境変数は、コンテナ内でも参照できます。

ARG

ARGENVと似ており、ビルド実行時の引数を設定するためのものです。 指定方法はENVと同じです。

Dockerfile
FROM ubuntu:20.04

ARG hello="Hello World!"
RUN echo $hello > hello.txt
ビルド
docker image build .                 
[+] Building 2.0s (6/6) FINISHED                                                                    docker:desktop-linux
 => [internal] load .dockerignore                                                                                   0.0s
 => => transferring context: 2B                                                                                     0.0s
 => [internal] load build definition from Dockerfile                                                                0.0s
 => => transferring dockerfile: 111B                                                                                0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                     2.0s
 => [1/2] FROM docker.io/library/ubuntu:20.04@sha256:bb1c41682308d7040f74d103022816d41c50d7b0c89e9d706a74b4e548636  0.0s
 => CACHED [2/2] RUN echo Hello World! > hello.txt                                                                  0.0s
 => exporting to image                                                                                              0.0s
 => => exporting layers                                                                                             0.0s
 => => writing image sha256:2fb6253504d4931f94602e8ead912a1865251b7babaaf614cd03d6148f2c7405                        0.0s
実行結果
$ docker container run -it 2fb6253504d4
root@f5f5d2819a38:/# cat hello.txt
Hello World!
root@f5f5d2819a38:/# echo $hello
 

ENV同様にARGで設定した変数$helloを用いてhello.txtが作成されていることがわかります。 しかし、コンテナ内で$helloを参照しても値は取得できません。 ARGENVと異なり、あくまでビルド時にのみ有効な変数として扱われます。 つまり、コンテナ内で使用する変数はENV、使用しない変数はARGで定義するのがよさそうです。

LABEL

LABELはイメージのメタデータを設定するためのものです。 docker image inspectで詳細を参照した際のConfig.Labelsにデータが設定されます。

Dockerfile
FROM ubuntu:20.04

LABEL key1=value1 key2=value2
実行結果
$ docker image inspect -f "{{json .Config.Labels}}" 74e0cc401dcf
{"key1":"label1","key2":"label2","org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"20.04"}

EXPOSE

EXPOSEはどのポートを公開するかを示すためのものです。 docker image inspectで詳細を参照した際のConfig.ExposedPortsにデータが設定されます。

Dockerfile
From nginx

EXPOSE 443
実行結果
$ docker image inspect -f "{{json .Config.ExposedPorts}}" e3c329fc0b66
{"80/tcp":{},"443/tcp":{}}

注意点として、ExposedPortsに設定したポートは公開されるわけではありません。 あくまでイメージとして公開を推奨しているポートを利用者に示すためのものです。 実際にはdocker container runを実行する際に-pオプションで公開するポートを指定する必要があります。

$ docker container run -p 80:80 e3c329fc0b66

マルチステージビルド

マルチステージビルドはv17.05以降に追加された機能で、1つのDockerfileに複数のFROMを指定することができます。 このFROMの区切りをステージと呼び、ステージ毎に指定したイメージの役割にあった処理を行います。

FROM hoge
# ステージ1

FROM fuga
# ステージ2

FROM piyo
# ステージ3

マルチステージビルドにより以下のようなことができるようになります。

イメージサイズの削減

例えばJavaで考えた場合、JavaのコンパイルにはJDKが必要ですが、実行するだけであればJREのみあれば十分です。 ビルドの役割としてはコンバイル → 実行としたいのですが、JDKをそのまま使用するとJREだけよりもイメージサイズが大きくなってしまいます。

App.java
class App {
  public static void main(String[] args) {
    System.out.println("Hello World");
  }
}
Dockerfile
FROM java:8u111-jdk

WORKDIR /app
COPY ./App.java .
RUN javac App.java

CMD ["java", "App"]
実行結果
$ docker image build -t java-image1 .
$ docker container run --rm java-image1
Hello World

そこで最終的なイメージとしてJREを利用するように以下のようなマルチステージビルドを使用します。

Dockerfile
# ビルド用ステージ
FROM java:8u111-jdk as jdk

WORKDIR /app
COPY ./App.java .
RUN javac App.java

# 実行環境ステージ
FROM java:8u111-jre

COPY --from=jdk /app/App.class .
CMD ["java", "App"]
実行結果
$ docker image build -t java-image2 .
$ docker container run --rm java-image2
Hello World
イメージサイズ比較
$ docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
java-image2   latest    b30068972ac3   21 minutes ago   311MB
java-image1   latest    23cc964799fe   34 minutes ago   643MB

見ての通り、実行結果は変わらず、イメージのサイズが小さくなっていることがわかります。

ビルドの流れとして、まずビルド用ステージで/appにコピーしたApp.javaをコンパイルし、/app/App.classを作成します(2〜6行目)。 次に実行環境ステージへビルド用ステージで作成した/app/App.classをコピーし、実行する処理を指定します(9〜12)。 別ステージのファイルをコピーする場合は、--from={ステージ名}または1ステージ目を0とした--form={インデックス}を使用します。 ステージ名はFROMas {ステージ名}と指定することでつけることができます。

複数環境のビルド

例えば、開発環境と本番環境で環境変数が異なるイメージを作成したいとします。 Dockerfileを分けることで実現はできますが、以下のようにマルチステージビルドによって1つのDockerfileで実現することができます。

Dockerfile
# ベースステージ
FROM ubuntu:20.04 AS base
CMD ["sh", "-c", "echo $env_value"]

# 開発用
FROM base AS dev
ENV env_value=DEV

# 本番用
FROM base AS prod
ENV env_value=PROD
実行結果1
$ docker image build --target dev -t dev_image .
$ docker container run --rm dev_image
DEV
実行結果2
$ docker image build --target prod -t prod_image .
$ docker container run --rm prod_image
PROD

共通的な命令を記述したステージを作成し、各環境毎のステージを共通のステージを用いて作成します。 あとはビルドの際に--targetオプションを用いて、ビルドしたいステージを指定します。 これによって1つのDockerfileで複数環境のイメージを作成することができます。