Posted at
LintDay 3

DockerfileをLintする

More than 1 year has passed since last update.

DockerfileをLintする場合はHadolintがオススメです。

Haskell Dockerfile Linterの略で、文字通りHaskellで実装されています。


Macで使ってみる

$ brew install hadolint

$ hadolint Dockerfile

これだけでDockerfileの静的解析が可能です。

特定のルールだけ除外してチェックしたい場合は下記のように実行します。

$ hadolint --ignore DL3003 --ignore DL3006 Dockerfile


ルール


DL3000

WORKDIRを使用する際は絶対パスを使う。

# BAD

WORKDIR usr/src/app

# GOOD
WORKDIR /usr/src/app


DL3001

意味のないbashコマンド(ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig)を使わない。

# BAD

FROM busybox
RUN top

# GOOD
FROM busybox


DL3002

rootユーザを使わない。

# BAD

FROM busybox
USER root

# GOOD
FROM busybox


DL3003

ディレクトリの変更はWORKDIRを使う。

# BAD

FROM busybox
RUN cd /usr/src/app

# GOOD
FROM busybox
WORKDIR /usr/src/app


DL3004

sudoを使わない。

# BAD

FROM busybox
RUN sudo apt-get install

# GOOD
FROM busybox
RUN apt-get install


DL3005

apt-getupgrade, dist-upgradeを使わない。

# BAD

FROM debian
RUN apt-get update && apt-get upgrade

# GOOD
FROM debian
RUN apt-get update


DL3006

イメージのバージョンには明示的にタグを付ける。

# BAD

FROM debian

# GOOD
FROM debian:jessie


DL3007

イメージタグにlatestを使わず明示的にリリースタグを使う。

# BAD

FROM debian:latest

# GOOD
FROM debian:jessie


DL3008

apt get installをする際はバージョンを固定する。

# BAD

RUN apt-get install python

# GOOD
RUN apt-get install python=2.7


DL3009

apt-get installの後にlistを削除する。

# BAD

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

# GOOD
RUN apt-get update && apt-get install -y python \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*


DL3011

0から65535のポートを使う。

# BAD

EXPOSE 80000

# GOOD
EXPOSE 65535


DL3012

メンテナの連絡先を記述する。

# BAD

MAINTAINER ktsujichan

# GOOD
MAINTAINER ktsujichan <ktsujichan@example.com>


DL3013

pipを使う際はバージョンを固定する。

# BAD

RUN pip install django

# GOOD
RUN pip install django==1.9


DL3014

-yを使う。

# BAD

RUN apt-get install python=2.7

# GOOD
RUN apt-get install -y python=2.7


DL3015

--no-install-recommendsを使う。

# BAD

RUN apt-get install -y python=2.7

# GOOD
RUN apt-get install -y --no-install-recommends python=2.7


DL3020

ADDよりCOPYを使う。

# BAD

ADD requirements.txt /usr/src/app/

# GOOD
COPY requirements.txt /usr/src/app/


DL4000

メンテナを記述する。

# BAD

FROM busybox

# GOOD
FROM busybox
MAINTAINER ktsujichan <ktsujichan@example.com>


DL4001

wgetcurlを併用しない。

# BAD

RUN wget http://google.com
RUN curl http://bing.com

# GOOD
RUN curl http://google.com
RUN curl http://bing.com


DL4003

複数のCMDを使わない。

# BAD

FROM busybox
CMD /bin/true
CMD /bin/false

# GOOD
FROM busybox
CMD /bin/true


DL4004

複数のENTRYPOINTを使わない。

# BAD

FROM busybox
ENTRYPOINT /bin/true
ENTRYPOINT /bin/false

# GOOD
FROM busybox
ENTRYPOINT /bin/true


DL4005

デフォルトのシェルを変更する場合はSHELLを使う。

# BAD

RUN apk add --update-cache bash=4.3.42-r3
RUN ln -sfv /bin/bash /bin/sh

# GOOD
RUN apk add --update-cache bash=4.3.42-r3
SHELL ["/bin/bash", "-c"]


SC1000

$はエスケープして使う。

# BAD

echo "$"

# GOOD
echo "\$"


SC1007

=の後のスペースを削除する。

# BAD

LANGUAGE= nl

# GOOD
LANGUAGE=nl


SC1010

doneの前にセミコロンまたは改行をする。

# BAD

for f in *; do echo "$f" done

