Use Docker Machine to setup Docker environments

Docker Machine is a command-line tool to help you create Docker environments on your computers or cloud providers.

ref:
https://docs.docker.com/machine/

Installatino on Mac

$ curl -L https://github.com/docker/machine/releases/download/v0.3.0/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine
$ chmod +x /usr/local/bin/docker-machine

Usage

$ docker-machine ls

$ docker-machine create --driver virtualbox dev

# list arguments for creating a VirtualBox VM
$ docker-machine create -d virtualbox

# see how to connect Docker to this machine
$ docker-machine env dev

# run this command to configure your shell
$ eval "$(docker-machine env dev)"

# then
$ docker info

Issues

Error response from daemon: client and server don't have same version (client : 1.19, server: 1.18)

$ docker-machine upgrade dev

Deal with persistent storage with Docker volumes

-v /var/lib/mysql 和 Dockerfile 裡的 VOLUME ["/var/lib/mysql"]
僅僅是 mkdir -p 然後「標示」該目錄要作為 volume
讓你可以在其他 container 使用 --volumes-from 來 mount 這個目錄
除此之外沒有做其他事

-v /host/path/:/var/lib/mysql 是把 host 的目錄 mount 進 container
但是不建議這麼做
因為這樣就不 portable 了

無論是 -v 還是寫在 Dockerfile 裡的 VOLUME
volume 都是在 docker run 的時候才建立的
volume 是跟著 container 而不是 image

volume 會一直存在
直到你用 docker rm -v 刪掉最後一個關聯到它的 container 為止

--

要注意的是
如果你寫

VOLUME ["/etc/mysql", "/var/lib/mysql"]
RUN apt-get update && apt-get install -y \
    mysql-server-5.5

因為在安裝 mysql 之前
就先建立了 /etc/mysql/var/lib/mysql 這兩個空目錄
所以 mysql-server 就不會幫你塞那些 config 檔案和執行 mysql_install_db 了

--

官方的建議是各開一個 mysql container 和 mysql data container
好處是之後你要升級 mysql 時
只要用新版本的 mysql container 去 --volumes-from 那個 mysql data container 即可

ref:
http://www.offermann.us/2013/12/tiny-docker-pieces-loosely-joined.html
http://stackoverflow.com/questions/25401951/mysql-installed-and-persisting-data-in-docker-images

如果你只用一個 mysql container 並 VOLUME ["/var/lib/mysql"] 的話
之後再開一個新版本的 mysql container 去 --volumes-from
會因為有兩個 container 都 EXPOSE 3306
所以那個新版本的 container 開不起來

不過
平常開發的話
其實一個 mysql container 也就可以了
只要 mysql container 有 VOLUME ['/var/lib/mysql/'] 目錄
之後要存取 mysql data 時
只需要 --volumes-from mysql_container 即可

超譯 Dockerfile

Dockerfile

FROM vinta/ubuntu:14.04

MAINTAINER Vinta Chen <[email protected]>

RUN apt-get update && apt-get install -y \
    python-dev \
    python-pip

RUN rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

RUN pip install pip --upgrade
RUN pip install ipdb ipython uniout

EXPOSE 8000

CMD ["bash"]

FROM

可以是 local 的 image
只要你在 docker build 的時候有指定 -t 參數即可
該 image 可以不在 docker hub 上

ADD / COPY

ADD 和 COPY 都是把 host 的檔案 "複製" 到容器裡
ADD 或 COPY 的路徑是相對於 docker build 時所使用的路徑
例如 .

ADD 可以自動把 ADD 進去的 .tar 解壓縮
否則你應該用 COPY 就好

如果你是要同步目錄
請使用 VOLUME 或 docker run 的時候使用 -v 參數

VOLUME

