1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Dockerfile ベストプラクティス

Last updated at Posted at 2022-03-03

この記事は

自分のために公式ドキュメントを自分がほしいものだけを簡単にまとめたものです。

Dockerfile ベストプラクティス

Dockerのイメージはレイヤーというものが積み重なってできているものだと考えればよいです。Dockerfileの各行が前の行でつくったレイヤーに新しいレイヤーを重ねていきます。

基本的にするべきこと

できるかぎりエフェメラルにする

エフェメラルとは簡単に壊せて簡単に作成できること。(たとえば、ホスト側に○○のファイルがあることが条件とかがあるとエフェメラルではなくなっていきます)

ビルドコンテキストのファイルがすべてデーモンに送られることを意識する

ビルドコンテキストのファイルがDockerデーモンに送られる。-fでDockerfileを指定してもこれは変わらない。docker build PATH の PATH がビルドコンテキストになる。ビルドに必要ないファイルはビルドコンテキストを変えて送らないようにするのが良い。
どのくらいの容量がDockerデーモンに送られるのかはビルドするときのログを見ること。以下のようなログがでるはずである。

Sending build context to Docker daemon  187.8MB

標準入力からビルドするとビルドコンテキストとして送られるファイルが0になる

ファイルを作らずに標準入力からDockerfileの内容を入力して docker build することもできる。

フォーマットは

docker build [OPTIONS] -

な感じで、

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -

とか、

docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

とかのように書ける。

これをすると、ビルドコンテキストとして送られるファイルがなくなる。ビルドするのにホスト上のファイルが不要であればこれでビルドが速くなる。

これをすると、ファイルが何も送られなくなるのでCOPYADDは失敗する。

いくつかのファイルだけをビルドコンテキストのファイルとして贈りたくないときは (.dockerignore)[https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#exclude-with-dockerignore] を使うこと。

ビルドコンテキストはローカルディレクトリーにしてDockerfileは標準入力から読みたい場合

docker build [OPTIONS] -f- PATH

ビルドコンテキストをリモートの場所にしてDockerfileを標準入力から読みたい場合

ほぼ同じだが PATH が URL になる。

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF

マルチステージビルドを使え

# syntax=docker/dockerfile:1
FROM golang:1.16-alpine AS build

# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep

# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only

# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project

# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

不要なモジュールはインストールするな

そのまま

アプリケーションを疎にする

役割でちゃんと分ける。アプリとDBとキャッシュのコンテナに分けるとか。

レイヤーの数を減らすことについて

(よくわからないから省略)

引数が複数行になる場合にはソートする

半角スペースと\で分けて、各引数が英数字順になるようにすると読みやすくてよい

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

ビルドキャッシュを有効に使え

--no-cache=truedocker buildで使えばキャッシュを使わないようにできる。でも遅くなる。どういうときにキャッシュを使って、どういうときにキャッシュを使わないほうが良いのかを知っておくのがよい。

  • ADDCOPYはファイルの中身のキャッシュを見て更新されているかを判定してキャッシュを使うかどうかを決める
  • その他のDockerfileのコマンドはファイルの中身を見ない。コマンドの文字列が変わっているかどうかだけを見る

Dockerfile内の命令について

FROM

できるだけ公式のイメージを使え。Alpineがおすすめ。

LABEL

ラベルつけたほうがいいよってこと

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

RUN

apt-get

よく使う。よくハマるのは以下。

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx

ってすると RUN apt-get update がキャッシュを使われてしまい実行されないので、

RUN apt-get update && apt-get install -y

のようにするか、以下のようにバージョン固定(バージョンピニング)する。以下の例ではapt cacheを消しているのでイメージが小さくなって良い。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

※公式DebianとUbuntuは自動でapt-get cleanをするのでrmは不要

パイプ

/bin/sh -cを使って実行するのパイプの最後のコマンドの終了コードしか見ない。それ以前のコマンドが失敗しても成功とみなすので注意すること。
この動作を変えたいのであればset -o pipefail &&を使うこと

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

dashなどは-o pipefailを使えないので

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

などとする

CMD

  • サービスを提供するコンテナであればCMDCMD ["executable", "param1", "param2"…]の形式で使うのがほとんどだと思う
  • CMD ["param", "param"] ENTRYPOINTの使い方を知っている人が少ないのでやめておくのが良さそう
  • サービス提供のコンテナでなければCMD ["perl", "-de0"], CMD ["python"], or CMD ["php", "-a"] みたいなインタラクティブシェルにするのがよい

EXPOSE

アプリケーションが使うデフォルトのポートを使うのが良い。Apacheウェブサーバーであれば80など。

ENV

(難しそうなので省略。ビルド時の変数であればARG使えば良さそう)

ADD or COPY

ADDよりCOPYを使うのがよい。ADDは単純なコピーではなくtar解凍だったりリモートURLが使えたりするので機能が複雑なので。例えば、ADD rootfs.tar.xz /とかでtar解凍ができる。

また、ADDするとレイヤーが増えるのでパッケージのダウンロードとインストールをするのならば、

ADD https://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

ではなくて、

RUN mkdir -p /usr/src/things \
    && curl -SL https://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

のようにするのがよい

ENTRYPOINT

これがデフォルトで実行されるコマンドの意味。これにCMDで引数を指定することができる。

VOLUME

変更可能なファイル、ユーザーがサービスの一部として提供するファイル部分にはボリュームを使えって話

USER

権限が必要ないのであればUSERをつかってrootじゃないユーザーを使おうって話

WORKDIR

WORKDIRをいつも使おう、絶対パスで使おうって話。

ONBUILD

(自分の作ったイメージが他のイメージのベースを作るときのFROMに使われるときの話で、自分はやらなそうなので省略)

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?