🐕

Dockerfileを,理解して書きたいよ~

2023/02/05に公開

はじめに

お気づきの方もいらっしゃるかもしれませんが,タイトルはマヂラブの野田さんを意識してます.
それはさておき,本記事は忘備録です.Dockerfile を書いているものの,挙動をあまり理解できていない人向けに書いています.

前提

Dockerfile の書き方として,shell 形式exec 形式参考1参考2)が存在し,それぞれにメリット,デメリットがあります(公式ドキュメントでは exec 形式が推奨).

コマンド

FROM

ベースとなるイメージを指定します.1 番上に書くことが多いです.
どういったものを指定するのか想像がついていない人もいると思うので軽く伝えておくと,OS であれば,linux ディストリビューション(典型は Debian系),そのほかにもミドルウェアとして MySQL や nginx,さらにはソフトウェアとして python を指定することもあります.自分がどのレベルから環境を変えたいかによって指定するイメージが変わります

補足

FROM [イメージ名]:[バージョン]で指定し,バージョンを指定しなければ,latest(最新版)が採用されます.

# 通常指定
FROM ubuntu:18.04
# 最新版が勝手に適用される
FROM ubuntu

RUN

イメージ作成時に実行するコマンドの指定できます.複数行にわたって書くこともできますが,イメージサイズを抑える目的で「&&」を使用して一行に書くことが推奨されています.
可読性のために,スラッシュで改行して表示することが多いです.

補足
FROM ubuntu
RUN apt-get update && apt-get install -y \
	    package-foo \
	    ...

WORKDIR

絶対パスを指定することで,この命令を書いた行以降はこのディレクトリで操作が行われます.

補足
FROM ubuntu
# tmp ディレクトリを作成して,tmp ディレクトリで作業
RUN mkdir tmp
WORKDIR /tmp

EXPOSE

コンテナが特定のポートを listen していることを Docker に通知する役割を果たします.

# 80番ポートをlistenしていることを通知
EXPOSE 80

ARG と ENV

環境変数を指定できます.ENV はコンテナに対して環境変数を設定する一方で,ARG のよる変数は 同一 Dockerfile 内でのみ使用可能です.(つまり,そのコンテナ全体で共有したいような変数を事前に決めておきたい場合は ENV,インストールするソフトウェアのバージョンといったビルド時に必要なもの等は ARG という認識でよさそうです)

補足

ARG [変数名]=[値] or ENV [環境変数名]=[値]のように記述できます.

ARG VERSION="18.04"
FROM ubuntu:${VERSION}

ARG TMP1="HI"
ENV TMP2="HELLO"

RUN mkdir hello
WORKDIR /hello

# Dockerfile 内では共にアクセス可能だが,ターミナルで echo $TMP1 とすると何も出力されない
RUN echo $TMP1 >> greeting
RUN echo $TMP2 >> greeting

COPY と ADD

指定したファイルやディレクトリをコンテナ内にコピー(追加) する.
COPY はローカルファイルをコンテナ内にコピーするだけだが,ADD は自動的に tar 展開してからコピーします.(ADD はリモートファイルを追加できる点も COPY と異なります)
COPY [コピー元] [コピー先] or ADD [追加元] [追加先]で記述します.

補足
FROM ubuntu

RUN mkdir hello
WORKDIR /hello

# tar ファイルを展開
ADD ./tar_dir/hello.tar /hello/ 
# Dockerfile と同階層に置いている for_copy ファイルをコンテナ内の hello ディレクトリに追加
COPY ./for_copy /hello/

CMD と ENTRYPOINT

コンテナ実行時にデフォルトで実行するコマンドやオプション指定で,原則末尾に書きます.複数書いても一番最後の命令しか処理されないことに注意してください.(補足でも述べていますが,CMD と ENTRYPOINT は完全に互換性のあるものではなく,併用することも可能な点に注意してください)

補足
FROM ubuntu

# 確実に実行するコマンド
ENTRYPOINT ["ls"]
# 引数として機能(docker run の引数で上書き可能)
CMD ["-a"]

併用する例を実行してみました.コンテナ起動時に ls コマンドが起動し,a オプションで隠しファイルを表示する操作をデフォルトとしています.これを変更するには,docker run 時の引数部分を-lにするなど,別のオプションをつけるとよいです.

SHELL

RUN, CMD, ENTRYPOINT 命令などの shell 形式で記述されている shell の種類を指定できます.デフォルトでは,Linux は["/bin/sh", "-c"],windows は["cmd", "/S", "/C"]となっているようですね.
以下は,単にシェルを変更して出力するだけの Dockerfile です.

FROM ubuntu
SHELL ["/bin/bash", "-c"]
RUN mkdir /hello && touch greeting
WORKDIR /hello

# /bin/bash と出力
RUN echo $0 > greeting
SHELL ["/bin/sh", "-c"]

# /bin/sh と出力
RUN echo $0 >> greeting

VOLUME

マウントする位置を示します.docker run -v /foo/bar と同じです.
VOLUME ["/foo/bar/"] or VOLUME /foo/bar/ のような指定が可能です.

補足
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

ちなみにこの例は公式ドキュメントそのものですが,試してみたところ,動作としては ubuntu の最新版コンテナを引っ張て来て,ルート直下に「myvol」というディレクトリを作成後,greetingというファイルに「hello world」を書きだします.最後の volume で myvol をホスト側のとあるディレクトリに匿名ボリュームでマウントしているようでした.

おわりに

ここまでご覧いただきありがとうございます.ここで共有したコードは GitHub で公開しているので,是非ご自身の環境で動かして理解してください.

https://github.com/amac-53/Dockerfile-zenn/

他にもいくつか命令があるようですが,あまり使用されている例を見かけないので,ここでは記述しませんでした.気になった方は公式ドキュメントをご覧ください.
書き方をある程度理解した(つもり)なので,今後は,メモリ削減・ビルドに要する時間の短縮・セキュリティといったより実用面に目を向けて学習していきます.

参考

Discussion