よく使うDockerfileの指令

Dockerfileに記述する「指令」は小文字でも問題ないが、見やすい大文字とする。

指令は上から順に実行され、差分管理でデフォルトでキャッシュされ、最後まで成功すると目的の新イメージができる。一番最初はFROMとなる(例外はARG)。

以降の例では、キャッシュを使わないビルドとしてdocker build -t test --no-cache .のようにしている。


FROM

ベースとなるイメージを指定。

Dockerfile

# CentOS7の場合
FROM centos:7

ENV

環境変数を定義。コンテナやこれより後の指令で利用できる。

スペースで区切ってキー 値と指定してもいいが、=を使ってキー1=値 キー2="あ た い"のようにすれば、一行にまとめられる。

コンテナ作成時に--envで変更できる。

Dockerfile

FROM centos:7

# 環境変数
ENV target=/tmp hello=ハロー
# 以降のDockerfile指令で$targetで使える

# コンテナへコピー
COPY Dockerfile $target

ビルド、起動

docker build -t test --no-cache .

# 確認
docker run --rm -it test ls /tmp/Dockerfile
/tmp/Dockerfile

# --envで上書き
docker run --rm -it --env "hello=こんにちは" test bash -c 'echo $target $hello'
/tmp こんにちは

RUN

現時点のイメージで実行するコマンド。ビルドの過程で実行して新イメージに反映される。

rootではなく別ユーザで実行したい場合は、予めUSER指令で変更しておく。コマンド実行中に対話的なシェルになるとエラーになるので、更新・インストールなどはyum --assumeyesapt --yesなどと自動化しておく必要がある。

書式はshell形式とexec形式の2種類。

詳しくはshell形式とexec形式の違い

Dockerfile

FROM ubuntu:18.04

# shell形式: DebianやUbuntuは/bin/shがdashにリンクされている
# ここで$0はシェルの名前になる
RUN ls -l $0

# exec形式: 別のシェルで実行したい場合
RUN [ "/bin/bash", "-c", "ls -l $0" ]

これでビルドすると、shell形式では/bin/shからのdashが、exec形式では/bin/bashが実行される。

ビルド、起動

docker build -t test --no-cache .
(中略)
Step 2/3 : RUN ls -l $0
 ---> Running in cead55cf0412
lrwxrwxrwx. 1 root root 4 Apr 26 21:16 /bin/sh -> dash
Removing intermediate container cead55cf0412
 ---> fe9b89f29592
Step 3/3 : RUN [ "/bin/bash", "-c", "ls -l $0" ]
 ---> Running in a767a9b1b14a
-rwxr-xr-x. 1 root root 1113504 Apr  4 18:30 /bin/bash

COPY

ホストからコンテナにファイルやディレクトリをコピーする。.dockerignoreファイルに記述したものは対象外になる。

ホストパス ホストパス コンテナパスのように指定する。ホストに存在しないならエラー。コンテナディレクトリは自動作成。

ホストのディレクトリはその中身がターゲットの下にコピーされる。ホストパスが複数なら、コンテナパスはかならず/で終わること。

所有者を変更するなら--chown=ユーザ:グループを最初に付ける。

Dockerfile

FROM centos:7

# ホストにa/b/c/というディレクトリがあるものとする

# /xyz/b/c: コンテナディレクトリは自動作成
COPY a/ /xyz/

# /docker: ファイル名変更(末尾/ないから)
COPY Dockerfile /docker

# /docs/Dockerfile: ホストパスはワイルドカード使える
COPY Do?ke[a-z]fi* /docs/

# binユーザ、nobodyグループに
COPY --chown=bin:nobody Dockerfile /chown/

# 以降はエラー
# ホスト複数なら正しくは/xyz/
#COPY a/ a/b/ /xyz

# 存在しないグループ
#COPY --chown=root:docker Dockerfile /nogroup/

ビルド、起動

# まずディレクトリ作成
mkdir -p a/b/c

# ビルド
docker build -t test --no-cache .

