shell形式とexec形式の違い

DockerfileRUNCMDENTRYPOINT指令で指定するコマンドの書式は、shell形式とexec形式がある。


  1. ログインシェルと/bin/shの違い
  2. shell形式の基本
  3. exec形式の基本
  4. Bashismとshell形式
  5. Bashismとシェルスクリプト
  6. exec形式でもシェルを通せば通常の環境変数を使える

ログインシェルと/bin/shの違い

多くのLinuxではログインシェル(/etc/passwdで指定)のデフォルトは/bin/bashだが、ここでいうshell形式のデフォルトは/bin/sh/bin/shの実体(リンク先)が/bin/bashではないことがあるので、特にBash固有の文法を使ったシェルスクリプト(bashism)を指定するときは注意。

Docker公式Linuxイメージで、/bin/shのリンク先

docker run --rm -it debian:9 ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Jan 24  2017 /bin/sh -> dash

docker run --rm -it ubuntu:18.04 ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Apr 26 21:16 /bin/sh -> dash

# どちらもログインシェルはBash
docker run --rm -it debian:9 grep '^root' /etc/passwd
root:x:0:0:root:/root:/bin/bash

docker run --rm -it ubuntu:18.04 grep '^root' /etc/passwd
root:x:0:0:root:/root:/bin/bash

# Alpineのシェル
docker run --rm -it alpine:3.7 cat /etc/shells
# valid login shells
/bin/sh
/bin/ash

shell形式の基本

端末で入力するように、RUN等の指令の後ろに普通にコマンドを続ける。

Dockerfile

FROM centos:7

# コマンドと引数、パイプなど、いつも通り
RUN cat /etc/resolv.conf | grep 'nameserver'

# CentOS7は/bin/shがBashなので、
# Bashism: ((算術式評価))
RUN i=0; ((i++)); echo $i;

ビルド、確認

docker build --tag test --no-cache .
(中略)
Step 3/3 : RUN i=0; ((i++)); echo $i;
 ---> Running in 2a2c4f22cd20
1

exec形式の基本

"で囲って配列のようにして、RUN等の指令の後ろに続ける。シェルで実行したいなら、["/bin/bash", "-c", "コマンド 引数1 引数2"]のようにする。

Dockerfile

FROM debian:9

# 直接実行(パイプなどは使えない)
RUN ["grep", "nameserver", "/etc/resolv.conf"]

# シェルで実行したいなら(パイプとか使える)
RUN ["/bin/bash", "-c", "cat /etc/resolv.conf | grep 'nameserver'"]

# Bashism: ((算術式評価))
RUN ["/bin/bash", "-c", "i=0; ((i++)); echo $i;"]

ビルド、確認

docker build --tag test --no-cache .
(中略)
Step 4/4 : RUN ["/bin/bash", "-c", "i=0; ((i++)); echo $i;"]
 ---> Running in c85f660d5c06
1

Bashismとshell形式

/bin/shがBashではないdebian:9ubuntu:18.04alpine:3.7などでshell形式を使うときは注意。

Dockerfile

FROM debian:9
# FROM ubuntu:18.04
# FROM alpine:3.7

# Bashismなので途中はエラー: ((算術式評価))
RUN i=0; ((i++)); echo $i;
# i=0のまま

ビルド、確認

docker build --tag test --no-cache .
(中略)
Step 2/2 : RUN i=0; ((i++)); echo $i;
 ---> Running in 67ba320f0a54
/bin/sh: 1: i++: not found
0

なお、上記では最後のechoコマンドがエラーにならないので、ビルド自体は成功してしまう。


Bashismとシェルスクリプト

/bin/shがBashではないdebian:9ubuntu:18.04alpine:3.7などでシェルスクリプトを使うときは注意。

まず、以下のようなBashismなシェルスクリプトを作る。ただし、先頭行のシェバン#!/bin/shにしてみると、

bashism.sh

#!/bin/sh
i=0
((i++))
echo $i

Dockerfile

FROM debian:9
# FROM ubuntu:18.04
# FROM alpine:3.7 # Bashは入っていない

# シェルスクリプトをコピー
COPY bashism.sh /root/
RUN chmod 775 /root/bashism.sh
RUN /root/bashism.sh

ビルド、確認

docker build --tag test --no-cache .
(中略)
Step 4/4 : RUN /root/bashism.sh
 ---> Running in 9d39bf8f34a9
/root/bashism.sh: 3: /root/bashism.sh: i++: not found
0

上記でも、シェルスクリプトの途中でエラーになっているが、最後の終了ステータスは0なので、ビルド自体は成功してしまう。

シェバンを/bin/bashに変更すると正しく処理(i=1)される。


exec形式でもシェルを通せば通常の環境変数を使える

Dockerfile

FROM debian:9

# これはechoが直接実行でありそのまま$HOMEの5文字が出力されてしまう
RUN ["echo", "$HOME"]

# だからシェルで実行する
RUN ["/bin/bash", "-c", "echo $HOME"]

ビルド

docker build --tag test --no-cache .
(中略)
Step 2/3 : RUN ["echo", "$HOME"]
 ---> Running in 5b8effc763d8
$HOME
Removing intermediate container 5b8effc763d8
 ---> bdc5886c5fac
Step 3/3 : RUN ["/bin/bash", "-c", "echo $HOME"]
 ---> Running in 7949b490ac32
/root