Dockerfile构建实践
Dockerfile構建實踐
本文介紹了用于構建有效圖像的推薦最佳實踐和方法。
Docker通過從一個Dockerfile文本文件中讀取指令來自動構建映像,該文本文件按順序包含構建給定映像所需的所有命令。ADockerfile遵循特定的格式和指令集,可以在Dockerfile參考中找到該指令。
Docker映像由只讀層組成,每個只讀層代表一個Dockerfile指令。這些層是堆疊的,每個層都是與上一層相比變化的增量。考慮一下Dockerfile:
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
每條指令創建一層:
? FROM從ubuntu:18.04Docker映像創建一個圖層。
? COPY 從Docker客戶端的當前目錄添加文件。
? RUN使用構建應用程序make。
? CMD 指定在容器中運行什么命令。
運行圖像并生成容器時,可以在基礎層之上添加一個新的可寫層(“容器層”)。對運行中的容器所做的所有更改(例如寫入新文件,修改現有文件和刪除文件)都將寫入可寫容器層。
一般準則和建議
創建臨時容器
定義的圖片Dockerfile應生成盡可能短暫的容器。“短暫”是指可以停止并銷毀容器,然后對其進行重建和替換,并采用絕對的最低限度的設置和配置。
構建環境
啟動docker build命令時,當前的工作目錄稱為構建上下文。默認情況下,假定Dockerfile位于此處,但是可以使用文件標志(-f)指定其它位置。無論Dockerfile實際位于何處,當前目錄中文件和目錄的所有遞歸內容,都將作為構建上下文發送到Docker守護程序。
構建上下文示例
為構建上下文創建一個目錄并cd進入該目錄。將“ hello”寫入hello文本文件,然后創建一個cat在其上運行的Dockerfile。從構建上下文(.)中構建圖像:
mkdir myproject && cd myproject
echo “hello” > hello
echo -e “FROM busybox\nCOPY /hello /\nRUN cat /hello” > Dockerfile
docker build -t helloapp:v1 .
移動Dockerfile并hello進入單獨的目錄并構建映像的第二個版本(不依賴于上次構建的緩存)。使用-f以指向Dockerfile并指定構建上下文的目錄:
mkdir -p dockerfiles context
mv Dockerfile dockerfiles && mv hello context
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context
構建映像所不需要的文件,會導致較大的構建上下文和較大的映像大小。這會增加生成圖像的時間,拉動和推動圖像的時間以及容器運行時的大小。要查看構建上下文有多大,在構建時查找如下消息Dockerfile:
Sending build context to Docker daemon 187.8MB
通過stdin
docker具有通過管道的能力來構建圖像Dockerfile,通過stdin與本地或遠程構建上下文。管道中的Dockerfile通過stdin 可以執行一次性構建,無需編寫Dockerfile到磁盤上,或者有用Dockerfile的產生,并且不應該事后持續。
本節中的示例使用此處的文檔,但是可以使用提供Dockerfileon的任何方法stdin。
例如,以下命令是等效的:
echo -e ‘FROM busybox\nRUN echo “hello world”’ | docker build -
docker build -<<EOF
FROM busybox
RUN echo “hello world”
EOF
可以使用首選方法或最適合用例的方法替換示例。
使用STDIN中的DOCKERFILE構建映像,而無需發送構建上下文
使用此語法可使用Dockerfilefrom來構建映像stdin,而無需發送其它文件作為構建上下文。連字符(-)占據的位置PATH,并指示Docker從而不是目錄中讀取構建上下文(僅包含Dockerfile)stdin:
docker build [OPTIONS] -
以下示例使用Dockerfile傳遞構建圖像stdin。沒有文件作為構建上下文發送到守護程序。
docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo “hello world”
EOF
在Dockerfile 不需要將文件復制到映像中的情況下,省略構建上下文會很有用,并且由于沒有文件發送到守護程序,因此可以提高構建速度。
如果要通過從構建上下文中排除某些文件來提高構建速度, 使用.dockerignore進行排除。
如果使用此語法,嘗試構建使用COPY或ADD,導致失敗的Dockerfile。以下示例說明了這一點:
create a directory to work in
mkdir example
cd example
create an example file
touch somefile.txt
docker build -t myimage:latest -<<EOF
FROM busybox
COPY somefile.txt .
RUN cat /somefile.txt
EOF
observe that the build fails
…
Step 2/3 : COPY somefile.txt .
COPY failed: stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file or directory
使用STDIN中的DOCKERFILE從本地構建上下文進行構建
使用此語法可使用本地文件系統上的文件,但使用Dockerfilefrom來構建映像stdin。該語法使用-f(或–file)選項來指定Dockerfile使用,使用連字符(-)作為文件名可指示docker讀取Dockerfile從stdin:
docker build [OPTIONS] -f- PATH
下面的示例使用當前目錄(.)作為構建上下文,并構建用的圖像Dockerfile,其通過傳遞stdin使用這里文檔。
create a directory to work in
mkdir example
cd example
create an example file
touch somefile.txt
build an image using the current directory as context, and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt .
RUN cat /somefile.txt
EOF
使用STDIN中的DOCKERFILE從遠程構建上下文進行構建
使用此格式可以從遠程文件來構建一個圖像git庫,使用Dockerfile從stdin。該語法使用-f(或–file)選項來指定Dockerfile使用,使用連字符(-)作為文件名可指示docker讀取Dockerfile從stdin:
docker build [OPTIONS] -f- PATH
如果要從不包含的存儲庫中構建映像Dockerfile,或者想要使用custom來構建Dockerfile,而不維護自己的存儲庫派發,則此語法很有用。
下面的示例使用Dockerfilefrom構建一個圖像stdin,并添加GitHub上“ hello-world” Git存儲庫中的hello.c文件。
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c .
EOF
引擎
當使用遠程Git存儲庫作為構建上下文構建映像時,Dockergit clone在本地計算機上執行一個存儲庫,并將這些文件作為構建上下文發送到守護程序。需要git在運行docker build命令的主機上安裝此功能。
排除
要排除與構建無關的文件(無需重組源存儲庫),使用.dockerignore文件。該文件支持類似于.gitignore文件的排除模式。
使用多階段構建
多階段構建使可以大幅度減小最終圖像的大小,而不必努力減少中間層和文件的數量。
由于映像是在生成過程的最后階段生成的,可以利用生成緩存來最小化映像層。
例如,如果構建包含多個層,則可以將從更改頻率較低(以確保生成緩存可重用)到更改頻率較高的順序排序:
? 安裝構建應用程序所需的工具
? 安裝或更新庫依賴項
? 生成
Go應用程序的Dockerfile可能類似于:
FROM golang:1.11-alpine AS build
Install tools required for project
Run docker build --no-cache . to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
List project dependencies with Gopkg.toml and Gopkg.lock
These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
Install library dependencies
RUN dep ensure -vendor-only
Copy the entire project and build it
This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project
This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["–help"]
不要安裝不必要的軟件包
為了降低復雜性,依賴性,文件大小和構建時間,避免僅由于“很容易安裝”而安裝多余或不必要的軟件包。例如,不需要在數據庫映像中包括文本編輯器。
解耦應用程序
每個容器應該只有一個方面。將應用程序解耦到多個容器中,可以更輕松地水平縮放和重復使用容器。例如,一個Web應用程序堆棧可能由三個單獨的容器組成,每個容器都有自己的唯一映像,以分離的方式管理Web應用程序,數據庫和內存中緩存。
將每個容器限制為一個進程是一個很好的經驗法則,但這并不是一成不變的規則。例如,不僅可以使用初始化進程來生成容器,而且某些程序還可以自行生成其它進程。例如,Celery可以產生多個工作進程,而Apache可以為每個求創建一個進程。
根據最佳判斷,使容器盡可能保持清潔和模塊化。如果容器相互依賴,則可以使用Docker容器網絡。確保這些容器可以通信。
最小化層數
在較舊的Docker版本中,重要的是最小化映像中的層數以確保其性能。添加了以下功能來減少此限制:
? 只有說明RUN,COPY,ADD創建圖層。其它說明創建臨時的中間映像,并且不會增加構建的大小。
? 盡可能使用多階段構建,并且僅將所需的工件復制到最終映像中。這使可以在中間構建階段中包含工具和調試信息,而無需增加最終映像的大小。
排序多行參數
只要有可能,就可以通過字母數字排序多行參數來簡化以后的更改。這有助于避免軟件包重復,并使列表更易于更新。這也使PR易于閱讀和查看。在反斜杠(\)之前添加空格也有幫助。
下面是來自一個示例buildpack-deps圖像:
RUN apt-get update && apt-get install -y
bzr
cvs
git
mercurial
subversion
&& rm -rf /var/lib/apt/lists/*
構建緩存
構建映像時,Docker將逐步Dockerfile執行指令,并按指定的順序執行每個指令。檢查每條指令時,Docker會在其緩存中尋找一個可以重用的現有映像,而不是創建一個新的(重復的)映像。
如果根本不想使用緩存,則可以使用命令–no-cache=true 上的選項docker build。但是,如果確實允許Docker使用其緩存,那么了解何時可以找到匹配的映像非常重要。Docker遵循的基本規則概述如下:
? 從已在緩存中的父映像開始,將下一條指令與從該基本映像派生的所有子映像進行比較,以查看是否其中一個是使用完全相同的指令構建的。如果不是,則高速緩存無效。
? 在大多數情況下,只需將中的指令Dockerfile與子圖像之一進行比較就足夠了。但是,某些說明需要更多的檢查和解釋。
? 對于ADD和COPY指令,將檢查圖像中文件的內容,并為每個文件計算一個校驗和。在這些校驗和中不考慮文件的最后修改時間和最后訪問時間。在緩存查找期間,將校驗和與現有映像中的校驗和進行比較。如果文件中的任何內容(例如內容和元數據)已更改,則緩存將無效。
? 除了ADD和COPY命令之外,緩存檢查不會查看容器中的文件來確定緩存是否匹配。例如,在處理RUN apt-get -y update命令時,不檢查容器中更新的文件以確定是否存在緩存命中。在這種情況下,僅使用命令字符串本身來查找匹配項。
一旦緩存無效,所有后續Dockerfile命令都會生成新映像,并且不使用緩存。
Dockerfile說明
這些建議旨在幫助創建高效且可維護的工具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”
運行
RUN指令的Dockerfile參考
將多行長或復雜的RUN語句分割成多行,并用反斜杠分隔,以使Dockerfile更具可讀性,可理解性和可維護性。
適當的
可能最常見的用例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
&& rm -rf /var/lib/apt/lists/*
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
并非所有的外殼程序都支持該-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指令的Dockerfile參考
該CMD說明應與任何參數一起用于運行映像中包含的軟件。CMD應該幾乎總是以的形式使用CMD [“executable”, “param1”, “param2”…]。因此,如果映像用于服務(例如Apache和Rails),則應運行CMD [“apache2”,"-DFOREGROUND"]。實際上,建議將這種形式的指令用于任何基于服務的映像。
在大多數其它情況下,CMD應使用交互式外殼程序,例如bash,python和perl。例如,CMD [“perl”, “-de0”],CMD [“python”],或CMD [“php”, “-a”]。使用這種形式意味著執行諸如之類的東西時 docker run -it python,將被放入一個可用的shell中,可以開始使用了。除非和預期用戶已經非常熟悉其工作CMD方式,否則應該很少將其與CMD [“param”, “param”]結合使用。ENTRYPOINTENTRYPOINT
暴露
Dockerfile的EXPOSE指令參考
該EXPOSE指令指示容器在其上偵聽連接的端口。因此,應該為應用程序使用通用的傳統端口。例如,包含Apache Web服務器EXPOSE 80的圖像將使用,而包含MongoDB的圖像將使用EXPOSE 27017等等。
對于外部訪問,用戶可以執行docker run帶有標志的執行,該標志指示如何將指定端口映射到他們選擇的端口。對于容器鏈接,Docker為從接收者容器到源容器(即MYSQL_PORT_3306_TCP)的路徑提供了環境變量。
ENV
ENV指令的Dockerfile參考
為了使新軟件更易于運行,可以使用ENV更新PATH容器所安裝軟件的 環境變量。例如,ENV PATH=/usr/local/nginx/bin:PATH確保其CMD["nginx"]正常工作。該ENV指令對于提供特定于要容器化的服務的必需環境變量(例如Postgres的)也很有用PGDATA。最后,ENV還可以用來設置常用的版本號,以便更容易維護版本凹凸,如以下示例所示:ENVPGMAJOR=9.3ENVPGVERSION=9.3.4RUNcurl?SLhttps://example.com/postgres?PATH確保其CMD ["nginx"] 正常工作。 該ENV指令對于提供特定于要容器化的服務的必需環境變量(例如Postgres的)也很有用 PGDATA。 最后,ENV還可以用來設置常用的版本號,以便更容易維護版本凹凸,如以下示例所示: ENV PG_MAJOR=9.3 ENV PG_VERSION=9.3.4 RUN curl -SL https://example.com/postgres-PATH確保其CMD["nginx"]正常工作。該ENV指令對于提供特定于要容器化的服務的必需環境變量(例如Postgres的)也很有用PGDATA。最后,ENV還可以用來設置常用的版本號,以便更容易維護版本凹凸,如以下示例所示:ENVPGM?AJOR=9.3ENVPGV?ERSION=9.3.4RUNcurl?SLhttps://example.com/postgres?PG_VERSION.tar.xz | tar -xJC /usr/src/postgres && …
ENV PATH=/usr/local/postgres-PGMAJOR/bin:PG_MAJOR/bin:PGM?AJOR/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命令的命令來在單個層中全部設置,使用和取消設置該變量。可以使用;或分隔命令&&。如果使用第二種方法,并且其中一個命令失敗,則命令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指令的Dockerfile參考
? COPY指令的Dockerfile參考
盡管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 https://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 https://example.com/big.tar.xz
| tar -xJC /usr/src/things
&& make -C /usr/src/things all
對于不需要ADDtar自動提取功能的其它項目(文件,目錄),應始終使用COPY。
入口點
ENTRYPOINT指令的Dockerfile參考
最好的用法ENTRYPOINT是設置映像的主命令,使該映像像該命令一樣運行(然后CMD用作默認標志)。
讓從命令行工具的圖像示例開始s3cmd:
ENTRYPOINT [“s3cmd”]
CMD ["–help"]
現在可以像這樣運行圖像以顯示命令的幫助:
$ docker run s3cmd
或使用正確的參數執行命令:
$ docker run s3cmd ls s3://mybucket
這很有用,因為映像名稱可以用作對二進制文件的引用,如上面的命令所示。
該ENTRYPOINT指令也可以與輔助腳本結合使用,即使啟動該工具可能需要一個以上的步驟,也可以使其以與上述命令類似的方式起作用。
例如,Postgres Official Image 使用以下腳本作為其腳本ENTRYPOINT:
#!/bin/bash
set -e
if [ “1"=′postgres′];thenchown?Rpostgres"1" = 'postgres' ]; then chown -R postgres "1"=′postgres′];thenchown?Rpostgres"PGDATA”
if [ -z "$(ls -A "$PGDATA")" ]; thengosu postgres initdb
fiexec gosu postgres "$@"
fi
exec “$@”
將應用程序配置為PID 1
此腳本使用的execbash命令 ,以使最終運行的應用程序成為容器的PID 1.這允許應用程序接收發送到所述容器任何Unix信號。
將幫助程序腳本復制到容器中,并通過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指令的Dockerfile參考
該VOLUME指令應用于公開由Docker容器創建的任何數據庫存儲區,配置存儲或文件/文件夾。強烈建議VOLUME將圖像用于任何可變和/或用戶可維修的部分。
用戶
USER指令的Dockerfile參考
如果服務可以在沒有特權的情況下運行,使用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 / Ubuntuadduser包裝器不支持此標志。
避免安裝或使用sudo它具有不可預測的TTY和信號轉發行為,這可能會引起問題。如果絕對需要類似的功能sudo,例如將守護程序初始化為,root但以非運行方式運行root,考慮使用“ gosu”。
最后,為減少層次和復雜性,避免USER頻繁地來回切換。
WORKDIR
適用于WORKDIR指令的Dockerfile參考
為了清楚和可靠起見,應始終為使用絕對路徑 WORKDIR。另外,應該使用WORKDIR而不是像那樣RUN cd … && do-something繁瑣的說明,這些說明難以閱讀,排除故障和維護。
Build
適用于ONBUILD指令的Dockerfile參考
一個ONBUILD命令將當前執行后Dockerfile構建完成。 ONBUILD在派生FROM當前圖像的任何子圖像中執行。將ONBUILD命令視為父母Dockerfile對孩子的指示Dockerfile。
Docker構建ONBUILD在子級中的任何命令之前先執行命令 Dockerfile。
ONBUILD對于將要構建FROM給定圖像的圖像很有用。例如,將使用ONBUILD一個語言堆棧映像,該映像構建Dockerfile在Ruby中ONBUILD以該語言編寫的任意用戶軟件,正如在Ruby的變體中所看到的那樣。
使用生成的圖像ONBUILD應獲得一個單獨的標簽,例如: ruby:1.9-onbuild或ruby:2.0-onbuild。
放入ADD或COPY放入時要小心ONBUILD。如果新構建的上下文缺少要添加的資源,則“ onbuild”映像將災難性地失敗。如上所述,添加一個單獨的標簽可以允許Dockerfile做出選擇,從而有助于緩解這種情況。
總結
以上是生活随笔為你收集整理的Dockerfile构建实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker基本原理概述
- 下一篇: Rust和C / C ++的跨语言链接时