# 確認
docker run --rm -it test bash -c "ls -d /xyz/b/c; ls /docker /docs/Dockerfile; ls -ld /chown/"
/xyz/b/c
/docker  /docs/Dockerfile
drwxr-xr-x. 2 bin nobody 24 May 10 15:00 /chown/

ADD

COPY指令と似ているが、ADD指令はリモートからダウンロードしたり、ローカルの圧縮ファイル(tarなど)を展開してコピーしたりできる。

リモートからダウロードするADD

Dockerfile

FROM centos:7

ENV url=http://ftp.meisei-u.ac.jp/mirror/apache/dist/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz

# リモートの場合は展開せず、ダウンロードとなる
ADD $url /download/

ビルド、起動

docker build -t test --no-cache .

docker run --rm -it test ls /download
apache-tomcat-9.0.8.tar.gz

ローカルのtarファイルを展開コピーするADD

Dockerfile

# Tomcat動かすのでjavaイメージ(alpineベース)
FROM java:8

ENV app=apache-tomcat-9.0.8 dir=/myapp

# ローカルの圧縮ファイルは展開してコピー
ADD $app.tar.gz $dir/

# 作業ディレクトリ: /myapp/apache-tomcat-9.0.8/
WORKDIR $dir/$app/

# Tomcatフォアグラウンドで起動
CMD ./bin/catalina.sh run

今回はまず、ホストにtarファイルをダウンロード。

ダウンロード、ビルド、起動

# Tomcat9.0.8のダウンロード
curl -LO http://ftp.meisei-u.ac.jp/mirror/apache/dist/tomcat/tomcat-9/v9.0.8/bin/apache-tomcat-9.0.8.tar.gz
ls
Dockerfile  apache-tomcat-9.0.8.tar.gz

# ビルド
docker build -t test --no-cache .

# Tomcatスタート
docker run --rm -dit --name test --publish 80:8080 test

# 確認
curl localhost

# コンテナ停止
docker stop test

WORKDIR

作業ディレクトリを指定。それ以降の指令(RUN,CMD,ENTRYPOINT,COPY,ADD)やコンテナ起動でのカレントディレクトリとなる。存在しなければディレクトリ自動作成。

RUN指令でのcdコマンドでは、それ以降の指令には反映しないので、作業ディレクトリはWORKDIRを使う。

コンテナ作成時に--workdirで変更できる。

Dockerfile

FROM centos:7

# WORKDIRで環境変数を使える
ENV dir=/host/abc

# フルパスで作業ディレクトリ指定
WORKDIR $dir
# 相対パス
WORKDIR xyz

# /host/abc/xyz
RUN pwd

ビルド、起動

docker build -t test --no-cache .

(中略)
/host/abc/xyz

# コンテナ作成時に変更するなら--workdir、必ずフルパス
docker run --rm -it --workdir /full/path/my-workdir test pwd
/full/path/my-workdir

USER

ユーザの指定。それ以降のRUNCMDENTRYPOINT指令と、コンテナ起動時のコマンドはこのユーザで実行される。WORKDIRで作成されるディレクトリはrootが所有者。

コンテナ作成時に--userで変更できる。

Dockerfile

FROM centos:7

# ユーザ追加してから
RUN useradd test-user
# ユーザ指定
USER test-user

# test-userで実行される
RUN touch /tmp/empty

# これはroot
WORKDIR /tmp/workdir

RUN ls -l /tmp

ビルド、起動

docker build -t test --no-cache .
(中略)
-rw-r--r--. 1 test-user test-user   0 May 20 06:14 empty
drwxr-xr-x. 2 root      root        6 May 20 06:14 workdir

# コンテナ作成時も引き継がれる
docker run --rm -it test whoami
test-user

# --userでユーザ変更
docker run --rm -it --user nobody test whoami
nobody

ARG

ビルド中だけ使える変数の定義。Dockerfileではデフォルト値を定義しておき、docker build --build-arg=で変更することもできる。

ENV指令と異なり、FROM指令より前に使えるが、コンテナに引き継がれない。

Dockerfile

# ARG指令はFROMより前に使える
ARG image=centos:7

