編寫Dockerfile的最佳實踐

  • 預計閱讀時間: 31分鐘

本文檔介紹了用於構建有效鏡像的最佳實踐和方法。


編寫Dockerfile的最佳實踐

Docker通過讀取Dockerfile的指令自動構建鏡像,該文件是一個文本文件,其中依次包含構建給定鏡像所需的所有命令。 Dockerfile遵循特定的格式和指令集,您可以在Dockerfile參考中找到該文件 。


編寫Dockerfile的最佳實踐

Docker鏡像由只讀層組成,每個只讀層代表一個Dockerfile指令。 各個層堆疊在一起,每個層都是上一層的變化的增量。 考慮一下這個Dockerfile :

<code>FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py/<code>

每條指令創建一層:

  • FROM從ubuntu:18.04 Docker鏡像創建一個層。
  • COPY從Docker客戶端的當前目錄添加文件。
  • RUN使用make構建您的應用程序。
  • CMD指定在容器內運行什麼命令。

運行鏡像並生成容器時,可以在基礎層之上添加一個新的可寫層 (“容器層”)。 對運行中的容器所做的所有更改(例如寫入新文件,修改現有文件和刪除文件)都將寫入這個可寫容器層。

有關鏡像層的更多信息(以及Docker如何構建和存儲鏡像),請參閱關於存儲驅動程序 。

一般準則和建議

創建臨時容器

Dockerfile定義的鏡像應該生成儘可能短暫的容器。 “短暫”是指可以停止並銷燬容器,然後對其進行重建和替換,並使用絕對的最小限度的設置和配置。

請參閱“十二因子應用程序”方法下的“ 過程 ” ,以瞭解以這種無狀態方式運行容器的動機。

瞭解構建的上下文環境

發出docker build命令時,當前工作目錄稱為build context 。 默認情況下,假定Dockerfile位於此處,但是您可以使用文件標誌( -f )指定其他位置。 無論Dockerfile實際位於何處,當前目錄中文件和目錄的所有遞歸內容都將作為構建上下文發送到Docker守護程序。

構建上下文示例

創建一個用於構建上下文的目錄,並將其插入cd 。 將“ hello”寫入名為hello的文本文件,並創建一個在其上運行cat的Dockerfile。 從構建上下文( . )中構建鏡像:

<code>mkdir myproject && cd myproject
echo "hello" > hello
echo -e "FROM busybox\\nCOPY /hello /\\nRUN cat /hello" > Dockerfile
docker build -t helloapp:v1 ./<code>

將Dockerfile和hello移到單獨的目錄中,並構建鏡像的第二個版本(不依賴上次構建的緩存)。 使用-f指向Dockerfile並指定構建上下文的目錄:

<code>mkdir -p dockerfiles context
mv Dockerfile dockerfiles && mv hello context
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context/<code>

疏忽地包含了構建鏡像所不需要的構建上下文文件會導致較大的鏡像體積。 這會增加生成鏡像的時間,拉取和推送鏡像的時間以及容器運行時的大小。 要查看您的構建上下文有多大,請在構建Dockerfile時查找如下消息:

<code>Sending build context to Docker daemon  187.8MB/<code>

Pipe Dockerfile through stdin

Docker可以通過使用本地或遠程構建上下文通過stdin Dockerfile來構建鏡像。 通過stdin插入Dockerfile可以在不將Dockerfile寫入磁盤的情況下執行一次性構建,或者在生成Dockerfile且以後不應持續的情況下使用。

為了方便起見,本節中的示例使用此處的文檔 ,但是可以使用在stdin上提供Dockerfile任何方法。

例如,以下命令是等效的:

<code> echo -e 'FROM busybox\\nRUN echo "hello world"' | docker build –
 
docker build -<FROM busybox
RUN echo "hello world"
EOF
/<code>

您可以使用首選方法或最適合您的用例的方法替換示例。

使用來自stdin的Dockerfile構建鏡像,而無需發送構建上下文

使用此語法使用stdin的Dockerfile構建鏡像,而無需發送其他文件作為構建上下文。 連字符( - )佔據PATH的位置,並指示Docker從stdin而不是目錄中讀取構建上下文(僅包含Dockerfile ):

<code> docker build [OPTIONS] -/<code>

以下示例使用通過stdin傳遞的Dockerfile構建鏡像。 沒有文件作為構建上下文發送到守護程序。

<code>docker build -t myimage:latest -<FROM busybox
RUN echo "hello world"
EOF
/<code>

在您的Dockerfile不需要將文件複製到鏡像中的情況下,省略構建上下文會很有用,並且由於沒有文件發送到守護程序,因此可以提高構建速度。