如果你在 docker run 的時候使用 -v /data 或是在 Dockerfile 裡寫 VOLUME ["/data"]
作用是在該容器(假設是 CONTAINER_A)裡建立一個目錄 /data
然後你可以在 run 其他容器(假設是 CONTAINER_B)時使用 --volumes-from CONTAINER_A 把 CONTAINER_A 的 /data 目錄 mount 到 CONTAINER_B 的 /data 目錄
簡單說就是開一個專門放 volume 的容器
然後讓其他容器去 mount

要把 host 的目錄掛載到容器裡則是要使用 docker run --name redis -it -v /path/in/host/:/path/in/container/ redis bash
/path/in/host/ 必須是絕對路徑

如果 /path/to/container/ 目錄已經存在
則會被覆蓋

ref:
http://docs.docker.com/userguide/dockervolumes/
http://m.oschina.net/blog/271225
https://crosbymichael.com/advanced-docker-volumes.html

如果你是在 mac 裡用 docker
則沒辦法直接使用 volume
因為 volume 的 host path 是相對於 docker server
而 mac 的 docker server 是跑在 boot2docker VM 裡面的
所以 docker server 並不認得你的 mac 的路徑
除非 http://vinta.ws/code/docker-port-forwarding-and-share-volume-in-mac-os-x.html

EXPOSE

用 EXPOSE 把 container 的 port 暴露出來
但是你在 docker run 的時候還是得用 -p 手動指定 host port 和 container port 的對應
雖然你也可以寫成 EXPOSE 80:8080 省掉每次都手動指定
不過這麼做的缺點就是你沒辦法開多個 container 了
因為 host 的 port 會衝突

CMD / ENTRYPOINT

CMD 會被 docker run 時傳進去的東西所覆蓋
ENTRYPOINT 會把 docker run 傳進去的東西當成指令的參數
http://segmentfault.com/q/1010000000417103
https://www.dockboard.org/docker-15-tips/

範例:

ENTRYPOINT ["ls"]
CMD ["-l"]
# 會執行 ls -l
$ docker run IMAGE_NAME

# 會執行 ls -ha
$ docker run IMAGE_NAME -ha

# 會執行 ls /etc -l
$ docker run IMAGE_NAME /etc -l

# 會執行 ls bash
$ docker run IMAGE_NAME bash

ONBUILD

ONBUILD 只會在這個 image 被當作 base image 時才會執行
而且 ONBUILD 會在 sub image 的 FROM 之後就被執行
比 sub image 的所有指令還早
https://www.dockboard.org/docker-quicktip-3-onbuild/

例如你可以在一個作為 python app 的 base image 裡頭寫這樣:

RUN mkdir -p /app
WORKDIR /app
ONBUILD COPY requirements.txt /app
ONBUILD RUN pip install -r requirements.txt

ref:
http://docs.docker.io/reference/builder/
http://blog.flux7.com/blogs/docker/docker-tutorial-series-part-3-automation-is-the-word-using-dockerfile
http://blog.tankywoo.com/docker/2014/05/08/docker-2-dockerfile.html
http://blog.tankywoo.com/docker/2014/05/08/docker-4-summary.html

Automated Builds

就是根據 Dockerfile(通常會放在 GitHub 上)的更新去自動 build 一個 image
http://docs.docker.com/docker-hub/builds/
https://registry.hub.docker.com/builds/add/

Best Practices

http://docs.docker.com/articles/dockerfile_best-practices/ << 建議閱讀
https://crosbymichael.com/dockerfile-best-practices.html
https://crosbymichael.com/dockerfile-best-practices-take-2.html

應該把 data 和 config 放在 volume
而不是寫死在 image 裡

apt-get 的 download cache 應該刪掉
以減少 image 的 size

儘量把可能會變動的指令寫在 Dockerfile 越後面的地方
這樣之後有了修改
docker build 的時候就可以利用之前的 cache

使用 .dockerignore

可以考慮使用 Debian
一樣都是使用 apt-get
但是 base image 的 size 小很多(因為什麼都沒裝)
debian 上的軟體都太舊了(除非用 testing repository)
不過裝了 python 相關的 packages 之後
其實 size 跟 ubuntu 差不了多少