Dockerfileによるイメージの構築
既存のDockerイメージをもとにして、そこからパッケージの追加や設定、あるいは不要な資源の削除などを施して、別の目的のための新たなDockerイメージを作ることができます。その方法の一つとして、既存イメージから別のイメージへ作り変える手続きを記述したDockerfileに従ってビルドする方法があります。
"Dockerfile"というファイル名のテキストファイルに、いくつかのビルダー用の命令を使って、Linuxの中で実行するようなコマンド操作やファイル操作、設定操作の手順を記述します。
イメージを作成する方法には他にも、稼働中のコンテナの状態をスナップショットのようにイメージに出力するという方法がありますが、Dockerfileによるイメージの構築は、それよりも設計として確実であり再現性が保証できるものになります。
Dockerfileに記述する手続きは、その段階ごとに「レイヤ(層)」としてイメージが生成されます。手続きの層がIDで管理され、たくさんのイメージの構築手順の途中までのレイヤが同じものはイメージの間で資源が共有されます。
Dockerfileは、必ず変更を加えるもとになる既存のイメージ(ベースイメージ)を選択します。仮想マシンのようにOSレベルの仮想化ではなく、サービスのデプロイがDockerの主な用途なので、少なくとも何らかの汎用的なLinuxディストリビューションをもとに作ることになります。
Dockerfileの書式
Dockerfileは主に以下の命令を使って記述します。
FROM
|
ベースイメージを指定する
|
RUN
|
構築中のイメージでコマンドを実行する
|
COPY
|
構築中のイメージの中へホストからファイルをコピーする
|
ENV
|
構築中のイメージの環境変数を設定する
|
CMD
ENTRYPOINT
|
完成イメージのコンテナ起動時に実行するコマンドを設定する
|
USER
|
コマンド実行時のユーザを指定する
|
ARG
|
Dockerfile内で使う変数を設定する
|
# コメント
|
「#」から行末は解釈されない(コメントにできる)
|
行頭に命令、続いてそれぞれの処理を各行に記述します。例えば、C言語開発環境のイメージを作るならば次のように書けます。
Dockerfile
FROM ubuntu:18.04
ARG USR=tenten
RUN apt-get update && apt-get -y install vim && apt-get -y install build-essential
RUN groupadd -g 1000 $USR
RUN useradd -m -u 1000 -g 1000 $USR
USER tenten
CMD ["/bin/bash"]
docker buildにより、Dockerfileからイメージをビルドします。
docker build -t イメージ名[:タグ] Dockerfileの場所
上のDockerfileがカレントディレクトリに存在し、「ubuntuclang」というイメージをビルドする場合は次のようにします。
$ docker build -t ubuntuclang .
[+] Building 179.1s (8/8) FINISHED
=> [internal] load build definition from Dockerfile
...
=> => writing image sha256:2ff3e46f19fc39d5bb38c4fe06acbe3ed66ffdf8f0490890fc5aae71955b0c8e
=> => naming to docker.io/library/ubuntuclang
ubuntu:18.04というベースイメージがローカルにない場合は、DockerHubからのダウンロードから行われます。その後もapt-getの処理が続くので、若干の時間がかかります。
この例は、次のようにコンテナを起動して、実際にC言語の開発に使えます。
$ docker run -it --name test -v ~/src:/mnt/host ubuntuclang
Dockerfile各命令の詳細
各命令を詳しく説明します。
FROM命令
イメージ構築の基盤とする既存のイメージ(ベースイメージ)を指定します。FROMに続きイメージ名とタグを指定します。タグ省略時はlatest(最新)が適用されます。
FROM イメージ
FROM イメージ:タグ
Dockerfile
FROM ubuntu
明示的にタグを指定する場合は、次のようにできます。
Dockerfile
FROM ubuntu:18.04
これだけで、指定したイメージと全くイメージを生成するDockerfileができたことになります。docker buildでDockerfileからubuntutestというイメージを構築します。
$ docker build -t ubuntutest .
[+] Building 0.1s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile
...
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntutest latest 18f1928b30e0 7 months ago 77.8MB
これはベースになるubuntuイメージと全く同じものを作っただけです。これをもとに、RUNやCOPYなどの命令を使って独自のイメージを構築する手続きを加えていきます。
RUN命令
RUN命令は、ベースイメージから生成するコンテナの中で、シェル(/bin/sh)からコマンドを実行します。パッケージのインストールや環境設定などを行います。
RUN コマンドライン
例えばベースのubuntuイメージに、パッケージ(vim)のインストールする場合は次のように書けます。
Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
このイメージのコンテナは、vimエディタがプレインストールされた状態になります。
RUN命令はコマンドラインの実行順に複数書くことができますが、この例ではシェルの&&演算を利用して、一つのRUN命令にコマンドラインをまとめています。これは、それぞれのコマンドを各々のRUN命令に記述してもかまいません。ただし一般に、イメージ構築時のレイヤーが命令ごとに生成されることを避けて、なるべく少ないRUN命令に集約する方法が好まれています。
RUN命令や後述の命令群は、構築時の環境内でスクリプトのように流れて実行されるわけではなく、RUN命令一つについてレイヤーを区切り、一旦イメージへコミットされます。その次のRUN命令はその前のレイヤーのイメージをベースに改めて変更を加えることになります。
RUN命令はExec形式というもう一つの書式があります。
RUN ["コマンド", "オプションなど"]
通常の書式(Shell形式)では、デフォルトで/bin/shのシェルからコマンドを実行しますが、Exec形式ではシェルではなく直接コマンドを実行します。/bin/shではなく/bin/bashでコマンドを実行する場合は、Exec形式で次のように書くことができます。
RUN ["/bin/bash", "-c" "apt-get install -y vim"]
COPY命令
COPY命令は、ホスト側のファイルやディレクトリをイメージ内のファイルパスにコピーします。
COPY ホストファイル イメージ側パス
次の例では、PHPの初期ページをイメージの中に組み込んでいます。
Dockerfile
FROM php:7.4-apache
COPY index.php /var/www/html/
index.php
<?php
phpinfo();
?>
コピー元がディレクトリの場合は、そのディレクトリ階層を再帰的にコピーします。
ENV命令
ENV命令は、イメージの環境変数を設定します。設定した環境変数が初期設定された状態でコンテナが起動します。
ENV 環境変数 値
ENV 環境変数=値 環境変数=値
次の例では、ubuntuのベースイメージから日本ロケールに設定したイメージを作ります。
Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y locales && locale-gen ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
コンテナの中でdateコマンドを実行すると表示が日本語になっていることがわかります。
$ docker build -t ubuntujp .
[+] Building 118.2s (6/6) FINISHED
..
$ docker run -it --name test ubuntujp /bin/bash
root@9cef0da8bd68:/# date
2024年 3月 24日 日曜日 03:17:02 UTC
CMD命令・ENTRYPOINT命令
構築したイメージのコンテナが起動するときに実行するコマンドを指定します。RUN、COPY、ENVはイメージ構築のための命令ですが、CMDとENTRYPOINTはコンテナ生成に関する命令です。これらはDockerfileで一回のみ記述します。
CMD コマンドライン
ENTRYPOINT コマンドライン
これらはRUNと同様Exec形式で書くことができ、その場合はシェルからではなく直接実行されます。
次の例は、CMD命令でコンテナ起動時に/bin/bashシェルが起動するようにしています。
Dockerfile
FROM ubuntu
CMD ["/bin/bash"]
$ docker build -t ubuntubash .
$ docker run -it --name test ubuntubash
root@08751953ba5e:/#
root@08751953ba5e:/#
ENTRYPOINT命令はCMD命令と同じですが、CMDのコマンドはdocker runでコマンドが指定された場合はdocker runのコマンドを優先し、ENTRYPOINTのコマンドはそれにかまわず必ず実行します。CMD命令は完全なコマンドラインでなくてもよく、コマンドオプションのみを分けて記述できます。両者のこの違いを利用して、コンテナ起動時に実行するコマンドにパラメータが指定されなかった場合に、デフォルトパラメータを適用する振る舞いを設定できます。
次の例では、コンテナ起動時にメッセージを起動します。docker runに"Message"を指定した場合はそれが表示され、何も指定しない場合は"Default"と表示されます。
Dockerfile
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["Default"]
$ docker build -t ubuntucmd .
$ docker run -it --name test ubuntucmd "Message"
Message
$ docker rm test
$ docker run -it --name test ubuntucmd
Default
USER命令
USER命令は、RUNやCMDなどイメージの構築中のコマンド実行時のユーザを指定します。
USER ユーザ名
次の例では、ユーザ「tenten」を構築時に追加して、ユーザtentenのログインでコンテナを起動します。
Dockerfile
FROM ubuntu
RUN groupadd -g 1000 tenten
RUN useradd -m -u 1000 -g 1000 tenten
USER tenten
CMD ["/bin/bash"]
$ docker build -t ubuntuuser .
$ docker run -it --name test ubuntuuser
tenten@cb5d2791ba4b:/$
ARG命令
ARG命令は、Dockerfileの中の変数を設定します。RUN、COPY、ENV、USER命令で変数を参照できます。CMD命令では使えません。
ARG 変数=値
RUN コマンド $変数
Dockerfile
FROM php:7.4-apache
ARG PAGE=index.php
COPY $PAGE /var/www/html/
コンテナのイメージエクスポート
冒頭で述べた通り、イメージを作るもう一つの方法に、コンテナの状態をスナップショットのようにイメージへ出力(エクスポート)する方法があります。この方法は、公開や大規模プロジェクトには向いていませんが、コンテナ内の試行錯誤で環境を作り上げたり、作業のバックアップに便利です。
あるコンテナの状態をイメージに書き出すにはdocker commit(docker container commit)コマンドを実行します。
docker [container] commit コンテナ イメージ名[:タグ]
イメージ書き出し中はデフォルトでコンテナのプロセスは停止しますが、コンテナをstopで停止状態にしてからcommitを行うと安全に書き出せます。
例えば、あるコンテナを「cdevel:1.0」イメージに出力します。このイメージはdocker image lsコマンドで、他のイメージと共に表示されます。
$ docker commit ctest cdevel:1.0
docker image lsで表示されるイメージは、アーカイブファイルに書き出すことができます。
docker [image] save イメージ名[:タグ] > アーカイブ.tar
そして、アーカイブから読み出してイメージを復元できます。
docker [image] load < アーカイブ.tar
$ docker save cdevel:1.0 > cdevel1.tar
$ docker load < cdevel1.tar