【Docker】Dockerfileの基本
Dockerfileとは
DockerfileはDockerイメージを作成するための設計図のようなものです。
前回記事では、docker image pull
でレジストリからイメージを取得していましたが、実際のユースケースではDockerfileから作成したイメージを利用するのがほとんどです。
Dockerfileには、ビルド時に実行する処理や配備するファイルの管理、環境変数などの設定が可能です。
イメージのビルド
Dockerfileの記述については後述しますが、Dockerfileからイメージを作成するにはdocker image build
コマンド実行します。
以下はカレントディレクトリのDockerfile
からイメージを作成するコマンドの例です。
-t(--tag)
オプションはタグ名を指定するもので、指定がない場合はリポジトリとタグは<none>
になります。
$ docker image build -t myimage:1.0 .
ビルドコンテキスト
上記コマンドでカレントディレクトリを指定しましたが、これは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
とするように**
を利用します。
testA.txt ## ルートディレクトリのtestA.txt
*.txt ## ルートディレクトリのすべてのテキストファイル
test?.txt ## ルートディレクトリのtest+1文字のテキストファイル
/dirA/*.txt ## ルートディレクトリ配下のdirAディレクトリにあるすべてのテキストファイル
**/*.txt ## すべてのテキストファイル
Dockerfileの書式
Dockerfileでは、命令 引数
という形でイメージ作成時に何(引数)をどうするか(命令)を記述していきます。
以下よりその命令について説明をしていきます。
FROM
FROM
は、作成するイメージのベースとなるイメージを指定します。
例えばイメージのベースとしてubuntu:20.04
を使用したい場合は、以下のように記述します。
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
の入力が求められエラーとなります。
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
によって作成されます。
前述のように、コピーするファイルはビルドコンテキストに含まれている必要があります。
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
ADD
もCOPY
と同じくファイルやディレクトリをイメージにコピーします。
COPY
との違いは、圧縮ファイルを指定した場合に、解凍されます。
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
はコンテナ起動時に実行するデフォルトのコマンドを指定します。
書式は[コマンド, 引数(オプション), 引数, …]
の形となります。
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
ENTRYPOINT
もCMD
と同様にコンテナ起動時に実行するコマンドを指定します。
指定方法も同じです。
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
またCMD
とENTRYPOINT
は併用でき、CMD
をENTRYPOINT
のデフォルト引数として扱います。
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
の実行の起点となるディレクトリを指定します。
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
になっています。
上述のようにRUN
やCOPY
、ADD
といったビルド時の命令にも影響があるため、特に相対パスを利用する場合には注意が必要です。
ENV
ENV
は環境変数を設定するためのものです。
{キー}={値}
の形で環境変数を指定します。
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
ARG
はENV
と似ており、ビルド実行時の引数を設定するためのものです。
指定方法はENV
と同じです。
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
を参照しても値は取得できません。
ARG
はENV
と異なり、あくまでビルド時にのみ有効な変数として扱われます。
つまり、コンテナ内で使用する変数はENV
、使用しない変数はARG
で定義するのがよさそうです。
LABEL
LABEL
はイメージのメタデータを設定するためのものです。
docker image inspect
で詳細を参照した際のConfig.Labels
にデータが設定されます。
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
にデータが設定されます。
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だけよりもイメージサイズが大きくなってしまいます。
class App {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
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を利用するように以下のようなマルチステージビルドを使用します。
# ビルド用ステージ
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={インデックス}
を使用します。
ステージ名はFROM
にas {ステージ名}
と指定することでつけることができます。
複数環境のビルド
例えば、開発環境と本番環境で環境変数が異なるイメージを作成したいとします。 Dockerfileを分けることで実現はできますが、以下のようにマルチステージビルドによって1つの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
$ docker image build --target dev -t dev_image .
$ docker container run --rm dev_image
DEV
$ docker image build --target prod -t prod_image .
$ docker container run --rm prod_image
PROD
共通的な命令を記述したステージを作成し、各環境毎のステージを共通のステージを用いて作成します。
あとはビルドの際に--target
オプションを用いて、ビルドしたいステージを指定します。
これによって1つのDockerfileで複数環境のイメージを作成することができます。