DockerfileをLintする

DockerfileをLintする場合は[Hadolint](https://github.com/lukasmartinelli/hadolint)がオススメです。
`Haskell Dockerfile Linter`の略で、文字通りHaskellで実装されています。

# Macで使ってみる
```zsh
$ brew install hadolint
$ hadolint Dockerfile
```

これだけでDockerfileの静的解析が可能です。
特定のルールだけ除外してチェックしたい場合は下記のように実行します。

```zsh
$ hadolint --ignore DL3003 --ignore DL3006 Dockerfile
```

# ルール
## [DL3000](https://github.com/lukasmartinelli/hadolint/wiki/DL3000)
`WORKDIR`を使用する際は絶対パスを使う。

```zsh
# BAD
WORKDIR usr/src/app

# GOOD
WORKDIR /usr/src/app
```

## [DL3001](https://github.com/lukasmartinelli/hadolint/wiki/DL3001)
意味のない`bash`コマンド(`ssh`, `vim`, `shutdown`, `service`, `ps`, `free`, `top`, `kill`, `mount`, `ifconfig`)を使わない。

```zsh
# BAD
FROM busybox
RUN top

# GOOD
FROM busybox
```

## [DL3002](https://github.com/lukasmartinelli/hadolint/wiki/DL3002)
rootユーザを使わない。

```zsh
# BAD
FROM busybox
USER root

# GOOD
FROM busybox
```

## [DL3003](https://github.com/lukasmartinelli/hadolint/wiki/DL3003)
ディレクトリの変更は`WORKDIR`を使う。

```zsh
# BAD
FROM busybox
RUN cd /usr/src/app

# GOOD
FROM busybox
WORKDIR /usr/src/app
```


## [DL3004](https://github.com/lukasmartinelli/hadolint/wiki/DL3004)
`sudo`を使わない。

```zsh
# BAD
FROM busybox
RUN sudo apt-get install

# GOOD
FROM busybox
RUN apt-get install
```

## [DL3005](https://github.com/lukasmartinelli/hadolint/wiki/DL3005)
`apt-get`の`upgrade`, `dist-upgrade`を使わない。

```zsh
# BAD
FROM debian
RUN apt-get update && apt-get upgrade 

# GOOD
FROM debian
RUN apt-get update
```

## [DL3006](https://github.com/lukasmartinelli/hadolint/wiki/DL3006)
イメージのバージョンには明示的にタグを付ける。

```zsh
# BAD
FROM debian

# GOOD
FROM debian:jessie
```

## [DL3007](https://github.com/lukasmartinelli/hadolint/wiki/DL3007)
イメージタグに`latest`を使わず明示的にリリースタグを使う。

```zsh
# BAD
FROM debian:latest

# GOOD
FROM debian:jessie
```

## [DL3008](https://github.com/lukasmartinelli/hadolint/wiki/DL3008)
`apt get install`をする際はバージョンを固定する。

```zsh
# BAD
RUN apt-get install python

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

## [DL3009](https://github.com/lukasmartinelli/hadolint/wiki/DL3009)
`apt-get install`の後にlistを削除する。

```zsh
# 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](https://github.com/lukasmartinelli/hadolint/wiki/DL3011)
`0`から`65535`のポートを使う。

```zsh
# BAD
EXPOSE 80000

# GOOD
EXPOSE 65535
```

## [DL3012](https://github.com/lukasmartinelli/hadolint/wiki/DL3012)
メンテナの連絡先を記述する。

```zsh
# BAD
MAINTAINER ktsujichan

# GOOD
MAINTAINER ktsujichan <ktsujichan@example.com>
```

## [DL3013](https://github.com/lukasmartinelli/hadolint/wiki/DL3013)
`pip`を使う際はバージョンを固定する。

```zsh
# BAD
RUN pip install django

# GOOD
RUN pip install django==1.9
```

## [DL3014](https://github.com/lukasmartinelli/hadolint/wiki/DL3014)
`-y`を使う。

```zsh
# BAD
RUN apt-get install python=2.7

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

## [DL3015](https://github.com/lukasmartinelli/hadolint/wiki/DL3015)
`--no-install-recommends`を使う。

```zsh
# BAD
RUN apt-get install -y python=2.7

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

## [DL3020](https://github.com/lukasmartinelli/hadolint/wiki/DL3020)
ADDよりCOPYを使う。

```zsh
# BAD
ADD requirements.txt /usr/src/app/

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

## [DL4000](https://github.com/lukasmartinelli/hadolint/wiki/DL4000)
メンテナを記述する。

```zsh
# BAD
FROM busybox

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

## [DL4001](https://github.com/lukasmartinelli/hadolint/wiki/DL4001)
`wget`と`curl`を併用しない。

```zsh
# BAD
RUN wget http://google.com
RUN curl http://bing.com

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

## [DL4003](https://github.com/lukasmartinelli/hadolint/wiki/DL4003)
複数の`CMD`を使わない。

```zsh
# BAD
FROM busybox
CMD /bin/true
CMD /bin/false

# GOOD
FROM busybox
CMD /bin/true
```

## [DL4004](https://github.com/lukasmartinelli/hadolint/wiki/DL4004)
複数の`ENTRYPOINT`を使わない。

```zsh
# BAD
FROM busybox
ENTRYPOINT /bin/true
ENTRYPOINT /bin/false

# GOOD
FROM busybox
ENTRYPOINT /bin/true
```

## [DL4005](https://github.com/lukasmartinelli/hadolint/wiki/DL4005)
デフォルトのシェルを変更する場合は`SHELL`を使う。

```zsh
# 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](https://github.com/koalaman/shellcheck/wiki/SC1000)
`$`はエスケープして使う。

```zsh
# BAD
echo "$"

# GOOD
echo "\$"
```

## [SC1007](https://github.com/koalaman/shellcheck/wiki/SC1007)
`=`の後のスペースを削除する。

```zsh
# BAD
LANGUAGE= nl

# GOOD
LANGUAGE=nl
```


## [SC1010](https://github.com/koalaman/shellcheck/wiki/SC1010)
`done`の前にセミコロンまたは改行をする。

```zsh
# BAD
for f in *; do echo "$f" done

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

## [SC1035](https://github.com/koalaman/shellcheck/wiki/SC1035)
スペースを入れる。

```zsh
# BAD
if ![-z foo ]; then true; fi

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

## [SC1045](https://github.com/koalaman/shellcheck/wiki/SC1045)
`&`と`;`を併用しない。

```zsh
# BAD
foo &; bar

# GOOD
foo & bar
```

## [SC1065](https://github.com/koalaman/shellcheck/wiki/SC1065)
引数の処理は`$n`を使う。

```zsh
# BAD
foo(input) {
  echo "$input"
}
foo("hello world");

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

## [SC1066](https://github.com/koalaman/shellcheck/wiki/SC1066)
変数の代入に`$`を使わない。

```zsh
# BAD
$greeting="Hello World"

# GOOD
greeting="Hello World"
```

## [SC1068](https://github.com/koalaman/shellcheck/wiki/SC1068)
`=`の前後にスペースを入れない。

```zsh
# BAD
foo = 42

# GOOD
foo=42
```

## [SC1077](https://github.com/koalaman/shellcheck/wiki/SC1077)
コマンド展開に`´`を使わない。

```zsh
# BAD
echo "Your username is ´whoami´"

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



## [SC1078](https://github.com/koalaman/shellcheck/wiki/SC1078)
ダブルクオートの文字列を閉じ忘れしない。

```zsh
# BAD
greeting="hello
target="world"

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

## [SC1081](https://github.com/koalaman/shellcheck/wiki/SC1081)
`If`ではなく`if`を使う。

```zsh
# BAD
If true
Then
  echo "hello"
Fi

# GOOD
if true
then
  echo "hello"
fi
```

## [SC1086](https://github.com/koalaman/shellcheck/wiki/SC1086)
ループの反復変数に`$`を使わない。

```zsh
# BAD
for $var in *
do
  echo "$var"
done

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

## [SC1087](https://github.com/koalaman/shellcheck/wiki/SC1087)
配列を展開する際は中括弧を使う。

```zsh
# BAD
echo "$array[@]"

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

## [SC1095](https://github.com/koalaman/shellcheck/wiki/SC1095)
関数名と処理の間にスペースか改行を入れる。

```zsh
# BAD
function foo{
  echo "hello world"
}

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

function foo {
  echo "hello world"
}
```

## [SC1097](https://github.com/koalaman/shellcheck/wiki/SC1097)
`==`を使わず、代入の場合は`=`を、比較の場合は`[ .. ]`を使う。

```zsh
# BAD
var==value

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

## [SC1098](https://github.com/koalaman/shellcheck/wiki/SC1098)
`eval`を使用する際は、特殊文字を引用/エスケープする。

```zsh
# BAD
eval $var=(a b)

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

## [SC1099](https://github.com/koalaman/shellcheck/wiki/SC1099)
`#`の前にスペースを入れる。

```zsh
# BAD
while sleep 1
do# show time
  date
done

# GOOD
while sleep 1
do # show time
  date
done
```

## [SC2002](https://github.com/koalaman/shellcheck/wiki/SC2002)
`cat`を使わない。

```zsh
# 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](https://github.com/koalaman/shellcheck/wiki/SC2015)
`A && B || C`のロジックは、Aが`true`だとCが実行されるので`if-then-else`ではない。

```zsh
# BAD
[[ $dryrun ]] && echo "Would delete file" || rm file

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

## [SC2026](https://github.com/koalaman/shellcheck/wiki/SC2026)
引用符の入れ子に注意する。

```zsh
# BAD
alias server_uptime='ssh $host 'uptime -p''

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

## [SC2028](https://github.com/koalaman/shellcheck/wiki/SC2028)
`echo`はエスケープを展開しないので、`printf`を使う。

```zsh
# BAD
echo "Name:\t$value"

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

## [SC2035](https://github.com/koalaman/shellcheck/wiki/SC2035)
`./*glob*`か`-- *glob*`を使う。

```zsh
# BAD
rm *

# GOOD
rm ./*

# GOOD
rm -- *
```

## [SC2046](https://github.com/koalaman/shellcheck/wiki/SC2046)
単語を分割させないようにクオートを使う。

```zsh
# BAD
ls -l $(getfilename)

# GOOD
ls -l "$(getfilename)"

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

## [SC2086](https://github.com/koalaman/shellcheck/wiki/SC2086)
グロブと単語分割を防ぐためにダブルクオートを使う。

```zsh
# 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](https://github.com/koalaman/shellcheck/wiki/SC2140)
引用符の入れ子に注意する。

```zsh
# BAD
echo "<img src="foo.png" />" > file.html

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

## [SC2154](https://github.com/koalaman/shellcheck/wiki/SC2154)
変数展開に注意する。

```zsh
# BAD
var=name
n=42
echo "$var_$n.jpg"

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

## [SC2164](https://github.com/koalaman/shellcheck/wiki/SC2164)
`cd`で失敗したときのために終了する。

```zsh
# 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
}
```