はじめに
最近、検証環境を構築するにあたり Dockerfile を書き始めるところから入るカワハラです。おはようございます。で、結局、当初の検証目的を忘れて Dockerfile 書きに没頭してしまって色々と滞っております...つきましては、Dockerfile を書くにあたってのメモを残しておきたいと思います。あくまでも「私的」なベストプラクティスですのでご注意下さい...
参考
以下のサイトを参考にさせて頂きました。
- Dockerfile Best Practices
- http://docs.docker.io/en/latest/examples/
- Build Images (Dockerfile Reference)
- DockerでJava Webアプリケーションの検証環境を構築する
- How to Use Entrypoint in Docker Builder
「私的」なベストプラクティス
MAINTAINER は入れましょう
どこの誰が作ったかをハッキリした方が良いと思うので MAINTAINER は指定しましょう。
MAINTAINER YOHEI KAWAHARA inokappa
上記のような感じ。
ENV
RUN したい時にパスが通っていないと悲しいのでパスが通ってなさそうだなーと思ったら、とりあえず以下のように RUN の前あたりで ENV でパスをしてしてあげると大概の場合は RUN が通りました。特にディレクトリごとバイナリをコピーした時等。
ENV PATH $PATH:/usr/local/jruby/bin RUN jgem install hoge
上記の例は PATH に /usr/local/jruby/bin を追加している。この後に jgem や jruby 等が使えるようになる。
EXPOSE
EXPOSE は直訳すると「晒す」。コンテナを起動した際に外部に公開するポートを指定することが出来る。docker run する時にオプションで指定する -p と同じ役割をする。例えば、Jenkins のデフォルトポート 8080 を晒す場合には以下のようにする。
EXPOSE 8080 CMD /usr/bin/java -jar /usr/share/jenkins/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080 --ajp13Port=-1
こんな感じにしておく。ちなみに、EXPOSE 8080:12345 としておくとコンテナをホストしている localhost の 12345 にバインドさせることも出来る。(はずなんだけど、何度試しても 12345 だけがバインドされてしまうので引き続き調べる)
CMD とか ENTRYPOINT とか
まだ、ちゃんと整理しきれていない部分もあるが、CMD とか ENTRYPOINT で docker run した時の挙動が異なるのでざっくりと書いてみる。
それぞれ、以下のような Dockerfile を作成して実行してみる。
以下はパターン 1。
# test1 FROM inokappa/wheezy-7.2-basic MAINTAINER YOHEI KAWAHARA inokappa ENTRYPOINT echo
以下はパターン 2。
# test2 FROM inokappa/wheezy-7.2-basic MAINTAINER YOHEI KAWAHARA inokappa CMD echo
どちらも echo を実行するだけ。とりあえず docker build -t inokappa/test1 . と docker build -t inokappa/test2 . で構築してから docker run してみる。
docker run inokappa/test1
以下のように表示される。
[なんにも表示されない]
同様に...
docker run inokappa/test2
以下のように表示される。
[なんにも表示されない]
これだけだとどちらも変わらない。「なんや、CMD と ENTRYPOINT は一緒やん...」と思ったが...
docker run inokappa/test1 a
以下のように表示される。
[なんにも表示されない]
では、docker run inokappa/test2 a とすると...
2013/12/28 03:06:58 Unable to locate a
というエラーが表示される。なるほど、なるほど、a がコマンドとして認識されてるっぽいぞ。ちなみに終了コードを比較してみる。

さらに docker inspect で確認してみる。(一部、抜粋)
// docker run inokappa/test1 a { "Config": { "Entrypoint": [ "/bin/sh", "-c", "echo" ], "Cmd": [ "a" ] }, "Args": [ "-c", "echo", "a" ], "Path": "/bin/sh" }
ほうほう、ENTRYPOINT で指定された場合に Path に /bin/sh -c が補完されており、docker run で指定したコマンドの a は Cmd に設定されている。
// docker run inokappa/test2 a { "Config": { "Entrypoint": null, "Cmd": [ "a" ] }, "Args": [], "Path": "a" }
CMD は Path には何も設定されず Cmd に docker run で引数につけた a が設定されているので a のみが実行される状態にエラーとなる。
ということで、CMD と ENTRYPOINT の違いは以下のような点が挙げられるかなと。
CMDでコマンドを指定した場合にはそのコマンドの引数をdocker run時に渡したら別のコマンドとして認識されるCMDはDockerfile内で完結させる必要がある(完結してしまう)ENTRYPOINTでコマンドを指定した場合にはそのコマンドの引数をdocker run時に渡すと引数として認識されるENTRYPOINTとCMDの合わせ技を用いることが出来る
なんかあった時の為にログイン出来るユーザーは作っておく
どうせコンテナなんだし上手く動かなければ捨てて Dockerfile を修正して作りなおせば良いし、docker attach も使えたりするのだが、コンテナにログイン出来る手順は複数あった方が良いというのが持論。ということで、以下のように sshd のインストールとログイン用のユーザーも作っておく。
RUN apt-get install openssh-server
RUN mkdir -p /var/run/sshd
RUN useradd -d /home/hogehoge -m -s /bin/bash hogehoge
RUN echo hogehoge:${your_pass} | chpasswd
RUN echo 'hogepass ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
docker run する時
docker run した後にすぐにコンテナの IP を確認したい時が多々あるので、以下のように docker ps -a してわざわざコンテナの ID を調べなくて便利だったりする。
con=`docker run -d ${container/image}`
docker inspect $con | jq '.[].NetworkSettings.IPAddress'
以下のような感じになる。(※.[].NetworkSettings.IPAddress でも .[]|.NetworkSettings.IPAddress でもイケてしまう)

最後に
Dockerfileを通してdockerの動作について少しだけ理解を深めることが出来た(気がする)CMDとENTRYPOINTの違いについてはまだまだモヤっとしているので使いながらフォローアップしていく