如果要通過從構建上下文中排除某些文件來提高構建速度,請使用.dockerignore進行排除 。

注意 :如果使用此語法,嘗試構建使用COPY或ADD的Dockerfile將會失敗。 以下示例說明了這一點:

<code># create a directory to work in
mkdir example
cd example
 
# create an example file
touch somefile.txt
 
docker build -t myimage:latest -<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
/<code>

使用stdin中的Dockerfile,從本地構建上下文進行構建

使用此語法使用本地文件系統上的文件,但使用stdin的Dockerfile來構建鏡像。 該語法使用-f (或--file )選項指定要使用的Dockerfile ,並使用連字符( - )作為文件名來指示Docker從stdin讀取Dockerfile :

<code>docker build [OPTIONS] -f- PATH/<code>

以下示例使用當前目錄( . )作為構建上下文,並使用Dockerfile構建鏡像,並使用here文檔通過stdin傳遞該文件 。

<code># 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- . <FROM busybox
COPY somefile.txt .
RUN cat /somefile.txt
EOF
/<code>

使用stdin中的Dockerfile從遠程構建上下文進行構建

使用此語法使用stdin的Dockerfile使用遠程git存儲庫中的文件構建鏡像。 該語法使用-f (或--file )選項指定要使用的Dockerfile ,並使用連字符( - )作為文件名來指示Docker從stdin讀取Dockerfile :

<code>docker build [OPTIONS] -f- PATH/<code>

如果您要從不包含Dockerfile的存儲庫中構建鏡像,或者想要使用自定義Dockerfile進行構建而又不維護自己的存儲庫派生,則此語法很有用。

以下示例使用stdin的Dockerfile構建鏡像,並從GitHub上的“ hello-world” Git存儲庫添加hello.c文件。

<code>docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <FROM busybox
COPY hello.c .
EOF
/<code>

釋義

當使用遠程Git存儲庫作為構建上下文構建鏡像時,Docker在本地計算機上執行存儲庫的git clone ,並將這些文件作為構建上下文發送到守護程序。 此功能要求將git安裝在運行docker build命令的主機上。

用.dockerignore排除

要排除與構建無關的文件(無需重構源存儲庫),請使用.dockerignore文件。 該文件支持類似於.gitignore文件的排除模式。 有關創建一個的信息,請參見.dockerignore文件 。

使用多階段構建

多階段構建使您可以大幅度減小最終鏡像的大小,而不必努力減少中間層和文件的數量。

由於鏡像是在構建過程的最後階段生成的,因此可以利用構建緩存來最小化鏡像層。

例如,如果您的構建包含多個層,則可以將它們從更改頻率較低(以確保生成緩存可重用)到更改頻率較高的順序排序:

  • 安裝構建應用程序所需的工具
  • 安裝或更新庫依賴項
  • 生成您的申請

Go應用程序的Dockerfile可能類似於:

<code>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"] /<code>

不要安裝不必要的軟件包

為了降低複雜性,依賴性,文件大小和構建時間,請避免僅僅因為它們“很容易安裝”而安裝多餘或不必要的軟件包。 例如,您不需要在數據庫鏡像中包括文本編輯器。

解耦應用

每個容器應該只有一個關注點。 將應用程序解耦到多個容器中,可以更輕鬆地水平縮放和重複使用容器。 例如,一個Web應用程序堆棧可能由三個單獨的容器組成,每個容器都有自己的唯一鏡像,以分離的方式管理Web應用程序,數據庫和內存中的緩存。

將每個容器限制為一個進程是一個很好的經驗法則,但這並不是一成不變的規則。 例如,不僅可以使用初始化進程來生成容器,而且某些程序還可以自行生成其他進程。 例如, Celery可以產生多個工作進程,而Apache可以為每個請求創建一個進程。

根據您的最佳判斷,使容器保持清潔和模塊化。 如果容器相互依賴,則可以使用Docker容器網絡來確保這些容器可以通信。

減少層數

在較舊的Docker版本中,務必最小化鏡像中的層數以確保其性能。 添加了以下功能來減少此限制:

  • 只有指令RUN , COPY , ADD才能創建層。 其他說明創建臨時的中間鏡像,並且不會增加構建的大小。
  • 儘可能使用多階段構建 ,並且僅將所需的工件複製到最終鏡像中。 這使您可以在中間構建階段中包含工具和調試信息,而無需增加最終鏡像的大小。

多行參數排列

儘可能通過字母數字排序多行參數來簡化以後的更改。 這有助於避免軟件包重複,並使列表更易於更新。 這也使PR易於閱讀和查看。 在反斜槓( \\ )之前添加空格也有幫助。

