概要
効率的で保守しやすい Dockerfile を作成することは、コンテナのパフォーマンスやセキュリティを向上させるために重要です。 本記事では、Dockerfile のベストプラクティスについて解説します。
ベストプラクティス
Docker の Linter ツール hadolint の内容などを参考に、整理しました。
パッケージマネージャー
パッケージマネージャーによって違いはありますが、基本的には以下の点に注意して記述します。
- 不要なパッケージをインストールしない
-y
オプションを使用して、インストール時にインストールの確認を求められないようにする- インストールするパッケージは、レポジトリの更新でバージョンが変わらないように固定する
- イメージサイズが肥大化しないように、インストール後にキャッシュを削除する
apt (Ubuntu/Debian)
apt
はエンドユーザー向けのコマンドなので、代わりにapt-get
やapt-cache
を使用する (DL3027)- 推奨パッケージをインストールしないように
--no-install-recommends
オプションを指定する (DL3015) - バージョンを固定する (DL3008)
-y
オプションを使用する (DL3014)- インストール後にキャッシュを削除する (DL3009)
RUN apt-get update && apt-get install -y --no-install-recommends \
python=2.7.* \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
pip
# pip
RUN pip install --no-cache-dir django==1.9
gem
- gem でバージョンを固定する (DL3013)
# gem
RUN gem install bundler:1.1
npm/yarn
# npm
RUN npm install express@4.1.1
# yarn
RUN yarn install \
&& yarn cache clean
WORKDIR、COPY、ADD コマンドのパスの指定は絶対パスにする
WORKDIR
、COPY
、ADD
コマンドでパスを指定する際には、絶対パスを使用することが推奨されます。
相対パスを使用すると、現在の作業ディレクトリに依存して動作が変わる可能性があるため、絶対パスを使用することで一貫性を保つことができます。
ベースイメージの指定
FROM
コマンドによるベースイメージの指定は、タグも明示的に指定します。
- latest タグは配布元の更新により再現性がなくなるため、使用しない (DL3006, DL3007)
--platform=
オプションは Dockerfile がプラットフォーム依存になるため、使用しない DL3029
FROM debian:jessie
wget/curl
wget/curl
は同等の機能を持つコマンドのため、どちらか一方で統一します。 (DL4001)wget
を使用する場合、--progress
オプションを指定して、ビルドログが肥大化することを防ぎます。(DL3047)
RUN wget --progress=dot:giga https://example.com/big_file.tar
ファイル・ディレクトリのコピーは ADD コマンドではなく COPY コマンドを使用する
ADD コマンドはファイルやディレクトリをコピーするだけでなく、URL からのダウンロードや圧縮ファイルの展開などの追加機能を持っています。そのため、単純なファイルやディレクトリのコピーには COPY コマンドを使用することが推奨されます。これにより、意図しない動作を避けることができます。
ADD requirements.txt /usr/src/app/
COPY requirements.txt /usr/src/app/
非 root ユーザーを作成する
- ユーザーはセキュリティリスクのある root ユーザーのままにせず、
USER
で非 root ユーザーに切り替えます。 (DL3002) useradd
でユーザーを作成する際は-l
オプションを使用して、 lastlog および faillog データベースにユーザーを追加しないようにすることで、イメージサイズを削減できます。(DL3046)
# パッケージのインストールなど root ユーザーで先に行う
# -l オプションを指定してユーザーを作成
RUN useradd -ml -s /bin/bash -G sudo hoge
# 非 root ユーザーを切り替える
USER hoge
連続した RUN コマンドは 1 つにまとめる
連続した RUN コマンドは 1 つにまとめることで、イメージサイズを削減できます。(DL3059)
RUN command1
RUN command2
RUN command1 \
&& command2
作業ディレクトリの変更は cd ではなく、WORKDIR で行う
作業ディレクトリの変更は cd
ではなく、WORKDIR で行います。(DL3003)
RUN cd /usr/src/app \
&& git clone git@github.com:lukasmartinelli/hadolint.git
WORKDIR /usr/src/app
RUN git clone git@github.com:lukasmartinelli/hadolint.git
デフォルトのシェルの変更は SHELL コマンドで行う
デフォルトのシェルの変更は、/bin/sh
のシンボリックリンクの張り変えではなく、SHELL コマンドで行います。(DL4005)
RUN ln -sfv /bin/bash /bin/sh
SHELL ["/bin/bash", "-c"]
パイプを使用する場合、-o pipefail
を設定する
RUN コマンドでパイプを使用した場合、パイプ内の最後のコマンドの終了コードのみ評価して成功を判断するため、途中のコマンドが失敗した場合でも、最後のコマンドが成功する限り、このビルドステップは成功し、新しいイメージが生成されます。
途中のコマンドが失敗した場合にビルドに失敗するようにするには、-o pipefail
を設定します。(DL4006)
RUN wget -O - https://some.site | wc -l > /number
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget -O - https://some.site | wc -l > /number
CMD/ENTRYPOINT は JSON 形式で指定する
CMD や ENTRYPOINT をシェル形式で指定すると、意図しないシェルの解釈や展開が行われる可能性があります。 これを防ぐために、JSON 形式で指定することが推奨されます。これにより、コマンドと引数が正確に渡されるようになります。
CMD
NGENTRYPOINT s3cmd
OKENTRYPOINT ["s3cmd"]
ENTRYPOINT
NGCMD my-service server
OKCMD ["my-service", "server"]
コメント