# GOOD
for f in *; do echo "$f"; done


SC1035

スペースを入れる。

# BAD

if ![-z foo ]; then true; fi

# GOOD
if ! [ -z foo ]; then true; fi


SC1045

&;を併用しない。

# BAD

foo &; bar

# GOOD
foo & bar


SC1065

引数の処理は$nを使う。

# BAD

foo(input) {
echo "$input"
}
foo("hello world");

# GOOD
foo() {
echo "$1"
}
foo "hello world"


SC1066

変数の代入に$を使わない。

# BAD

$greeting="Hello World"

# GOOD
greeting="Hello World"


SC1068

=の前後にスペースを入れない。

# BAD

foo = 42

# GOOD
foo=42


SC1077

コマンド展開に´を使わない。

# BAD

echo "Your username is ´whoami´"

# GOOD
echo "Your username is $(whoami)"


SC1078

ダブルクオートの文字列を閉じ忘れしない。

# BAD

greeting="hello
target="
world"

# GOOD
greeting="hello"
target="
world"


SC1081

Ifではなくifを使う。

# BAD

If true
Then
echo "hello"
Fi

# GOOD
if true
then
echo "hello"
fi


SC1086

ループの反復変数に$を使わない。

# BAD

for $var in *
do
echo "$var"
done

# GOOD
for var in *
do
echo "$var"
done


SC1087

配列を展開する際は中括弧を使う。

# BAD

echo "$array[@]"

# GOOD
echo "${array[@]}"


SC1095

関数名と処理の間にスペースか改行を入れる。

# BAD

function foo{
echo "hello world"
}

# GOOD
foo() {
echo "hello world"
}

function foo {
echo "hello world"
}


SC1097

==を使わず、代入の場合は=を、比較の場合は[ .. ]を使う。

# BAD

var==value

# GOOD
var=value
[ "$var" = value ]


SC1098

evalを使用する際は、特殊文字を引用/エスケープする。

# BAD

eval $var=(a b)

# GOOD
eval "$var=(a b)"


SC1099

#の前にスペースを入れる。

# BAD

while sleep 1
do# show time
date
done

# GOOD
while sleep 1
do # show time
date
done


SC2002

catを使わない。

# BAD

cat file | tr ' ' _ | grep a_
cat file | ( while read i; do echo "${i%?}"; done )

# GOOD
< file tr ' ' _ | grep a_ # **simple commands only, won't work with compounds
( while read i; do echo "${i%?}"; done ) < file # postfix works for everything


SC2015

A && B || Cのロジックは、AがtrueだとCが実行されるのでif-then-elseではない。

# BAD

[[ $dryrun ]] && echo "Would delete file" || rm file

# GOOD
if [[ $dryrun ]]
then
echo "Would delete file"
else
rm file
fi


SC2026

引用符の入れ子に注意する。

# BAD

alias server_uptime='ssh $host 'uptime -p''

# GOOD
alias server_uptime='ssh $host '"'uptime -p'"


SC2028

echoはエスケープを展開しないので、printfを使う。

# BAD

echo "Name:\t$value"

# GOOD
printf "Name:\t%s\n" "$value"


SC2035

./*glob*-- *glob*を使う。

# BAD

rm *

# GOOD
rm ./*

# GOOD
rm -- *


SC2046

単語を分割させないようにクオートを使う。

# BAD

ls -l $(getfilename)

# GOOD
ls -l "$(getfilename)"

getfilename | while IFS='' read -r line
do
ls -l "$line"
done


SC2086

グロブと単語分割を防ぐためにダブルクオートを使う。

# BAD

echo $1
for i in $*; do :; done # this one and the next one also applies to expanding arrays.
for i in $@; do :; done

# GOOD
echo "$1"
for i in "$@"; do :; done # or, 'for i; do'


SC2140

引用符の入れ子に注意する。

# BAD

echo "<img src="foo.png" />" > file.html

# GOOD
echo "<img src=\"foo.png\" />" > file.html


SC2154

変数展開に注意する。

# BAD

var=name
n=42
echo "$var_$n.jpg"

# GOOD
var=name
n=42
echo "${var}_$n.jpg"


SC2164

cdで失敗したときのために終了する。

# BAD

cd generated_files
rm -r *.c

func(){
cd foo
do_something
}

# GOOD
cd generated_files || exit
rm
-r *.c

func(){
cd foo || return
do_something
}