這是來自buildpack-deps鏡像的示例:

<code>RUN apt-get update && apt-get install -y \\
  bzr \\
  cvs \\
  git \\
  mercurial \\
  subversion/<code>

利用構建緩存

在構建鏡像時,Docker將Dockerfile執行Dockerfile的指令, Dockerfile指定的順序執行每個指令。 在檢查每條指令時,Docker會在其緩存中尋找一個可以重用的現有鏡像,而不是創建一個新的(重複的)鏡像。

如果根本不想使用緩存,則可以在docker build命令上使用--no-cache=true選項。 但是,如果您確實讓Docker使用其緩存,那麼瞭解何時可以找到匹配的鏡像非常重要。 Docker遵循的基本規則概述如下:

  • 從已在緩存中的父鏡像開始,將下一條指令與從該基本鏡像派生的所有子鏡像進行比較,以查看是否其中一個是使用完全相同的指令構建的。 如果不是,則高速緩存無效。
  • 在大多數情況下,僅將Dockerfile的指令與子鏡像之一進行比較就足夠了。 但是,某些說明需要更多的檢查和解釋。
  • 對於ADD和COPY指令,將檢查鏡像中文件的內容,併為每個文件計算一個校驗和。 在這些校驗和中不考慮文件的最後修改時間和最後訪問時間。 在高速緩存查找期間,將校驗和與現有鏡像中的校驗和進行比較。 如果文件中的任何內容(例如內容和元數據)發生了更改,則緩存將無效。
  • 除了ADD和COPY命令以外,緩存檢查不會查看容器中的文件來確定緩存是否匹配。 例如,在處理RUN apt-get -y update命令時,不會檢查容器中RUN apt-get -y update的文件,以確定是否存在緩存命中。 在這種情況下,僅使用命令字符串本身來查找匹配項。

緩存無效後,所有後續Dockerfile命令都會生成新鏡像,並且不使用緩存。

Dockerfile說明

這些建議旨在幫助您創建高效且可維護的Dockerfile 。

FROM

Dockerfile的FROM指令參考

儘可能使用當前的官方鏡像作為鏡像的基礎。 我們建議使用Alpine鏡像,因為它受到嚴格控制且尺寸較小(當前小於5 MB),同時仍是完整的Linux發行版。

LABEL

瞭解對象標籤

您可以在鏡像上添加標籤,以幫助按項目組織鏡像,記錄許可信息,幫助自動化或出於其他原因。 對於每個標籤,添加一行以LABEL開頭並帶有一個或多個鍵值對的行。 以下示例顯示了不同的可接受格式。 內嵌包含解釋性註釋。

