想定読者
- Dockerは使ったことあるけど、Dockerfileを真面目に書いたことはない方
- 気にすべきことを1つでも知っておきたいと思っている方
気をつけなきゃと思ったこと
1. コマンドの微妙な違いを理解しておく
1.1 RUN cd <dir>
と WORKDIR <dir>
どちらも作業ディレクトリを指定するという意味では同じですが、微妙な違いを認識しておらず少しハマりました。
例えば、centosのベースイメージにnginxをソースからビルドしてインストールする場合、深く考えずに
RUN cd /usr/local/src
RUN curl -O https://nginx.org/download/nginx-1.18.0.tar.gz
RUN tar xvzf nginx-1.18.0.tar.gz
RUN cd nginx-1.18.0
RUN ./configure
RUN make
RUN make install
のようにかいて docker build
しようとすると、
/bin/sh: ./configure: No such file or directory
となります。
はじめは、curlでファイル取れてない?tarで解凍失敗した?などなど考え検証するのですが、実際には4行目で /nginx-1.18.0 に移動できていないことが原因だったりします。
RUN cd nginx-1.18.0
とだけ書いてしまうと、/nginx-1.18.0
を一瞬覗いただけで実際には移動しておらず、その後のコマンドはカレントディレクトリで実行されてしまうため、./configureなんてないよ、と言われているという次第です。なので一行目のcdも機能しておらず、結果的に全てのコマンドが/
で実行されている形になっています。
こんなときに、WORKDIR
を用いて、
WORKDIR /usr/local/src
RUN curl -O https://nginx.org/download/nginx-1.18.0.tar.gz
RUN tar xvzf nginx-1.18.0.tar.gz
WORKDIR /usr/local/src/nginx-1.18.0
RUN ./configure
RUN make
RUN make install
のようにに書きかえてあげるとちゃんとディレクトリを渡り歩いてうまく動いてくれます1。
RUN cd /usr/local/src/nginx-1.18.0 && ./configure && make && make install
のように書くこともできます。
というか、イメージ容量削減の観点では後者の方が望ましいようです。(説明は3.2にて)
1.2 ARG
と ENV
変数を定義するという意味では同じですが、ARGは頻繁には使われないイメージです。違いは以下。
-
ENV
→ コンテナ内に定義される環境変数 -
ARG
→ Dockerfile内でのみ使用できる変数
私は ARG
は主に、
ARG NGINX_VERSION
RUN curl -O https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
docker build --build-arg NGINX_VERSION=1.18.0 --tag=sample_image:sample_tag
のようにビルド時に --build-arg
で渡したバージョンをDockerfile内で参照する形で使っています。
1.3 VOLUME
とdocker run -v
これは、Dockerfileのコマンドというより、Dockerのマウントの種類の違いに関するものなので少し逸れますが、若干ハマったのでメモ的に記載します。マウントの種類とその違いについては各自調査いただければと思います。
Dockerfile内に VOLUME <path>
とかくと、docker run -v <path>:<path>
と書いたのと同等なのかと思っていましたが、前者は マウントの種類のうち「(Docker)ボリューム」によるマウントで、後者は「バインドマウント」によるマウントであって、挙動が異なるということを書いておきます。(詳細は長くなりそうなので別記事で書くかもしれません。)
2. 認証情報等をイメージに残さないようにする
社内環境でDockerを使うととにかくプロキシの壁にはまります。
-
docker build
しようとしたら、初っ端からエラー => ビルド時にプロキシ認証情報を渡す -
yum install
/wget
/curl
しようとしたら名前解決エラー => プロキシ認証情報を設定ファイルに書き込んでから実行2 or 実行時に引数で渡す3
などなど。
ビルドするときだけでこれだけプロキシ認証情報を入れ込むタイミングがあるので、気をつけないとイメージの中のどこかに残ったままになってしまう恐れがあります。考えられるbetterな対応としては、
ビルド時にプロキシ認証情報を渡すとき
docker build --build-arg HTTP_PROXY=${HTTP_PROXY} --build-arg HTTPS_PROXY=${HTTPS_PROXY} --tag=sample_image:sample_tag
のように、ホストに事前に設定した環境変数HTTP_PROXY
, HTTPS_PROXY
を--build-argで渡す4
プロキシ認証情報を設定ファイルに書き込んだ場合
Dockerfileのなかで、書き込んだプロキシ情報をsed
などで一通り削除しておく4
3. イメージの容量がなるべく軽くなるようにする
3.1 なるべくオフィシャルイメージを使う
オフィシャルイメージは熟練者が書いたDockerfileで作られており無駄がないので、特別な理由がない限りはオフィシャルイメージを使うことをおすすめします。
以下は、nginx(latest)のイメージを例にイメージを比較した表です。
||容量|ベースイメージ|nginxインストール方法|
|:----|:----|:----|:---|:---|:---|
|Official Image|126 MB|debian:buster-slim|不要|
|自作1|328 MB|centos 8|yum install nginx
|
|自作2|487 MB|centos 8|ソースからビルド
|
「現実にはcentosあたりをベースに自作するケースが多いのでは」という仮説から自作のものはcentos 8をベースにしているので熟練者の削減術の成果がわかるものではありません。あくまで参考数値とご認識ください。centosはalpineやdebianと比べるとだいぶ容量をとるようですね。
3.2 RUNコマンドは極力まとめる
依存ライブラリがたくさんあるときに、
RUN yum install -y gcc
RUN yum install -y pcre pcre-devel
RUN yum install -y zlib zlib-devel
RUN yum install -y openssl openssl-devel
RUN yum install -y make
のように一つ一つRUN
する形で書くと、その都度イメージのレイヤが追加されてイメージの容量の増加に繋がってしまうようです。こういうときは、
RUN yum install -y gcc \
&& yum install -y pcre pcre-devel \
&& yum install -y zlib zlib-devel \
&& yum install -y openssl openssl-devel \
&& yum install -y make
のように一つの RUN
でまとめてみましょう。
3.3 マルチステージビルドを活用する
Vue.js アプリケーションを Docker 化する にあるように、1つのDockerfileの中で
- アプリのビルドイメージ作成ステージ
- ビルド成果物デプロイ用イメージ作成ステージ
のように複数のステージを定義し連携することで、アプリのビルド時のみ必要であったファイル等を最終的なイメージに含めなくて済み、イメージの軽量化につながるというものです。
そのほかにも、イメージの軽量化につながる工夫が公式ページ(以下)等にたくさん書かれているので、Dockerfileの熟練者を目指す方は熟読と実践をしてみていただければと思います。
終わりに
Dockerはなかなか慣れるまで時間がかかります。次はdocker-composeについてかけたらと思います。