# デフォルトでcentos:7
FROM $image

# Linuxディストロ名を取り出す
RUN grep 'PRETTY_NAME' /etc/os-release

ビルド、起動

# デフォルトでビルドの場合
docker build -t test --no-cache .
(中略)
Step 1/3 : ARG image=centos:7
Step 2/3 : FROM $image
 ---> e934aafc2206
Step 3/3 : RUN grep 'PRETTY_NAME' /etc/os-release
 ---> Running in 71eea118f15d
PRETTY_NAME="CentOS Linux 7 (Core)"

# --build-argで変更した場合
docker build -t test --no-cache --build-arg image=debian:9 .

(中略)
Step 1/3 : ARG image=centos:7
Step 2/3 : FROM $image
 ---> 8626492fecd3
Step 3/3 : RUN grep 'PRETTY_NAME' /etc/os-release
 ---> Running in 75efbe14fb07
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"

# コンテナには引き継がれない
docker run --rm -it test bash -c "echo $image"


EXPOSE

コンテナのサーバが受付するポートをイメージ利用者に対して明示するための、ドキュメントとしての役割。

これが無くても、「コンテナIPアドレス:そのサーバのポート」でアクセスできる(コンテナIPアドレスに到達できるクライアントならば)。

「ホストIPアドレス:ホストポート」でアクセスできるようにする(「コンテナIPアドレス:コンテナポート」に転送する)なら、--publishが必要(EXPOSEがあってもなくても)。

Dockerfile

FROM httpd:2.4

# httpdサーバは普通80ポートなのを変更
RUN sed -i 's/Listen 80/Listen 8888/' /usr/local/apache2/conf/httpd.conf

# 通常と違うポートにしたら、なおさらドキュメントとして明示
EXPOSE 8888

# コンテナ起動で実行するコマンド
CMD httpd -D FOREGROUND

ビルド、起動

docker build -t test --no-cache .

# --publishでポート転送
docker run --rm -dit --name test --publish 9999:8888 test

# 確認
curl localhost:9999
<html><body><h1>It works!</h1></body></html>

# 停止
docker stop test

ONBUILD

ONBUILDは次回以降のビルド時に実行される指令を指定する。その指令は今回のビルドでは実行されない。今回のビルドを基本イメージとし、さらに基本イメージから本番イメージをビルドする前提。

例えばWebサーバのコンテンツをホストからコピーするのは本番のときで、基本のイメージは事前に作っておくケース。

基本イメージのDockerfile

FROM httpd:2.4

# 次回以降のビルドで実行する指令
# Webコンテンツをコンテナへコピー
ONBUILD COPY index.html /usr/local/apache2/htdocs/

基本イメージweb-serverのビルド

docker build -t web-server --no-cache .

本番イメージのDockerfile

FROM web-server

本番イメージsite1の準備

mkdir site1; cd site1;

# site1のDockerfile
echo 'FROM web-server' > Dockerfile

# site1のコンテンツ作成
echo 'Hello, site1!' > index.html

本番イメージsite1のビルド

# site1のビルド
docker build -t site1 --no-cache .

# サーバ起動
docker run --rm -dit --name test --publish 80:80 site1

# 確認
curl localhost
Hello, site1!

# 停止
docker stop test

CMDとENTRYPOINT

CMDENTRYPOINTは、どちらもコンテナ起動時に実行されるプログラム(コマンド)を指定する。

両方を併用する場合、単独で使用する場合、コンテナ作成時にコマンド引数を渡す場合(docker createの後端のコマンド部分)、shell形式とexec形式の違いなどと、ケースによって動作が異なる。複数回記述してもそれぞれは最後の一つが有効。

サーバなど動作継続したいコンテナの場合、フォアグラウンドにしたプログラムを指定しないと、すぐにコンテナ停止となる。

詳しくは、CMDとENTRYPOINTの違い


STOPSIGNAL

STOPSIGNALはあまり使わないかもしれないが、詳しくはコンテナ終了シグナルとSTOPSIGNALへ。