この記事は
自分のために公式ドキュメントを自分がほしいものだけを簡単にまとめたものです。
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
とかのように書ける。
これをすると、ビルドコンテキストとして送られるファイルがなくなる。ビルドするのにホスト上のファイルが不要であればこれでビルドが速くなる。
これをすると、ファイルが何も送られなくなるのでCOPY
やADD
は失敗する。
いくつかのファイルだけをビルドコンテキストのファイルとして贈りたくないときは (.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=true
をdocker build
で使えばキャッシュを使わないようにできる。でも遅くなる。どういうときにキャッシュを使って、どういうときにキャッシュを使わないほうが良いのかを知っておくのがよい。
-
ADD
とCOPY
はファイルの中身のキャッシュを見て更新されているかを判定してキャッシュを使うかどうかを決める - その他の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
- サービスを提供するコンテナであれば
CMD
はCMD ["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
に使われるときの話で、自分はやらなそうなので省略)