帶空格的字符串必須用引號引起來, 否則必須轉義空格。 內引號( " )也必須轉義。

<code># 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=""/<code>

一幅鏡像可以有多個標籤。 在Docker 1.10之前,建議將所有標籤合併為一個LABEL指令,以防止創建額外的層。 不再需要此操作,但仍支持組合標籤。

<code># Set multiple labels on one line 

LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"/<code>

上面也可以寫成:

<code> # 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"/<code>

請參閱瞭解對象標籤以獲取有關可接受的標籤鍵和值的準則。 有關查詢標​​籤的信息,請參閱“ 管理對象上的標籤”中與過濾有關的項目。 另請參閱Dockerfile參考中的LABEL 。

RUN

RUN指令的Dockerfile參考

將多行長或複雜的RUN語句分割成多行,並用反斜槓分隔,以使您的Dockerfile更具可讀性,可理解性和可維護性。

APT-GET

RUN的最常見用例可能是apt-get的應用程序。 因為它安裝軟件包,所以RUN apt-get命令需要注意一些陷阱。

避免使用RUN apt-get upgrade和dist-upgrade ,因為許多來自父鏡像的“基本”軟件包都無法在無特權的容器內升級。 如果父鏡像中包含的軟件包已過期,請聯繫其維護者。 如果您知道有一個特定的軟件包foo需要更新,請使用apt-get install -y foo自動更新。

始終在同一RUN語句中將RUN apt-get update與apt-get install結合在一起。 例如:

<code>RUN apt-get update && apt-get install -y \\
    package-bar \\
    package-baz \\
    package-foo/<code>

在RUN語句中單獨使用apt-get update會導致緩存問題,並且後續的apt-get install說明將失敗。 例如,假設您有一個Dockerfile:

<code>FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl/<code>

構建鏡像後,所有層都在Docker緩存中。 假設您稍後通過添加額外的軟件包來修改apt-get install :

<code>FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx/<code>

Docker將初始指令和修改後的指令視為相同,並重復使用先前步驟中的緩存。 結果,由於構建使用了緩存版本,因此不執行apt-get update 。 由於未運行apt-get update ,因此您的構建可能會獲得curl和nginx軟件包的過時版本。

使用RUN apt-get update && apt-get install -y可確保您的Dockerfile安裝最新的軟件包版本,而無需進一步的編碼或手動干預。 這種技術稱為“緩存清除”。 您還可以通過指定軟件包版本來實現緩存清除。 這稱為版本固定,例如:

<code>RUN apt-get update && apt-get install -y \\
    package-bar \\

    package-baz \\
    package-foo=1.3.*/<code>

版本固定會強制構建檢索特定版本,而不管緩存中的內容是什麼。 該技術還可以減少由於所需包裝中的意外更改而導致的故障。

下面是格式正確的RUN指令,演示了所有的apt-get建議。

<code>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/*/<code>

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命令取決於使用管道字符( | )將一個命令的輸出管道傳輸到另一個命令的能力,如以下示例所示:

<code>RUN wget -O - https://some.site | wc -l > /number/<code>

Docker使用/bin/sh -c解釋器執行這些命令,該解釋器僅評估管道中最後一個操作的退出代碼以確定成功。 在上面的示例中,即使wget命令失敗,只要wc -l命令成功,此構建步驟也會成功並生成一個新鏡像。

如果希望由於管道中的任何階段的錯誤而導致命令失敗,請在set -o pipefail &&添加set -o pipefail &&以確保意外的錯誤可以防止構建意外進行。 例如:

<code>RUN set -o pipefail && wget -O - https://some.site | wc -l > /number/<code>

並非所有的外殼程序都支持-o pipefail選項。

如果是基於Debian的鏡像上的dash外殼,請考慮使用RUN的exec形式顯式選擇一個確實支持pipefail選項的外殼。 例如:

<code>RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]/<code>

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 ,您將被放入可用的外殼中,隨時可以使用。 除非您和您的預期用戶已經非常熟悉ENTRYPOINT工作原理,否則CMD很少以CMD ["param", "param"]的形式與ENTRYPOINT結合使用。

EXPOSE

有關EXPOSE指令的Dockerfile參考

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還可以用於設置常用的版本號,以便更容易維護版本凹凸,如以下示例所示:

<code>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/<code>

類似於在程序中具有恆定變量(與硬編碼值相反),此方法使您可以更改單個ENV指令以自動神奇地修改容器中軟件的版本。

每條ENV線都會創建一個新的中間層,就像RUN命令一樣。 這意味著即使您在以後的層中取消設置環境變量,它也仍將保留在該層中,並且其值也無法轉儲。 您可以通過創建如下所示的Dockerfile,然後對其進行構建來進行測試。

<code>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/<code>

為避免這種情況,並真正取消設置環境變量,請在外殼程序中使用RUN命令和shell命令,以在單個層中全部設置,使用和取消設置該變量。 您可以使用;分隔命令; 或&& 。 如果您使用第二種方法,並且其中一個命令失敗,則docker build也將失敗。 這通常是個好主意。 將\\用作Linux Dockerfiles的行連續字符可提高可讀性。 您還可以將所有命令放入一個Shell腳本中,並讓RUN命令運行該Shell腳本。

<code>FROM alpine
RUN export ADMIN_USER="mark" \\
    && echo $ADMIN_USER > ./mark \\
    && unset ADMIN_USER
CMD sh/<code>

<code>$ docker run --rm test sh -c 'echo $ADMIN_USER'/<code>

ADD or COPY

  • 用於ADD指令的Dockerfile參考
  • COPY指令的Dockerfile參考

儘管ADD和COPY在功能上相似,但通常來說COPY是首選。 那是因為它比ADD更透明。 COPY僅支持將本地文件基本複製到容器中,而ADD某些功能(如僅本地tar提取和遠程URL支持)並不是立即顯而易見的。 因此,與ADD rootfs.tar.xz /一樣, ADD rootfs.tar.xz /的最佳用途是將本地tar文件自動提取到鏡像中。

如果您有多個使用不同上下文的文件的Dockerfile步驟,請單獨COPY而不是一次全部COPY 。 這樣可以確保僅在特別需要的文件發生更改時,才使每個步驟的構建緩存無效(強制重新運行該步驟)。

例如:

<code>COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp//<code>

與放置COPY . /tmp/相比,導致RUN步驟的緩存失效更少COPY . /tmp/ COPY . /tmp/之前。

由於鏡像大小很重要,因此強烈建議不要使用ADD從遠程URL獲取軟件包。 您應該使用curl或wget代替。 這樣,您可以在提取文件後刪除不再需要的文件,而不必在鏡像中添加另一層。 例如,您應該避免做以下事情:

<code>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/<code>

相反,請執行以下操作:

<code>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/<code>

對於不需要ADD的tar自動提取功能的其他項目(文件,目錄),應始終使用COPY 。

ENTRYPOINT

ENTRYPOINT指令的Dockerfile參考

ENTRYPOINT的最佳用法是設置鏡像的主命令,以使該鏡像像該命令一樣運行(然後使用CMD作為默認標誌)。

讓我們從命令行工具s3cmd的鏡像示例開始:

<code>ENTRYPOINT ["s3cmd"]
CMD ["--help"]/<code>

現在可以像這樣運行鏡像以顯示命令的幫助:

<code>$ docker run s3cmd/<code>

或使用正確的參數執行命令:

<code>$ docker run s3cmd ls s3://mybucket/<code>

這很有用,因為鏡像名稱可以用作對二進制文件的引用,如上面的命令所示。

ENTRYPOINT指令也可以與輔助腳本結合使用,即使啟動該工具可能需要一個以上的步驟,也可以使其與上述命令類似地工作。

例如, Postgres Official Image使用以下腳本作為其ENTRYPOINT :

<code>#!/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 "$@"/<code>

將應用程序配置為PID 1

該腳本使用exec Bash命令,以便最終運行的應用程序成為容器的PID1。這使該應用程序可以接收發送到該容器的所有Unix信號。 有關更多信息,請參見ENTRYPOINT參考 。

將幫助程序腳本複製到容器中,並在容器啟動時通過ENTRYPOINT運行:

<code>COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]/<code>

該腳本允許用戶以多種方式與Postgres進行交互。

它可以簡單地啟動Postgres:

<code>$ docker run postgres/<code>

或者,它可以用於運行Postgres並將參數傳遞給服務器:

<code>$ docker run postgres postgres --help/<code>

最後,它也可以用於啟動一個完全不同的工具,例如Bash:

<code>$ docker run --rm -it postgres bash/<code>

VOLUME

VOLUME指令的Dockerfile參考

VOLUME指令應用於公開由Docker容器創建的任何數據庫存儲區,配置存儲或文件/文件夾。 強烈建議您將VOLUME用於鏡像的任何可變和/或用戶可維修的部分。

USER

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 / Ubuntu adduser包裝器不支持此標誌。

避免安裝或使用sudo因為它具有不可預測的TTY和信號轉發行為,可能會導致問題。 如果您絕對需要類似於sudo功能,例如將守護程序初始化為root卻以非root身份運行,請考慮使用“ gosu” 。

最後,為減少層次和複雜性,請避免頻繁來回切換USER 。

WORKDIR

WORKDIR指令的Dockerfile參考

為了清楚和可靠,您應該始終為WORKDIR使用絕對路徑。 另外,您應該使用WORKDIR而不是增加諸如RUN cd … && do-something類的指令,這些指令難以閱讀,排除故障和維護。

ONBUILD

Dockerfile的ONBUILD指令參考

當前Dockerfile構建完成後,將執行ONBUILD命令。 ONBUILD在FROM當前鏡像派生的任何子鏡像中執行。 將ONBUILD命令視為父Dockerfile給子Dockerfile 。

Docker構建在子Dockerfile任何命令之前執行ONBUILD命令。

ONBUILD給定鏡像構建的鏡像, ONBUILD非常有用。 例如,您可以將ONBUILD用於語言堆棧鏡像,該鏡像可在ONBUILD中構建用該語言編寫的任意用戶軟件,如Ruby的ONBUILD變體所示 。

使用ONBUILD構建的鏡像應獲得單獨的標籤,例如: ruby:1.9-onbuild或ruby:2.0-onbuild 。

將ADD或COPY放入ONBUILD時要小心。 如果新構建的上下文缺少要添加的資源,則“ onbuild”鏡像將災難性地失敗。 如上所述,添加一個單獨的標籤,可以通過允許Dockerfile作者做出選擇來緩解這種情況。

官方鏡像示例

這些官方鏡像具有示例性Dockerfile :

  • Go [ttps://hub.docker.com/_/golang/]
  • Perl [https://hub.docker.com/_/perl/]
  • Hy [https://hub.docker.com/_/hylang/]
  • Ruby [https://hub.docker.com/_/ruby/]

譯文地址:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/


  • Dockerfile Reference
  • More about Base Images
  • More about Automated Builds
  • Guidelines for Creating Official Images


分享到:


相關文章: