はじめに
コンテナを使って開発をする機会が多いですが、業務ではすでに環境が用意されていることが多くDockerfileを触る機会は少ないと思います。
テンプレートや既存のDockerfileをどこかからコピーしてきて、そのまま使うことなども多く、理解できていないがちなので、復習も兼ねてまとめたいと思います。
Dockerfileの基本文法
FROM
カスタムimageのベースになるimageを指定します。
Docker Hubのような公開されているリポジトリからimageを引っ張ってくることができます。
imageは1:1ではなく、tagを使って複数の種類が公開されています。
例えば、bookworm(Debian12)
をベースにNode v22.12
をインストールしているimageを使う場合は、以下のように指定します。
FROM node:22.12-bookworm
WORKDIR
RUN
, CMD
, ENTRYPOINT
, ADD
, COPY
を実行する際の作業ディレクトリを指定します。
ここで指定したディレクトリが存在しない場合は自動で作成されます。
以下のように指定すると/app
が作成され、そこをworking directoryとして他の命令類が実行されます。
WORKDIR /app
COPY
COPY <コピー元> <コピー先>
のように指定し、ローカルにあるコピー元のファイルやディレクトリをコピー先のパスにコピーします。
この時のコピー先のパスは、WORKDIR
からの相対パスになります。
WORKDIR /app
COPY . .
上記のような条件を想定すると、ホスト側のカレントディレクトリ(Dockerfileが存在するディレクトリ)を、コンテナ内の/app
ディレクトリにコピーします。
これに似た機能としてADD
命令がありますが、基本的にはCOPY
命令を使う方が良いようです。
RUN
Dockerfileで作成するカスタムimageのビルド時に実行されます。
コンテナの実行時ではなくビルド時です。
以後必要になるソフトウェアパッケージのインストールなどを行うことが多いです。
debian系のベースimageを使った場合、apt-get
でパッケージをインストールすると思いますが、現在利用可能なパッケージのリストを更新(apt-get update
)と、パッケージのインストールは同一RUN命令内で実行するのがベストプラクティスのようです。
RUN apt-get update && apt install -y \
git \
gcc \
# ...
CMD
コンテナを起動するときに実行するデフォルトのコマンドを指定します。
デフォルトなので、引数として実行するコマンドを指定した場合はそちらが優先されます。
なので、CMD [ "bash" ]
があるDockerfileをbuildして実行するのと、CMD
が指定されていないDockerfileをbuildして、$ docker run -it --name CONTAINER_NAME IMAGE_NAME bash
をすることによって得られる結果は同じになります。
公開されているimageを確認すると、defaultの挙動としてのCMD
が指定されていることを確認できるはずです。
例えば、mysql:8.0ではCMD ["mysqld"]
が指定されていますが、character-setの設定を変えて起動したい時などは、引数で実行したいコマンドを指定します。
docker run -it --name mysql mysql:8.0 mysqld --character-set-server=utf8
ENTRYPOINT
dockerではデフォルトのentrypointとして、/bin/sh -c
が指定されています。
例えば、以下のようなDockerfileをビルドして、コンテナを立ち上げるとCMD
に指定したコマンドが/bin/bash -c
の引数に渡されていることがわかります。(後から解説しますがshell形式で指定しているのがポイントです)
FROM ubuntu:latest
CMD /bin/bash
$ docker run -it --name fuga fuga
root@7a8318a62284:/# ps la
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 1 0 20 0 2380 1432 do_wai Ss pts/0 0:00 /bin/sh -c /bin/bash
4 0 7 1 20 0 4296 3468 do_wai S pts/0 0:00 /bin/bash
ENTRYPOINT
は、このデフォルトだと/bin/sh -c
になっている箇所をカスタマイズすることが可能です。
例えば、以下のようなスクリプトをENTRYPOINT
に指定することによって、CMD
の実行前に"hello world"
を出力することができます。
#!/bin/bash
echo "hello world"
"$@"
FROM ubuntu:latest
COPY /entrypoint.sh /bin/
RUN chmod +x /bin/entrypoint.sh
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD [ "bash" ]
$ docker run -it --name fuga fuga
hello world
root@c8a263464b89:/# ls
CMD
を実行する前に必要な処理をENTRYPOINT
に指定することがほとんどだと思います。
Railsで自動生成されるdocker-entrypointを見ると、前処理として必要なdbのsetupなどが記載されていることがわかります。
shell形式とexec形式
CMD
とENTRYPOINT
では、shell形式とexec形式のどちらも指定することができます。
# shell形式
CMD echo "hello world!"
#exec形式
CMD [ "echo", "hello world!" ]
shell形式の場合は、その名の通りshellがCMD
を解釈しそのコマンドを実行します。
なのでCMD
に指定したコマンドはshellからforkされて実行されています。
FROM ubuntu:latest
CMD /bin/bash
root@4e6c0bdbb347:/# ps la
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 1 0 20 0 2380 1432 do_wai Ss pts/0 0:00 /bin/sh -c /bin/bash
4 0 7 1 20 0 4296 3548 do_wai S pts/0 0:00 /bin/bash
exec形式の場合は、直接コマンドが実行されます。そのまま実行されるのでPIDは1になります。
FROM ubuntu:latest
CMD ["/bin/bash"]
root@eb20652c9389:/# ps la
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 1 0 20 0 4296 3544 do_wai Ss pts/0 0:00 /bin/bash
4 0 9 1 20 0 7660 3468 - R+ pts/0 0:00 ps la
Dockerfile以外のカスタムイメージの作り方
docker commit
カスタムイメージを作る際にはDockerfileを使うことが多いですが、docker commitを使うことによって、コンテナに対して変更を加え、そこからカスタムイメージを作成することも可能です。
例えばubuntu:latest
をベースイメージとしてタグを切り、コンテナを立ち上げて変更分をイメージに反映させようとすると以下のようになります。
$ docker tag ubuntu:latest hoge
$ docker run -it --name hoge hoge
# コンテナに対して変更を加える
root@77ca2ef25a30:/# apt update -y && apt install -y vim
root@77ca2ef25a30:/# exit
# コンテナの変更分をイメージに反映させる
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77ca2ef25a30 hoge "/bin/bash" About a minute ago Exited (0) 23 seconds ago hoge
$ docker commit 77ca2ef25a30 hoge
参考