Docker – Dockerfile のベストプラクティスを解説

概要

効率的で保守しやすい Dockerfile を作成することは、コンテナのパフォーマンスやセキュリティを向上させるために重要です。 本記事では、Dockerfile のベストプラクティスについて解説します。

ベストプラクティス

Docker の Linter ツール hadolint の内容などを参考に、整理しました。

パッケージマネージャー

パッケージマネージャーによって違いはありますが、基本的には以下の点に注意して記述します。

  • 不要なパッケージをインストールしない
  • -y オプションを使用して、インストール時にインストールの確認を求められないようにする
  • インストールするパッケージは、レポジトリの更新でバージョンが変わらないように固定する
  • イメージサイズが肥大化しないように、インストール後にキャッシュを削除する

apt (Ubuntu/Debian)

  • apt はエンドユーザー向けのコマンドなので、代わりに apt-getapt-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/*
Docker

pip

  • pip でバージョンを固定する (DL3028)
  • pip で --no-cache-dir オプションを使用する (DL3042)
# pip
RUN pip install --no-cache-dir django==1.9
Docker

gem

  • gem でバージョンを固定する (DL3013)
# gem
RUN gem install bundler:1.1
Docker

npm/yarn

  • npm でバージョンを固定する (DL3016)
  • yarn でキャッシュをクリアする (DL3060)
# npm
RUN npm install express@4.1.1
# yarn
RUN yarn install \
    && yarn cache clean
Docker

WORKDIR、COPY、ADD コマンドのパスの指定は絶対パスにする

WORKDIRCOPYADD コマンドでパスを指定する際には、絶対パスを使用することが推奨されます。 相対パスを使用すると、現在の作業ディレクトリに依存して動作が変わる可能性があるため、絶対パスを使用することで一貫性を保つことができます。

ベースイメージの指定

FROM コマンドによるベースイメージの指定は、タグも明示的に指定します。

  • latest タグは配布元の更新により再現性がなくなるため、使用しない (DL3006, DL3007)
  • --platform= オプションは Dockerfile がプラットフォーム依存になるため、使用しない DL3029
FROM debian:jessie
Docker

wget/curl

  • wget/curl は同等の機能を持つコマンドのため、どちらか一方で統一します。 (DL4001)
  • wget を使用する場合、--progress オプションを指定して、ビルドログが肥大化することを防ぎます。(DL3047)
RUN wget --progress=dot:giga https://example.com/big_file.tar
Docker

ファイル・ディレクトリのコピーは ADD コマンドではなく COPY コマンドを使用する

ADD コマンドはファイルやディレクトリをコピーするだけでなく、URL からのダウンロードや圧縮ファイルの展開などの追加機能を持っています。そのため、単純なファイルやディレクトリのコピーには COPY コマンドを使用することが推奨されます。これにより、意図しない動作を避けることができます。

NG
ADD requirements.txt /usr/src/app/
Docker
OK
COPY requirements.txt /usr/src/app/
Docker

非 root ユーザーを作成する

  • ユーザーはセキュリティリスクのある root ユーザーのままにせず、USER で非 root ユーザーに切り替えます。 (DL3002)
  • useradd でユーザーを作成する際は -l オプションを使用して、 lastlog および faillog データベースにユーザーを追加しないようにすることで、イメージサイズを削減できます。(DL3046)
# パッケージのインストールなど root ユーザーで先に行う

# -l オプションを指定してユーザーを作成
RUN useradd -ml -s /bin/bash -G sudo hoge
# 非 root ユーザーを切り替える
USER hoge
Docker

連続した RUN コマンドは 1 つにまとめる

連続した RUN コマンドは 1 つにまとめることで、イメージサイズを削減できます。(DL3059)

NG
RUN command1
RUN command2
Docker
OK
RUN command1 \
    && command2
Docker

作業ディレクトリの変更は cd ではなく、WORKDIR で行う

作業ディレクトリの変更は cd ではなく、WORKDIR で行います。(DL3003)

NG
RUN cd /usr/src/app \
    && git clone git@github.com:lukasmartinelli/hadolint.git
Docker
OK
WORKDIR /usr/src/app
RUN git clone git@github.com:lukasmartinelli/hadolint.git
Docker

デフォルトのシェルの変更は SHELL コマンドで行う

デフォルトのシェルの変更は、/bin/sh のシンボリックリンクの張り変えではなく、SHELL コマンドで行います。(DL4005)

NG
RUN ln -sfv /bin/bash /bin/sh
Docker
OK
SHELL ["/bin/bash", "-c"]
Docker

パイプを使用する場合、-o pipefail を設定する

RUN コマンドでパイプを使用した場合、パイプ内の最後のコマンドの終了コードのみ評価して成功を判断するため、途中のコマンドが失敗した場合でも、最後のコマンドが成功する限り、このビルドステップは成功し、新しいイメージが生成されます。 途中のコマンドが失敗した場合にビルドに失敗するようにするには、-o pipefail を設定します。(DL4006)

NG
RUN wget -O - https://some.site | wc -l > /number
Docker
OK
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget -O - https://some.site | wc -l > /number
Docker

CMD/ENTRYPOINT は JSON 形式で指定する

CMD や ENTRYPOINT をシェル形式で指定すると、意図しないシェルの解釈や展開が行われる可能性があります。 これを防ぐために、JSON 形式で指定することが推奨されます。これにより、コマンドと引数が正確に渡されるようになります。

  • CMD

    NG
    ENTRYPOINT s3cmd
    Docker
    OK
    ENTRYPOINT ["s3cmd"]
    Docker
  • ENTRYPOINT

    NG
    CMD my-service server
    Docker
    OK
    CMD ["my-service", "server"]
    Docker

コメント

コメントする