こんにちは。Яeiです。
今回は、現役エンジニアの私がDockerfileのWORKDIR の書き方について記事にしようと思います。
WORKDIR は利用しなくても特に問題なく期待通りの動作になってしまうことが多いのであまり用途を詳しく理解している人はいないかもしれません。
しかし、この記載をひと手間しておくことでDockerfileが書きやすくなったり、読みやすくなったりしますので是非覚えておきましょう。
当記事は以下の公式リファレンスを元に記載しております。
Dockerfile reference >>
目次
結論:WORKDIRの書き方
書き方は以下となります。
WORKDIR <working directory path>
※ working directory path にはコンテナの中で作業するベースディレクトリを指定します。
存在しないパスを指定した場合は新規で作成されます。
詳細:WORKDIRの書き方
前提知識(誰しも一度は RUN cd と書いて失敗する?)
レファレンスに目を通すことなくDockerfileを書き始めた人は、おそらく誰しもが以下のような書き方をしてビルド失敗するのではないでしょうか。
私はそうでしたし、私の周りもことごとく散っていきました。
(いや、最初にリファレンスに目を通しなさい、と思いますが)
$ cat Dockerfile
FROM nginx:latest
RUN adduser rei
RUN echo "test" > /home/rei/test.txt
RUN cd /home/rei
RUN cat test.txt
上記Dockerfileは、ユーザディレクトリ直下にファイルを作成し、移動してcatで中身を確認するだけの簡単な例になります。
これをもとにイメージビルドすると以下のように怒られます。
#8 [5/5] RUN cat test.txt
#8 sha256:db6aba3cea8967dbbc3641d7728a07c1c3f4bfc4562afeade75450262cdb2d76
#8 0.419 cat: test.txt: No such file or directory
#8 ERROR: executor failed running [/bin/sh -c cat test.txt]: exit code: 1
細かい部分は別途記事で書きたいと思いますが、ざっくりと説明しますと、
一つのRUN命令で一つのコンテナ内で作業しているイメージ
となります。つまり上の例の場合、cdで移動したコンテナと、cat test.txt するコンテナは別ものなので「No such file or directory」となるのです。
ちなみに、一つのRUN命令でこういった事象が発生することから、以下のように書けば良いと思われるかもしれません。
RUN cd /home/rei; \
cat test.txt
これは一つの方法としては正解で、オフィシャルイメージでもこの手法を用いていることもあります。
ただし、Dockerのベストプラクティスでは解読が困難になるのであまり使わない方が良いかもしれない旨が記載されております。
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
you should use WORKDIR instead of proliferating instructions like RUN cd … && do-something, which are hard to read, troubleshoot, and maintain
ではこのような処理が必要になった場合はどうすればよいのでしょうか。
そこで登場するのがWORKDIRになるのです。
Dockerfileの可読性と相談して、WORKDIR だと逆に可読性が落ちる場合にこの記載方法を検討すると良いでしょう。
WORKDIRの使い方
WORKDIR <working directory path>
※ working directory path にはコンテナの中で作業するベースディレクトリを指定します。
存在しないパスを指定した場合は新規で作成されます。
例えば、FROM句で指定するイメージ次第では指定されている作業ディレクトリがどこになっているのかが分からないことがあります。
その状態でDockerfileでカレント配下のファイル指定をしてしまうと想定外の動作をしてしまいます。
こういった想定外の動作を回避するべく、自分が利用するDockerfileでは極力WORKDIRを指定してあげると良いでしょう。
例えば、ユーザホームディレクトリをワーキングディレクトリに設定したい場合は以下のようにしてあげれば問題ありません。
$ cat Dockerfile
FROM nginx:latest
RUN adduser rei
RUN echo "test" > /home/rei/test.txt
WORKDIR /home/rei
RUN cat test.txt
例えばapacheの公式イメージを参照させて頂くと、Dockerfileの冒頭の方に以下のように設定されております。
ENV HTTPD_PREFIX /usr/local/apache2
ENV PATH $HTTPD_PREFIX/bin:$PATH
RUN mkdir -p "$HTTPD_PREFIX" \
&& chown www-data:www-data "$HTTPD_PREFIX"
WORKDIR $HTTPD_PREFIX
コンテナは基本的に1プロセス1コンテナがベストプラクティスとされております。
そのため、WORKDIRの指定は自ずと適切なものが定まってくると思います。
WORKDIRをmkdir代わりに使うのはどうなの?
WORKDIRの「存在しないパスを指定した場合は新規で作成されます。」という特性について考察したいと思います。
WORKDIRは名前の通り「作業するためのディレクトリ」という意味合いが強い命令になります。
そのため「存在しないパスを指定した場合は新規作成」という機能は副次効果という意味合いで取っておいた方が良いと思います。
(これはあくまでも私個人の意見です)
そのため、ディレクトリ作成のためだけにこの命令を使うのはナンセンスとなります。
例えば以下の例は理想ではありません。
FROM nginx:latest
RUN adduser rei
ARG USER_HOME="/home/rei"
WORKDIR ${USER_HOME}
WORKDIR ${USER_HOME}/logs
WORKDIR ${USER_HOME}/bin
WORKDIR ${USER_HOME}/src
意図としてはユーザホーム直下にlogs, bin, srcディレクトリが必要になるため、ディレクトリ作成しております。
このブロックの下の方でこれらのディレクトリに資材を配置していくことを想定してのことですが、これでは「作業用のディレクトリ指定」の意味が分かりません。
WORKDIRの直下にWORKDIRがきてしまったら最初のWORKDIRは何の作業のために必要なの?となってしまいます。
こういった場合は素直に以下のように記述した方が可読性が上がります。
FROM nginx:latest
RUN adduser rei
ARG USER_HOME="/home/rei"
WORKDIR ${USER_HOME}
RUN mkdir ./logs ./bin ./src
また、コンテナログインした際のカレントディレクトリは最後にWORKDIRで設定した場所になります。
そのため、先の例では「\${USER_HOME}/src」がカレントになっていたのに対して、この例では「\${USER_HOME}」がカレントになるわけです。
おまけ
これは完全におまけになりますが、Dockerのドキュメントにある引っ掛け問題を記載しておきます。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
この命令で最後のpwdで何が表示されるでしょうか?というもの(最初/aは存在しない状態とする)。
答えは/a/b/cになります。ここまで読まれた方は当たり前に感じるかもしれませんが、面白い例だなと思いました。
当記事は以上となります。
WORKDIR命令は使わなくても問題はありませんが、作業ディレクトリを明確にする意味では設定しない理由はありません。
Dockerのドキュメントでも明示することを勧めておりますので是非、覚えておいてください。
長々とお疲れさまでした。