Dockerfile參考
Docker
可以從Dockerfile
中讀取指令來自動構建映象。Dockerfile
是一個文字檔案,它包含了使用者可以在命令呼叫以製作映象的命令。使用者可以使用docker build
連續執行一些命令列指令來開啟一個自動構建。
此文件描述了在Dockerfile
中可以使用的命令。當你讀完這個文件時,請參閱
Dockfile
最佳實踐
獲取進階指南。
使用
docker build
命令從Dockerfile
和上下文構建映象。構建上下文是特定路徑或URL的檔案集合。該路徑是你本地檔案系統的一個目錄。URL是一個Git倉庫地址。
上下文會被遞迴處理。所以,路徑包含的任意字母路合URL包含的倉庫及其子模組也會被處理。一下例項展示了一個使用當前目錄作為上下文的build
命令:
$ docker build . Sending build context to Docker daemon6.51 MB ...
構建由Docker daemon
執行, 而非cli。構建程序的第一件事是(遞迴的)傳送上下文給守護程序(daemon)。在大多數情況下,最好以一個空目錄下作為上下文傳送給守護程序並且保持Dockerfile
在該目錄下。只為構建Dockerfile
增加必須的檔案。
CMD
CMD
指令有三種用法:
-
CMD ["executable","param1","param2"]
(exec
形式, 這是首選形式) -
CMD ["param1","param2"]
(作為ENTRYPOINT
預設引數) -
CMD command param1 param2
(shell 形式)
一個Dockerfile
裡只能有一個CMD
指令。如果你有多個CMD
指令,只有最後一個
生效。
CMD
的主要目的是為執行容器提供預設值。預設值可以包含一個可執行檔案,也忽略可執行檔案,在此情況下必須同時指定ENTRYPOINT
指令。
CMD
用於為ENTRYPOINT
指令提供預設引數,CMD
和ENTRYPOINT
都應該使用json
陣列格式。
注:exec
形式傳遞json
陣列,意味著你必須使用雙引號(")而不是單引號(')引用字元
注:與shell
形式不同,exec
形式不會像,那樣呼叫命令列shell
。這意味著沒有通常的shell
處理。例如,CMD [ "echo", "$HOME" ]
將不會對$HOME
做變數替換。如果你想使用shell
處理可使用shell
形式或直接執行一個shell
,例如:["sh", "-c", "echo $HOME"]
。當使用exec
形式並且直接執行一個shell
,在這種情況下shell
形式,執行環境變數擴充套件的是shell
,而不是docker
。
當使用shell
或exec
格式時,CMD
指令設定映象執行時執行的命令。
如果你使用CMD
的shell
形式,<command>
將以/bin/sh -c
的形式執行:
FROM ubuntu CMD echo "This is a test." | wc -
如果你想不使用shell
執行你的<command>
就必須以JSON
陣列的形式表示並且使用可執行檔案的完整路徑。陣列形式是CMD
的首選格式。任何獨立的引數都必須表達為陣列的一個獨立的字串。
FROM ubuntu CMD ["/usr/bin/wc","--help"]
如果你係統容器每次執行相同的可執行檔案,你應該考慮ENTRYPOINT
和CMD
結合使用。
如果使用者為docker run
指定了引數,那麼他們將覆蓋CMD
中指定的預設引數。
注:不要混淆RUN
和CMD
。RUN
實際上執行命令並提交結果;CMD
在構建時什麼都不執行,只是指定映象將要執行的命令。
EXPOSE
EXPOSE <port> [<port>...]
EXPOSE
指令通知Docker
容器執行時監聽指定的網路埠。EXPOSE
不會使容器埠對宿主機可訪問。要那麼做,你必須使用-p
標記來發布一系列埠或者-P
標記釋出所有暴露埠。你可以暴露一個埠號並可以使用另一個埠對外發布。
要在宿主機系統上設定埠重定向,
使用-P
標記
。Docker
網路功能支援網路內建立網路而不需要暴露埠,詳細資訊請檢視功能概述
。
ADD
ADD
有兩種形式:
ADD <src>... <dest> ADD ["<src>",... "<dest>"]
ADD
指令
ENTRYPOINT
ENTRYPOINT
有2中形式:
-
ENTRYPOINT ["executable", "param1", "param2"]
(exec
形式, 首選) -
ENTRYPOINT command param1 param2
(shell
形式)
ENTRYPOINT
允許你配置一個將作為可執行程式執行的容器。
例如,以下命令將啟動一個nginx
預設監控80埠:
docker run -i -t --rm -p 80:80 nginx
docker run <image>
的命令列引數將被追加到以exec
形式的ENTRYPOINT
所有元素後面,並且覆蓋使用CMD
指定的所有元素。這使得引數可以被傳遞給入口, 例如,docker run <image> -d
將傳遞-d
引數給入口。你可以使用docker run --entrypoint
標記覆蓋ENTRYPOINT
執行。
shell
形式阻止任何CMD
或者run
的命令列引數被使用,但是有個弊端,你的ENTRYPOINT
將被作為/bin/sh -c
的一個子命令啟動,不能傳遞訊號。這意味著可執行程式不是容器ID為1的程序 - 並且不會接受Unix訊號 - 所以你的可執行程式不會接受來自docker stop <container>
的SIGTERM
。
只有Dockerfile
最後一個ENTRYPOINT
指令會生效。
VOLUME
VOLUME ["/data"]
VOLUME
指令建立一個具有指定名稱的掛載點並且將其標記作為從宿主機或者其他容器外部掛載卷。值可以是一個JSON
陣列,VOLUME ["/var/log"]
,或者有多引數的純字串,比如:VOLUME /var/log
或者VOLUME /var/log /var/db
。更多Docker
客戶端的掛載指令資訊/例子,移步文件通過卷共享目錄
。
docker run
命令使用基礎映象內指定位置存在的任意資料初始化新建立的卷。比如,認為以下Dockerfile
片段:
FROM ubuntu RUN mkdir /myvol RUN echo "hello world" > /myvol/greeting VOLUME /myvol
這個Dockerfile
的結果是致使docker run
會建立一個新的掛載點/myvol
並且拷貝gretting
檔案到新建立的卷。
關於Dockerfile
中的volumes
,請注意以下事項。
-
基於
Windows
容器的Volumes
:當使用基於Windows
的容器,容器內volume
的目標位置必須是以下之一:C
-
從
Dockerfile
內更改卷: 如果任何構建步驟在volume
宣告之後修改了資料,這些修改將會被丟棄。 -
JSON 格式:
列表將會被作為一個
JSON
陣列解析。你必須使用雙引號(")而不是單引號(')將單詞包起來。 -
主機目錄在容器執行時宣告:
主機目錄(掛載點)本質上是與主機相關的。這是為了保證映象的可移植性。因為一個指定的主機目錄不能保證在所有的主機上可用。因此,你不能在
Dockerfile
內掛載一個主機目錄。VOLUME
指令不支援指定一個主機目錄
引數。你必須在容器建立或執行時指定掛載點。
Exec形式ENTRYPOINT例項
你可以使用ENTRYPOINT
的exec
形式設定相當穩定的預設命令和引數,然後使用CMD
任意一種形式設定額外的更可能被修改的其他附加預設值。
FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"]
但你執行該容器時,你僅僅可以看到top
程序:
$ docker run -it --rm --name testtop -H top - 08:25:00 up7:27,0 users,load average: 0.00, 0.01, 0.05 Threads:1 total,1 running,0 sleeping,0 stopped,0 zombie %Cpu(s):0.1 us,0.1 sy,0.0 ni, 99.7 id,0.0 wa,0.0 hi,0.0 si,0.0 st KiB Mem:2056668 total,1616832 used,439836 free,99352 buffers KiB Swap:1441840 total,0 used,1441840 free.1324440 cached Mem PID USERPRNIVIRTRESSHR S %CPU %MEMTIME+ COMMAND 1 root2001974423362080 R0.00.10:00.04 top
要進一步檢查結果,可以使用docker exec
:
$ docker exec -it test ps aux USERPID %CPU %MEMVSZRSS TTYSTAT STARTTIME COMMAND root12.60.1197522352 ?Ss+08:240:00 top -b -H root70.00.1155722164 ?R+08:250:00 ps aux
並且你可以使用docker stop test
請求top
優雅的退出。
以下Dockerfile
展示了使用ENTRYPOINT
在前端執行Apache
(例如,PID為1)。
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"]
如果你需要為單個可執行程式寫一個啟動指令碼,你可以使用exec
和gosu
命令來確保最終執行程式可以收到Unix
訊號。
#!/usr/bin/env bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
最後,如果你需要在退出時做一些額外的清理(或者與其他容器通訊),或者配合執行多個可執行檔案,你可能需要確保ENTRYPOINT
指令碼接受Unix
訊號,傳遞他們並做更多工作:
#!/bin/sh # Note: I've written this using sh so it works in the busybox container too # USE the trap if you need to also do manual cleanup after the service is stopped, #or need to start multiple services in the one container trap "echo TRAPed signal" HUP INT QUIT TERM # start service in background here /usr/sbin/apachectl start echo "[hit enter key to exit] or run 'docker stop <container>'" read # stop service and clean up here echo "stopping apache" /usr/sbin/apachectl stop echo "exited $0"
如果你使用docker run -it -p 80:80 --name test apache
執行該映象,然後你可以使用docker exec
檢查容器程序,或者docker top
,並且可以通過指令碼停止Apache
。
$ docker exec -it test ps aux USERPID %CPU %MEMVSZRSS TTYSTAT STARTTIME COMMAND root10.10.04448692 ?Ss+00:420:00 /bin/sh /run.sh 123 cmd cmd2 root190.00.2713044440 ?Ss00:420:00 /usr/sbin/apache2 -k start www-data200.20.2 3604686004 ?Sl00:420:00 /usr/sbin/apache2 -k start www-data210.20.2 3604686000 ?Sl00:420:00 /usr/sbin/apache2 -k start root810.00.1155722140 ?R+00:440:00 ps aux $ docker top test PIDUSERCOMMAND 10035root{run.sh} /bin/sh /run.sh 123 cmd cmd2 10054root/usr/sbin/apache2 -k start 1005533/usr/sbin/apache2 -k start 1005633/usr/sbin/apache2 -k start $ /usr/bin/time docker stop test test real0m 0.27s user0m 0.03s sys0m 0.03s
--entrypoint
覆蓋ENTRYPOINT
配置,但是這隻會將二進位制設定為exec
(sh -c
不會被使用)。
注:exec
形式被解析為JSON
陣列,意味著你必須使用雙引號(")包裹單詞而不是單引號(')。
注:不像shell
形式,exec
形式並不會呼叫shell
命令。這意味著不會做普通的shell
處理。例如,ENTRIPOIN ["echo", "$HOME"]
將不能對$HOME
做變數置換。如果你既想shell
處理又想使用shell
形式或直接執行一shell
,例如:ENTRYPOINT ["sh", "-c", "echo $HOME"]
。當使用exec
形式和直接執行shell
時,在shell
形式這種情況下,是shell
做的環境變數擴充套件,而不是docker
。
Shell形式ENTRYPOINT例項
你可以為ENTRYPOINT
指定一個純文字的字串,它會以/bin/sh -c
的形式執行。這種形式將使用shell
處理shell
代替shell
環境變數,並且將忽略任何CMD
或者docker run
命令的命令列引數。為了確保docker stop
能夠正常發出訊號給任何長時間執行的ENTRYPOINT
可執行檔案,您需要記住使用exec
啟動它:
FROM ubuntu ENTRYPOINT exec top -b
當你啟動映象,你會看到PID
為1的程序:
$ docker run -it --rm --name test top Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached CPU:5% usr0% sys0% nic94% idle0% io0% irq0% sirq Load average: 0.08 0.03 0.05 2/98 6 PIDPPID USERSTATVSZ %VSZ %CPU COMMAND 10 rootR31640%0% top -b
它將會在執行docker stop
時徹底退出:
$ /usr/bin/time docker stop test test real0m 0.20s user0m 0.02s sys0m 0.04s
如果你忘記了在ENTRYPOINT
開頭增加exec
:
FROM ubuntu ENTRYPOINT top -b CMD --ignored-param1
你可以啟動它(為了下一步給它指定名稱):
$ docker run -it --name test top --ignored-param2 Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached CPU:9% usr2% sys0% nic88% idle0% io0% irq0% sirq Load average: 0.01 0.02 0.05 2/101 7 PIDPPID USERSTATVSZ %VSZ %CPU COMMAND 10 rootS31680%0% /bin/sh -c top -b cmd cmd2 71 rootR31640%0% top -b
你可以看到top
的輸出,ENTRYPOINT
指定的不是PID 1。
如果你接下來執行docker stop test
,容器不會被徹底退出 - 超時以後top
命令會被髮送一個SIGKILL
。
$ docker exec -it test ps aux PIDUSERCOMMAND 1 root/bin/sh -c top -b cmd cmd2 7 roottop -b 8 rootps aux $ /usr/bin/time docker stop test test real0m 10.19s user0m 0.04s sys0m 0.03s
理解CMD和ENTRYPOINT如何互動
CMD
和ENTRYPOINT
指令都定義了當啟動一個容器時執行什麼命令。描述他們如何一起工作的規則很少。
-
Dockerfile
至少應該指定一個CMD
或ENTRYPOINT
命令。 -
當容器做一個可執行程式時,
ENTRYPOINT
應該被定義。 -
CMD
應該被用作一種給ENTRYPOINT
定義預設引數的方式,或在容器中執行ad-hoc
命令的方式。 -
當執行容器時是用了互動引數時,
CMD
將被會被覆蓋。
下表顯示了對不同ENTRYPOINT
/CMD
組合執行的命令:
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |