Dockerfile指令
這些建議旨在幫助您創建高效且可維護的Dockerfile。
FROM
盡可能使用當前的官方圖像作為圖像的基礎。我們推薦Alpine圖像,因為它是嚴格控制的並且尺寸小(目前小於5 MB),同時仍然是完整的Linux發行版。
標簽
您可以為圖像添加標簽,以幫助按項目組織圖像,記錄許可信息,幫助實現自動化或出於其他原因。對於每個標簽,添加LABEL以一個或多個鍵值對開頭的行。以下示例顯示了不同的可接受格式。內容包括解釋性意見。
必須引用帶空格的字符串或必須轉義空格。內引號字符(")也必須進行轉義。
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
圖像可以有多個標簽。在Docker 1.10之前,建議將所有標簽組合到一條LABEL指令中,以防止創建額外的層。這不再是必需的,但仍然支持組合標簽。
# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"
以上也可以寫成:
# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"
有關可接受的標簽鍵和值的指導,請參閱了解對象標簽。有關查詢標簽的信息,請參閱管理對象標簽中與過濾相關的項目。另請參見 Dockerfile參考中的LABEL。
RUN
RUN在使用反斜杠分隔的多行上拆分長或復雜語句,以使您Dockerfile更具可讀性,可理解性和可維護性。
APT-GET的
可能最常見的用例RUN是應用程序apt-get。因為它安裝了包,所以該RUN apt-get命令有幾個需要注意的問題。
避免RUN apt-get upgrade和dist-upgrade,因為父圖像中的許多“基本”包無法在非特權容器內升級 。如果父圖像中包含的包已過期,請與其維護人員聯系。如果您知道有特定包,foo需要更新,請使用 apt-get install -y foo自動更新。
始終在同一 聲明中結合RUN apt-get update使用。例如:apt-get installRUN
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo
apt-get update在RUN語句中單獨使用會導致緩存問題,並且后續apt-get install指令會失敗。例如,假設你有一個Dockerfile:
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl
構建映像后,所有層都在Docker緩存中。假設您稍后apt-get install通過添加額外包修改:
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx
Docker將初始和修改的指令視為相同,並重用前面步驟中的緩存。其結果是,apt-get update在不執行,因為編譯使用緩存的版本。因為apt-get update沒有運行,您的構建有可能得到的一個過時的版本curl和 nginx包。
使用RUN apt-get update && apt-get install -y確保您的Dockerfile安裝最新的軟件包版本,無需進一步編碼或手動干預。這種技術被稱為“緩存破壞”。您還可以通過指定包版本來實現緩存清除。這稱為版本固定,例如:
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo=1.3.*
版本固定強制構建以檢索特定版本,而不管緩存中的內容是什么。此技術還可以減少由於所需包中意外更改而導致的故障。
下面是一個結構良好的RUN說明,演示了所有apt-get 建議。
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
該s3cmd參數指定一個版本1.1.*。如果映像以前使用的是舊版本,則指定新版本會導致緩存破壞,apt-get update並確保安裝新版本。列出每行的包也可以防止包重復中的錯誤。
此外,當您通過刪除/var/lib/apt/lists它來清理apt緩存時會減小圖像大小,因為apt緩存不存儲在圖層中。由於RUN語句以#開頭apt-get update,因此包緩存始終在刷新之前刷新apt-get install。
官方Debian和Ubuntu映像自動運行apt-get clean,因此不需要顯式調用。
使用管道
某些RUN命令依賴於使用管道符(|)將一個命令的輸出傳遞到另一個命令的能力,如下例所示:
RUN wget -O - https://some.site | wc -l > /number
Docker使用/bin/sh -c解釋器執行這些命令,解釋器僅評估管道中最后一個操作的退出代碼以確定成功。在上面的示例中,只要wc -l命令成功,即使wget命令失敗,此構建步驟也會成功並生成新映像。
如果您希望命令因管道中任何階段的錯誤而失敗,請預先set -o pipefail &&確定意外錯誤,以防止構建無意中成功。例如:
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
並非所有shell都支持該-o pipefail選項。
在諸如dash基於Debian的映像上的shell的情況下,考慮使用exec形式RUN明確選擇支持該pipefail選項的shell 。例如:
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]
CMD
該CMD指令應用於運行圖像包含的軟件以及任何參數。CMD應該幾乎總是以形式使用CMD ["executable", "param1", "param2"…]。因此,如果圖像是用於服務的,例如Apache和Rails,那么你可以運行類似的東西CMD ["apache2","-DFOREGROUND"]。實際上,建議將這種形式的指令用於任何基於服務的圖像。
在大多數其他情況下,CMD應該給出一個交互式shell,例如bash,python和perl。例如,CMD ["perl", "-de0"],CMD ["python"],或CMD ["php", "-a"]。使用此表單意味着當您執行類似的操作時 docker run -it python,您將被放入可用的shell中,隨時可以使用。 CMD應該很少的方式使用CMD ["param", "param"]會同ENTRYPOINT,除非你和你預期的用戶已經非常熟悉如何ENTRYPOINT 工作的。
EXPOSE
該EXPOSE指令指示容器偵聽連接的端口。因此,您應該為您的應用程序使用通用的傳統端口。例如,包含Apache Web服務器EXPOSE 80的圖像將使用,而包含MongoDB的圖像將使用EXPOSE 27017,依此類推。
對於外部訪問,您的用戶可以docker run使用一個標志來執行,該標志指示如何將指定端口映射到他們選擇的端口。對於容器鏈接,Docker為從接收容器返回源的路徑提供環境變量(即MYSQL_PORT_3306_TCP)。
ENV
要使新軟件更易於運行,您可以使用ENV更新PATH容器安裝的軟件的 環境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH確保CMD ["nginx"] 正常工作。
該ENV指令對於提供特定於您希望容納的服務的必需環境變量也很有用,例如Postgres PGDATA。
最后,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
與在程序中使用常量變量(與硬編碼值相對)類似,此方法允許您更改單個ENV指令以自動神奇地破壞容器中的軟件版本。
每ENV行創建一個新的中間層,就像RUN命令一樣。這意味着即使您在將來的圖層中取消設置環境變量,它仍然會在此圖層中保留,並且可以轉儲其值。您可以通過創建如下所示的Dockerfile來測試它,然后構建它。
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'
mark
要防止這種情況,並且確實取消設置環境變量,請使用RUN帶有shell命令的命令,在單個圖層中設置,使用和取消設置變量all。您可以使用;或分隔命令&&。如果您使用第二種方法,並且其中一個命令失敗,則docker build也會失敗。這通常是一個好主意。使用\Linux Dockerfiles作為行繼續符可以提高可讀性。您還可以將所有命令放入shell腳本中,並讓RUN命令運行該shell腳本。
FROM alpine
RUN export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh
$ docker run --rm test sh -c 'echo $ADMIN_USER'
ADD COPY
一般而言,雖然ADD並且COPY在功能上類似,但是COPY 是優選的。那是因為它更透明ADD。COPY僅支持將本地文件基本復制到容器中,同時ADD具有一些功能(如僅限本地的tar提取和遠程URL支持),這些功能並不是很明顯。因此,最好的用途ADD是將本地tar文件自動提取到圖像中,如ADD rootfs.tar.xz /。
如果您有多個Dockerfile步驟使用上下文中的不同文件,則COPY它們是單獨的,而不是一次性完成。這可確保每個步驟的構建緩存僅在特定所需文件更改時失效(強制重新執行該步驟)。
例如:
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
導致RUN步驟的緩存失效次數少於放置 COPY . /tmp/之前的緩存失效次數。
由於圖像大小很重要,ADD因此強烈建議不要使用從遠程URL獲取包。你應該使用curl或wget代替。這樣,您可以刪除提取后不再需要的文件,也不必在圖像中添加其他圖層。例如,你應該避免做以下事情:
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
而是做一些像:
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
對於不需要ADDtar自動提取功能的其他項目(文件,目錄),您應該始終使用COPY。
ENTRYPOINT
最好的用法ENTRYPOINT是設置圖像的主命令,允許該圖像像該命令一樣運行(然后CMD用作默認標志)。
讓我們從命令行工具的圖像示例開始s3cmd:
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
現在可以像這樣運行圖像來顯示命令的幫助:
$ docker run s3cmd
或使用正確的參數執行命令:
$ docker run s3cmd ls s3://mybucket
這很有用,因為圖像名稱可以兼作二進制文件的引用,如上面的命令所示。
該ENTRYPOINT指令還可以與輔助腳本結合使用,允許它以與上述命令類似的方式運行,即使啟動該工具可能需要多個步驟。
例如,Postgres官方圖像 使用以下腳本作為其ENTRYPOINT:
#!/bin/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 "$@"
將app配置為PID 1
此腳本使用的execbash命令 ,以使最終運行的應用程序成為容器的PID 1.這允許應用程序接收發送到所述容器任何Unix信號。如需更多信息,請參閱ENTRYPOINT參考。
幫助程序腳本被復制到容器中並通過ENTRYPOINT容器啟動運行:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]
該腳本允許用戶以多種方式與Postgres交互。
它可以簡單地啟動Postgres:
$ docker run postgres
或者,它可用於運行Postgres並將參數傳遞給服務器:
$ docker run postgres postgres --help
最后,它還可以用來啟動一個完全不同的工具,比如Bash:
$ docker run --rm -it postgres bash
VOLUME
該 VOLUME指令應用於公開由docker容器創建的任何數據庫存儲區域,配置存儲或文件/文件夾。強烈建議您使用圖像VOLUME的任何可變和/或用戶可維修部分。
USER
如果服務可以在沒有權限的情況下運行,請使用USER更改為非root用戶。首先在Dockerfile類似的東西中創建用戶和組RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres。
考慮一個顯式的UID / GID
圖像中的用戶和組被分配了非確定性UID / GID,因為無論圖像重建如何,都會分配“下一個”UID / GID。因此,如果它很重要,您應該分配一個顯式的UID / GID。
由於Go存檔/ tar包處理稀疏文件時未解決的錯誤,嘗試在Docker容器內創建具有非常大的UID的用戶可能導致磁盤耗盡,因為/var/log/faillog容器層中填充了NULL(\ 0)字符。解決方法是將--no-log-init標志傳遞給useradd。Debian / Ubuntu adduser包裝器不支持此標志。
避免安裝或使用,sudo因為它具有可能導致問題的不可預測的TTY和信號轉發行為。如果您絕對需要類似的功能sudo,例如將守護程序初始化root為非運行它root,請考慮使用“gosu”。
最后,為了減少層次和復雜性,避免USER頻繁地來回切換。
WORKDIR
為了清晰和可靠,您應該始終使用絕對路徑 WORKDIR。此外,您應該使用難以閱讀,排除故障和維護WORKDIR的擴散指令RUN cd … && do-something。
ONBUILD
一個ONBUILD命令將當前執行后Dockerfile構建完成。ONBUILD在任何導出FROM當前圖像的子圖像中執行。將該ONBUILD命令視為父母Dockerfile給孩子的指令Dockerfile。
Docker構建ONBUILD在子代中的任何命令之前執行命令Dockerfile。
ONBUILD對於將要構建FROM給定圖像的圖像非常有用。例如,您將使用ONBUILD一個語言堆棧映像來構建在該語言中編寫的任意用戶軟件 Dockerfile,正如您在Ruby的ONBUILD變體中所看到的那樣。
構建的圖像ONBUILD應該獲得單獨的標記,例如:ruby:1.9-onbuild或ruby:2.0-onbuild。
把時要小心,ADD或COPY在ONBUILD。如果新構建的上下文缺少正在添加的資源,則“onbuild”映像將發生災難性故障。如上所述,添加單獨的標記有助於通過允許Dockerfile作者做出選擇來緩解這種情況。