七、Dockerfile
Docker 可以通過 Dockerfile 的內容來自動構建鏡像。Dockerfile 是一個包含創建鏡像所有命令的文本文件,通過docker build命令可以根據 Dockerfile 的內容構建鏡像,在介紹如何構建之前先介紹下 Dockerfile 的基本語法結構。
Dockerfile 有以下指令選項:
- FROM
- MAINTAINER
- RUN
- CMD
- EXPOSE
- ENV
- ADD
- COPY
- ENTRYPOINT
- VOLUME
- USER
- WORKDIR
- ONBUILD
7.1 FROM
用法:
或者
- FROM指定構建鏡像的基礎源鏡像,如果本地沒有指定的鏡像,則會自動從 Docker 的公共庫 pull 鏡像下來。
- FROM必須是 Dockerfile 中非注釋行的第一個指令,即一個 Dockerfile 從FROM語句開始。
- FROM可以在一個 Dockerfile 中出現多次,如果有需求在一個 Dockerfile 中創建多個鏡像。
- 如果FROM語句沒有指定鏡像標簽,則默認使用latest標簽。
7.2 MAINTAINER
用法:
指定創建鏡像的用戶
RUN 有兩種使用方式
每條RUN指令將在當前鏡像基礎上執行指定命令,并提交為新的鏡像,后續的RUN都在之前RUN提交后的鏡像為基礎,鏡像是分層的,可以通過一個鏡像的任何一個歷史提交點來創建,類似源碼的版本控制。
exec 方式會被解析為一個 JSON 數組,所以必須使用雙引號而不是單引號。exec 方式不會調用一個命令 shell,所以也就不會繼承相應的變量,如:
這種方式是不會達到輸出 HOME 變量的,正確的方式應該是這樣的
RUN [ "sh", "-c", "echo", "$HOME" ]
RUN產生的緩存在下一次構建的時候是不會失效的,會被重用,可以使用--no-cache選項,即docker build --no-cache,如此便不會緩存。
7.3 CMD
CMD有三種使用方式:
- CMD
- CMD
- CMD command param1 param2 (shell form)
CMD指定在 Dockerfile 中只能使用一次,如果有多個,則只有最后一個會生效。
CMD的目的是為了在啟動容器時提供一個默認的命令執行選項。如果用戶啟動容器時指定了運行的命令,則會覆蓋掉CMD指定的命令。
CMD會在啟動容器的時候執行,build 時不執行,而RUN只是在構建鏡像的時候執行,后續鏡像構建完成之后,啟動容器就與RUN無關了,這個初學者容易弄混這個概念,這里簡單注解一下。
7.4 EXPOSE
EXPOSE <port> [<port>...]
告訴 Docker 服務端容器對外映射的本地端口,需要在 docker run 的時候使用-p或者-P選項生效。
7.5 ENV
ENV <key> <value> # 只能設置一個變量
ENV <key>=<value> ... # 允許一次設置多個變量
指定一個環節變量,會被后續RUN指令使用,并在容器運行時保留。
例子:
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
等同于
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
7.6 ADD
ADD復制本地主機文件、目錄或者遠程文件 URLS 從 并且添加到容器指定路徑中 。
支持通過 GO 的正則模糊匹配,具體規則可參見 Go filepath.Match
ADD hom* /mydir/ # adds all files starting with "hom"
ADD hom?.txt /mydir/ # ? is replaced with any single character
- 路徑必須是絕對路徑,如果 不存在,會自動創建對應目錄
- 路徑必須是 Dockerfile 所在路徑的相對路徑
- 如果是一個目錄,只會復制目錄下的內容,而目錄本身則不會被復制
7.7 COPY
COPY復制新文件或者目錄從 并且添加到容器指定路徑中 。用法同ADD,唯一的不同是不能指定遠程文件 URLS。
7.8 ENTRYPOINT
- ENTRYPOINT
- ENTRYPOINT command param1 param2 (shell form)
配置容器啟動后執行的命令,并且不可被 docker run 提供的參數覆蓋,而CMD是可以被覆蓋的。如果需要覆蓋,則可以使用docker run --entrypoint選項。
每個 Dockerfile 中只能有一個ENTRYPOINT,當指定多個時,只有最后一個生效。
Exec form ENTRYPOINT 例子
通過ENTRYPOINT使用 exec form 方式設置穩定的默認命令和選項,而使用CMD添加默認之外經常被改動的選項。
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
通過 Dockerfile 使用ENTRYPOINT展示前臺運行 Apache 服務
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
Shell form ENTRYPOINT 例子
這種方式會在/bin/sh -c中執行,會忽略任何CMD或者docker run命令行選項,為了確保docker stop能夠停止長時間運行ENTRYPOINT的容器,確保執行的時候使用exec選項。
FROM ubuntu
ENTRYPOINT exec top -b
如果在ENTRYPOINT忘記使用exec選項,則可以使用CMD補上:
FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此類推
7.9 VOLUME
創建一個可以從本地主機或其他容器掛載的掛載點,后續具體介紹。
7.10 USER
指定運行容器時的用戶名或 UID,后續的RUN、CMD、ENTRYPOINT也會使用指定用戶。
7.11 WORKDIR
為后續的RUN、CMD、ENTRYPOINT指令配置工作目錄。可以使用多個WORKDIR指令,后續命令如果參數是相對路徑,則會基于之前命令指定的路徑。
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最終路徑是/a/b/c。
WORKDIR指令可以在ENV設置變量之后調用環境變量:
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
最終路徑則為 /path/$DIRNAME。
7.12 ONBUILD
配置當所創建的鏡像作為其它新創建鏡像的基礎鏡像時,所執行的操作指令。
例如,Dockerfile 使用如下的內容創建了鏡像 image-A:
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于 image-A 創建新的鏡像時,新的 Dockerfile 中使用 FROM image-A 指定基礎鏡像時,會自動執行 ONBUILD 指令內容,等價于在后面添加了兩條指令。
# Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的鏡像,推薦在標簽中注明,例如 ruby:1.9-onbuild。
7.13 Dockerfile Examples
# Nginx
#
# VERSION 0.0.1
FROM ubuntu
MAINTAINER Victor Vieux <victor@docker.com>
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
# Firefox over VNC
#
# VERSION 0.3
FROM ubuntu
# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'
EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]
# Multiple images example
#
# VERSION 0.1
FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f
FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4
# You?ll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.
7.14 docker build
$ docker build --help
Usage: docker build [OPTIONS] PATH | URL | -
Build a new image from the source code at PATH
--force-rm=false Always remove intermediate containers, even after unsuccessful builds # 移除過渡容器,即使構建失敗
--no-cache=false Do not use cache when building the image # 不實用 cache
-q, --quiet=false Suppress the verbose output generated by the containers
--rm=true Remove intermediate containers after a successful build # 構建成功后移除過渡層容器
-t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success
參考文檔:Dockerfile Reference
7.15 dockerfile 最佳實踐
為了在docker build過程中更快上傳和更加高效,應該使用一個.dockerignore文件用來排除構建鏡像時不需要的文件或目錄。例如,除非.git在構建過程中需要用到,否則你應該將它添加到.dockerignore文件中,這樣可以節省很多時間。
為了降低復雜性、依賴性、文件大小以及構建時間,應該避免安裝額外的或不必要的包。例如,不需要在一個數據庫鏡像中安裝一個文本編輯器。
在大多數情況下,一個容器應該只單獨跑一個程序。解耦應用到多個容器使其更容易橫向擴展和重用。如果一個服務依賴另外一個服務,可以參考 Linking Containers Together。
我們知道每執行一個指令,都會有一次鏡像的提交,鏡像是分層的結構,對于Dockerfile,應該找到可讀性和最小化層之間的平衡。
如果可能,通過字母順序來排序,這樣可以避免安裝包的重復并且更容易更新列表,另外可讀性也會更強,添加一個空行使用\換行:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
鏡像構建過程中會按照Dockerfile的順序依次執行,每執行一次指令 Docker 會尋找是否有存在的鏡像緩存可復用,如果沒有則創建新的鏡像。如果不想使用緩存,則可以在docker build時添加--no-cache=true選項。
從基礎鏡像開始就已經在緩存中了,下一個指令會對比所有的子鏡像尋找是否執行相同的指令,如果沒有則緩存失效。在大多數情況下只對比Dockerfile指令和子鏡像就足夠了。ADD和COPY指令除外,執行ADD和COPY時存放到鏡像的文件也是需要檢查的,完成一個文件的校驗之后再利用這個校驗在緩存中查找,如果檢測的文件改變則緩存失效。RUN apt-get -y update命令只檢查命令是否匹配,如果匹配就不會再執行更新了。
為了有效地利用緩存,你需要保持你的 Dockerfile 一致,并且盡量在末尾修改。
Dockerfile 指令
- FROM: 只要可能就使用官方鏡像庫作為基礎鏡像
- RUN: 為保持可讀性、方便理解、可維護性,把長或者復雜的RUN語句使用\分隔符分成多行
- 不建議RUN apt-get update獨立成行,否則如果后續包有更新,那么也不會再執行更新
- 避免使用RUN apt-get upgrade或者dist-upgrade,很多必要的包在一個非privileged權限的容器里是無法升級的。如果知道某個包更新,使用apt-get install -y xxx
- 標準寫法
- RUN apt-get update && apt-get install -y package-bar package-foo
例子:
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
btrfs-tools \
build-essential \
curl \
dpkg-sig \
git \
iptables \
libapparmor-dev \
libcap-dev \
libsqlite3-dev \
lxc=1.0* \
mercurial \
parallel \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.0*
- CMD: 推薦使用CMD [“executable”, “param1”, “param2”…]這種格式,CMD [“param”, “param”]則配合ENTRYPOINT使用
- EXPOSE: Dockerfile 指定要公開的端口,使用docker run時指定映射到宿主機的端口即可
- ENV: 為了使新的軟件更容易運行,可以使用ENV更新PATH變量。如ENV PATH /usr/local/nginx/bin:$PATH確保CMD ["nginx"]即可運行
ENV也可以這樣定義變量:
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
- ADDorCOPY:ADD比COPY多一些特性「tar 文件自動解包和支持遠程 URL」,不推薦添加遠程 URL
如不推薦這種方式:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
推薦使用 curl 或者 wget 替換,使用如下方式:
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.gz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
如果不需要添加 tar 文件,推薦使用COPY。
參考文檔: