Dockerfile的最佳實踐

簡介

Docker經過讀取Dockerfile文件中的指令自動構建鏡像。Dockerfile文件爲一個文本文件,裏面包含構建鏡像所需的全部的命令。Dockerfile文件遵循特定的格式和指令集 Docker鏡像由只讀層組成,每一個層都表明一個Dockerfile指令。這些層是堆疊的,每一個層都是前一層變化的增量。示例:php

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
複製代碼

每一個指令構建一層: FROM 根據ubuntu18.04docker鏡像建立一個層 COPY 將Docker客戶端當前的目錄文件添加到鏡像中 RUN 使用make構建應用程序 CMD 指定在容器中運行的命令前端

經過鏡像啓動一個容器的時候,會在基礎層上添加一個可寫層。對正在運行的容器所作的全部更改(例如,寫入新文件,修改現有文件和刪除文件)都將寫入此可寫容器層。python

建議

1.建立臨時容器

經過Dockerfile文件定義的鏡像,產生的容器儘量的是臨時的。所謂的臨時,意思是,容器可中止,銷燬,重建和替代爲最小設置和配置。linux

2.理解構建上下文

當你發出一個docker build命令時,當前目錄將做爲構建的上下文。Dockerfile默認存放在此目錄下。可是,你能夠經過-f選項指定一個不一樣的目錄。不論Dockerfile文件實際存放在何處。當前目錄中的全部文件和目錄的遞歸內容都將做爲構建上下文發送到Docker守護程序。nginx

示例: 建立一個用於構建上下文的目錄,並cd進入該目錄下。寫入"Hello"字符串到一個文本文件中,命名爲hello並建立一個Dockerfile文件運行cat命令,經過.構建上下文構建鏡像。git

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文件的目錄,並指定構建的上下文目錄。github

mkdir -p dockerfiles context
mv Dockerfile dockerfiles && mv hello context
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context
複製代碼

構建鏡像時,不經意包含沒必要要的文件將會致使一個臃腫的構建上下文和一個臃腫的鏡像,這將致使構建鏡像時長增長,提交到倉庫和從倉庫拉取時長,容器運行時大小都將增長。構建鏡像時能夠看到構建上下文的大小。golang

經過.dockerignore文件排除

要排除與構建無關的文件(不重構源存儲庫),請使用.dockerignore文件。此文件支持相似於.gitignore文件的排除模式web

使用多級構建

多級構建容許你大幅減少最終圖像的大小而無需努力減小中間層和文件的數量。因爲圖像是在構建過程的最後階段構建的,所以能夠經過利用構建緩存來最小化圖像層。例如,若是您的構建包含多個圖層,則能夠從較不頻繁更改(以確保構建緩存可重用)到更頻繁更改的順序對它們進行排序:sql

  • build程序所需的工具安裝
  • 安裝升級庫依賴
  • 生產應用程序

示例: 一個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應用程序、數據庫和內存緩存。

將每一個容器限制爲一個進程是一個很好的經驗法則,但它不是一個硬性規則。 例如,不只可使用init進程生成容器,並且某些程序可能會自行生成其餘進程。 例如,Celery能夠生成多個工做進程,Apache能夠爲每一個請求建立一個進程。

使用你最好的判斷來保持容器儘量的乾淨和模塊化。若是容器彼此依賴,可使用Docker容器網絡來確保這些容器可以通訊。

減小圖層的數量

在舊版本的Docker中,最大限度地減小圖像中的圖層數量以確保它們具備高性能很是重要。添加了如下特性來減小這種限制:

  • 只用RUN,COPY,ADD指令會建立層,其餘指令建立臨時中間層,並不增長構建的大小。
  • 在可能的狀況下,使用多階段構建,並僅將所需的工件複製到最終圖像中。這容許您在中間構建階段中包含工具和調試信息,而不會增長最終圖像的大小

多行參數排序

只要有可能,經過按字母順序排序多行參數來緩解之後的更改。這有助於避免重複包並使列表更容易更新。這也使PR更容易閱讀和審查。 在反斜槓(\)以前添加空格也有幫助。

示例:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion
複製代碼

緩存利用

構建映像時,Docker會逐步執行Dockerfile中的指令,按指定的順序執行每一個指令。 在檢查每條指令時,Docker會在其緩存中查找能夠重用的現有映像,而不是建立新的(重複)映像。

若是你不想使用緩存,能夠在docker build命令中使用--no-cache=true選項,然而,若是你讓Docker使用緩存,理解何時使用,何時不能,使用一個匹配的鏡像將很是重要,Docker遵循的基本規則概述以下:

  • 從已在緩存中的父鏡像開始,接下來的指令將對比全部的子鏡像看是否有一個使用相同的指令構建而成,若是沒有,緩存則是無效的。
  • 在大多數狀況下,只需將Dockerfile中的指令與其中一個子映像進行比較就足夠了。然而,某些指示須要更多的檢查和解釋。
  • 對於ADD和COPY指令,將檢查映像中文件的內容,併爲每一個文件計算校驗和。 在這些校驗和中不考慮文件的最後修改時間和最後訪問時間。 在高速緩存查找期間,將校驗和與現有映像中的校驗和進行比較。 若是文件中的任何內容(例如內容和元數據)發生了任何更改,則緩存將失效。
  • 除了ADD和COPY命令以外,高速緩存檢查不會查看容器中的文件以肯定高速緩存匹配。 例如,在處理RUN apt-get -y update命令時,不檢查容器中更新的文件以肯定是否存在緩存命中。 在這種狀況下,只需使用命令字符串自己來查找匹配項。

一旦緩存無效,Dockerfile命令將產生新的鏡像而不使用緩存。

Dockerfile指令

FROM

FROM <image> [AS <name>]

或者

FROM <image>[:<tag>] [AS <name>]

或者

FROM <image>[@<digest>] [AS <name>]

  • FROM指令初始化新的構建階段併爲後續指令設置基本映像。所以,有效的Dockerfile必須以FROM指令開頭.
  • FROM能夠出現屢次在同一個Dockerfile文件中,爲了建立多個鏡像,或者使用一個做爲另外一個鏡像的依賴,只要在每一個新的FROM執行以前記錄上一個鏡像的ID。每一個FROM指令都會清空以前命令建立的任何狀態。
  • 可選的,每一個FROM指令均可以經過AS name提供一個名詞,該名詞能夠在子FROM指令和COPY --from<name|index>指令中指待該鏡像。
  • tag和digest值是可選的,若是省略他們,將使用latest版本,若是未找到將會拋出異常。 儘量的使用官方鏡像做爲基礎鏡像。

瞭解ARG和FROM如何互動: FROM指令支持在第一個FROM以前經過任意ARG指令聲明變量 示例:

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras
複製代碼

LABEL

你能夠爲你的鏡像添加labels,用來組織鏡像,記錄證書信息,或者其餘緣由,對應每一個label,增長以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=""
複製代碼

上面的也能夠寫成以下形式:

# 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

  • RUN
  • RUN ["executable", "param1", "param2"]

RUN指令將執行任何命令在當前鏡像的一個新層上並提交結果。提交後的鏡像將會在下一步中使用。

在使用反斜槓分隔的多行上拆分長或複雜的RUN語句,以使Dockerfile更具可讀性,可理解性和可維護性。

RUN apt-get update && apt-get install -y這些命令不要分開,

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/*
複製代碼

上面的這個指令是建議這樣使用的,最後的執行用於清除安裝過程當中的緩存。

CMD

CMD ["executable","param1","param2"]

CMD ["param1","param2"]

CMD command param1 param2

一個Dockerfile文件中,只能有一個CMD指令,若是出現多個,只有最後一個起做用。

CMD的主要目的是爲執行容器提供默認值。 這些默認值能夠包含可執行文件,也能夠省略可執行文件,在這種狀況下,您還必須指定ENTRYPOINT指令。

若是CMD用於爲ENTRYPOINT指令提供默認參數,則應使用JSON數組格式指定CMD和ENTRYPOINT指令。exec表單被解析爲JSON數組,這意味着您必須使用雙引號(「)來圍繞單詞而不是單引號(')。

CMD指令應該被用於運行被鏡像包含的軟件,並使用提供的參數。CMD指令應儘量的使用CMD ["executable", "params1","params2"...].所以,若是鏡像是一個服務,好比Apache,應這樣運行CMD ["apache2", "-DFOREGROUND"].事實上,這種形式的指令被建議使用在以服務爲基礎的鏡像上。

大多數狀況,CMD應該提供一個交互式的shell。例如bash.python.perl.示例: CMD ["perl", "-de0"], CMD ["python"],或者CMD ["php" "-a"].以這種形式的指令,意味着當你執行如這樣的命令時docker run -it python,你將進入一個可用的shell裏面,CMD指令在和ENTRYPOINT指令結合使用時,將不多使用CMD ["param", "param"]這種形式,除非你和客戶都很熟悉ENTRYPOINT是如何工做的。

EXPOSE

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker容器在運行時偵聽指定的網絡端口。 您能夠指定端口是偵聽TCP仍是UDP,若是未指定協議,則默認爲TCP。EXPOSE指令實際上不會發布端口。 它在構建映像的人和運行容器的人之間起到一種文檔的做用,關於哪些端口要發佈。要在運行容器時實際發佈端口,請在docker run上使用-p標誌發佈和映射一個或多個端口,或使用-P標誌發佈全部公開的端口並將它們映射到高階端口。默認狀況下,EXPOSE假定爲TCP。 您還能夠指定UDP:

EXPOSE 80/udp

要同時在TCP和UDP上公開,須要包含兩行:

EXPOSE 80/tcp
EXPOSE 80/udp
複製代碼

在這種狀況下,若是將-P與docker run一塊兒使用,則端口將爲TCP公開一次,對UDP公開一次。 請記住,-P在主機上使用短暫的高階主機端口,所以TCP和UDP的端口不一樣。

不管EXPOSE設置如何,您均可以使用-p標誌在運行時覆蓋它們。 例如: docker run -p 80:80/tcp -p 80:80/udp ...

要在主機系統上設置端口重定向,請參閱使用-P標誌。 docker network命令支持建立用於容器之間通訊的網絡,而無需公開或發佈特定端口,由於鏈接到網絡的容器能夠經過任何端口相互通訊。

EXPOSE指令指示容器偵聽鏈接的端口。 所以,您應該爲您的應用程序使用通用的傳統端口。 例如,包含Apache Web服務器的映像將使用EXPOSE 80,而包含MongoDB的映像將使用EXPOSE 27017等.

對於外部訪問,您的用戶可使用標誌執行docker run,該標誌指示如何將指定端口映射到他們選擇的端口。 對於容器連接,Docker爲從收件人容器返回源的路徑提供環境變量.

ENV

ENV <key> <value>
ENV <key>=<value> ...
複製代碼

ENV指令將環境變量設置爲值。 此值將在構建階段中的全部後續指令的環境中,而且也能夠在許多內聯替換。

ENV指令有兩種形式。 第一種形式ENV ,將單個變量設置爲一個值。 第一個空格後面的整個字符串將被視爲 - 包括空格字符。 該值將針對其餘環境變量進行解釋,所以若是未對其進行轉義,則將刪除引號字符。

第二種形式ENV = ...容許一次設置多個變量。 請注意,第二種形式在語法中使用等號(=),而第一種形式則否則。 與命令行解析同樣,引號和反斜槓可用於在值內包含空格。

示例:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
複製代碼
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
複製代碼

兩種形式是同樣的。

爲了使新軟件更易於運行,您可使用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

要防止這種狀況,而且實際上取消設置環境變量,請使用帶有shell命令的RUN命令,在單個圖層中設置,使用和取消設置變量all。 您能夠將命令與; 要麼 &&。 若是您使用第二種方法,而且其中一個命令失敗,則docker構建也會失敗。 這一般是一個好主意。 使用\做爲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'不會輸出mark

ADD or COPY

ADD

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chown功能僅用於Dockerfile構建linux容器.在windows容器上無效。

ADD指令從複製新文件,目錄或遠程文件URL,並將它們添加到路徑的鏡像文件系統中。 每一個可能包含通配符,匹配將使用Go的filepath.Match規則完成。 例如:

複製代碼

是絕對路徑,或相對於WORKDIR的路徑,源將在目標容器中複製到該路徑中。

ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/         # adds "test" to /absoluteDir/
複製代碼

添加包含特殊字符(例如[和])的文件或目錄時,須要按照Golang規則轉義這些路徑,以防止它們被視爲匹配模式。 例如,要添加名爲arr [0] .txt的文件,請使用如下命令:

ADD arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/
複製代碼

全部新文件和目錄都是使用UID和GID爲0建立的,除非可選的--chown標誌指定給定用戶名,組名或UID / GID組合以請求添加內容的特定全部權。 --chown標誌的格式容許用戶名和組名字符串或任意組合的直接整數UID和GID。 提供沒有組名的用戶名或沒有GID的UID將使用與GID相同的數字UID。 若是提供了用戶名或組名,則容器的根文件系統/ etc / passwd和/ etc / group文件將分別用於執行從名稱到整數UID或GID的轉換。 如下示例顯示了--chown標誌的有效定義:

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
複製代碼

若是容器根文件系統不包含/ etc / passwd或/ etc / group文件,而且在--chown標誌中使用了用戶名或組名,則構建將在ADD操做上失敗。 使用數字ID不須要查找,也不依賴於容器根文件系統內容.

在是遠程文件URL的狀況下,目標將具備600的權限。若是正在檢索的遠程文件具備HTTP Last-Modified標頭,則該標頭的時間戳將用於設置目標上的mtime 文件。 可是,與ADD期間處理的任何其餘文件同樣,mtime將不包含在肯定文件是否已更改且應更新緩存中。

ADD遵照如下規則:

  • 路徑必須位於構建的上下文中; 你不能添加../something / something,由於docker構建的第一步是將上下文目錄(和子目錄)發送到docker守護進程。
  • 若是是URL且不以尾部斜槓結尾,則從URL下載文件並將其複製到。
  • 若是是URL而且以尾部斜槓結尾,則從URL推斷文件名,並將文件下載到<dest> /<filename>。 例如,ADD http://example.com/foobar /將建立文件/foobar。 URL必須具備很是重要的路徑,以便在這種狀況下能夠發現適當的文件名(example.com將不起做用
  • 若是是目錄,則複製目錄的所有內容,包括文件系統元數據。不復制目錄自己,只複製其內容。
  • 若是是可識別的壓縮格式(identity,gzip,bzip2或xz)的本地tar存檔,則將其解壓縮爲目錄。 遠程URL中的資源不會被解壓縮。 複製或解壓縮目錄時,它與tar -x具備相同的行爲,結果是:
1.不管在目的地路徑上存在什麼,
2.源樹的內容,在逐個文件的基礎上解決了有利於「2.」的衝突。
複製代碼
  • 若是是任何其餘類型的文件,則將其與元數據一塊兒單獨複製。 在這種狀況下,若是以尾部斜槓/結尾,則將其視爲目錄,的內容將寫入 / base()。
  • 若是直接或因爲使用通配符指定了多個資源,則必須是目錄,而且必須以斜槓/結尾。
  • 若是不以尾部斜槓結束,則將其視爲常規文件,的內容將寫入。
  • 若是不存在,則會在其路徑中建立全部缺乏的目錄。

COPY

  • COPY [--chown=<user>:<group>] <src>... <dest>
  • COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chown功能僅用於Dockerfile構建linux容器.在windows容器上無效。

COPY指令從複製新文件或目錄,並將它們添加到路徑的容器的文件系統中。

能夠指定多個資源,但文件和目錄的路徑將被解釋爲相對於構建上下文的源。

每一個可能包含通配符,匹配將使用Go的filepath.Match規則完成。 例如:

COPY hom* /mydir/        # adds all files starting with "hom"
COPY hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"
複製代碼

是絕對路徑,或相對於WORKDIR的路徑,源將在目標容器中複製到該路徑中。

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/
複製代碼

複製包含特殊字符(例如[和])的文件或目錄時,須要按照Golang規則轉義這些路徑,以防止它們被視爲匹配模式。 例如,要複製名爲arr [0] .txt的文件,請使用如下命令:

COPY arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/
複製代碼

除非可選的--chown標誌指定給定用戶名,組名或UID / GID組合以請求複製內容的特定全部權,不然將使用UID和GID爲0建立全部新文件和目錄。 --chown標誌的格式容許用戶名和組名字符串或任意組合的直接整數UID和GID。 提供沒有組名的用戶名或沒有GID的UID將使用與GID相同的數字UID。 若是提供了用戶名或組名,則容器的根文件系統/ etc / passwd和/ etc / group文件將分別用於執行從名稱到整數UID或GID的轉換。 如下示例顯示了--chown標誌的有效定義:

COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
複製代碼

若是容器根文件系統不包含/ etc / passwd或/ etc / group文件,而且在--chown標誌中使用了用戶名或組名,則構建將在COPY操做上失敗。 使用數字ID不須要查找,也不依賴於容器根文件系統內容。

可選地,COPY接受一個標誌--from = <name | index>,可用於將源位置設置爲先前的構建階段(使用FROM .. AS 建立),而不是由發送的構建上下文 用戶。 該標誌還接受爲使用FROM指令啓動的全部先前構建階段分配的數字索引。 若是找不到具備指定名稱的構建階段,則嘗試使用具備相同名稱的圖像。

COPY遵照如下規則:

  • 路徑必須位於構建的上下文中; 你不能COPY ../something / something,由於docker構建的第一步是將上下文目錄(和子目錄)發送到docker守護進程。
  • 若是是目錄,則複製目錄的所有內容,包括文件系統元數據。不復制目錄自己,只複製其內容。
  • 若是是任何其餘類型的文件,則將其與元數據一塊兒單獨複製。 在這種狀況下,若是以尾部斜槓/結尾,則將其視爲目錄,的內容將寫入 / base()。
  • 若是直接或因爲使用通配符指定了多個資源,則必須是目錄,而且必須以斜槓/結尾。
  • 若是不以尾部斜槓結束,則將其視爲常規文件,的內容將寫入。
  • 若是不存在,則會在其路徑中建立全部缺乏的目錄。

儘管ADD和COPY在功能上類似,但通常來講,COPY是優選的。 那是由於它比ADD更透明。 COPY僅支持將本地文件基本複製到容器中,而ADD具備一些功能(如僅限本地的tar提取和遠程URL支持),這些功能並非很明顯。所以,ADD的最佳用途是將本地tar文件自動提取到圖像中,如ADD rootfs.tar.xz /中所示.

若是在Dockerfile中有多個步驟使用不一樣的文件,請針對每一個文件,單獨執行COPY指令,而不是放在一塊兒執行COPY,這確保了每一步構建的緩存在當前步驟使用,若是特定的文件改變了。 示例:

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
複製代碼

與放置在COPY以前相比,RUN步驟的緩存失效次數更少。 因爲圖像大小很重要,所以強烈建議不要使用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
複製代碼

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

ENTRYPOINT

  • ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
  • ENTRYPOINT command param1 param2

ENTRYPOINT容許您配置將做爲可執行文件運行的容器。 例如,如下將使用其默認內容啓動nginx,偵聽端口80: docker run -i -t --rm -p 80:80 nginx

exec形式的ENTRYPOINT中的命令行參數將會追加到docker run <image>全部元素的後面。而且會覆蓋CMD指令指定的參數。這容許將參數傳遞給入口點。 docker run <image> -d會將-d參數傳遞給入口點。 您可使用docker run --entrypoint標誌覆蓋ENTRYPOINT指令。

shell表單能夠防止使用任何CMD或run命令行參數,但缺點是ENTRYPOINT將做爲/bin/sh -c的子命令啓動,它不傳遞信號。 這意味着可執行文件不是容器的PID 1 - 而且不會收到Unix信號 - 所以您的可執行文件不會從docker stop <container>接收SIGTERM

Dockerfile文件中只有最後一個ENTRYPOINT指令會起做用.

示例

你可使用exec 形式的ENTRYPOINT來設置相對穩定的默認的命令和參數,並使用任意形式的CMD指令來設置額外的默認的可能會變化的。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
複製代碼

當你運行容器的時候,能夠發現top是惟一的進程 docker run -it --rm --name test top -H

爲了將來測試結果,你可以使用docker exec

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux
複製代碼

而且您能夠優雅地請求中止top使用docker stop test

下面的示例展現了使用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"]
複製代碼

示例2

可爲ENTRYPOINT指定一個字符串,他將在/bin/sh -c中執行,這種形式使用shell處理替換環境變量,並會忽略CMD或者docker run 命令行參數。爲了正確有效的執行docker stop,須要記住以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% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b
複製代碼

當執行docker stop命令時,可正常退出

$ /usr/bin/time docker stop test
test
real	0m 0.20s
user	0m 0.02s
sys	0m 0.04s
複製代碼

若是你忘記以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% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
Load average: 0.01 0.02 0.05 2/101 7
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
    7     1 root     R     3164   0%   0% top -b

複製代碼

能夠看到top輸出代表ENTRYPOINT指令並未做爲PID1的進程運行。 這個時候運行docker stop,容器不會正常退出,stop指令會強制發出一個SIGKILL信號在數分鐘之後。

$ docker exec -it test ps aux
PID   USER     COMMAND
    1 root     /bin/sh -c top -b cmd cmd2
    7 root     top -b
    8 root     ps aux
$ /usr/bin/time docker stop test
test
real	0m 10.19s
user	0m 0.04s
sys	0m 0.03s
複製代碼

理解CMD和ENTRYPOINT指令如何整合

這兩個指令都定義了當運行一個容器的時候,應運行什麼命令,在合同使用時需遵照如下規則:

  • Dockerfile應至少指定一個CMD或ENTRYPOINT命令。
  • 使用容器做爲可執行文件時,應定義ENTRYPOINT。
  • CMD應該用做爲ENTRYPOINT命令定義默認參數或在容器中執行ad-hoc命令的方法。
  • 使用備用參數運行容器時,將覆蓋CMD。

下表顯示了針對不一樣ENTRYPOINT / CMD組合執行的命令:

null 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

若是CMD是從基礎鏡像中定義,設置ENTRYPOINT將會重置CMD爲一個空的值,在這種狀況下,CMD須要指定一個值在該鏡像定義的時候。

ENTRYPOINT的最佳使用是用來設置鏡像的主命令。容許該映像像該命令同樣運行(而後使用CMD做爲默認標誌)。

讓咱們以一個命令行工具s3cmd的鏡像的例子開始:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]
複製代碼

如今可使用下面的命令運行來展現命令行的幫助信息: docker run s3cmd

或者使用正確的參數來執行命令: docker run s3cmd ls s3://mybucket

VOLUME

VOLUME ["/data"]

VOLUME指令使用指定的名稱建立一個掛載點,持有外部掛載的本地主機或者其餘容器的卷。其值能夠爲一個JSON數組,VOLUMN ["/var/log"],或者一個字符串,例如VOLUMN /var/log或者VOLUMN /var/log /var/db.

docker run命令使用基本映像中指定位置存在的任何數據初始化新建立的卷。 例如,請考慮如下Dockerfile片斷:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
複製代碼

此Dockerfile會生成一個映像,該映像會致使docker run在/myvol上建立新的掛載點,並將greeting文件複製到新建立的卷中。

關於指定卷的說明: 特別注意如下幾點關於Dockerfile文件中的卷

  • 基於windows容器的卷:當使用windows類型的容器時,容器中的卷需是一下中的一種:
1. 不存在或空目錄
 2. C之外的驅動器
複製代碼
  • 從Dockerfile中更改卷:若是任何構建步驟在聲明後更改卷內的數據,那麼這些更改將被丟棄。
  • JSON形式:該列表被解析爲JSON數組。 您必須用雙引號(「)而不是單引號(')括起來。
  • 主機目錄在容器運行時聲明:主機目錄(mountpoint)本質上是依賴於主機的。 這是爲了保持圖像的可移植性,由於不能保證給定的主機目錄在全部主機上均可用。 所以,您沒法從Dockerfile中安裝主機目錄。 VOLUME指令不支持指定host-dir參數。 您必須在建立或運行容器時指定安裝點。

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

USER

USER <user>[:<group>] or
USER <UID>[:<GID>]
複製代碼

USER指令用於設置用戶名或者UID,可選的用戶組或者GID.當運行鏡像的時候,以便在運行映像時以及Dockerfile中跟隨它的任何RUN,CMD和ENTRYPOINT指令時使用。

在Windows上,若是用戶不是內置賬戶,則必須先建立用戶。 這可使用做爲Dockerfile一部分調用的net user命令來完成。

FROM microsoft/windowsservercore
    # Create Windows user in the container
    RUN net user /add patrick
    # Set it for subsequent commands
    USER patrick
複製代碼

WORKDIR

WORKDIR /path/to/workdir

WORKDIR指令爲Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPY和ADD指令設置工做目錄。若是WORKDIR 目錄不存在,即便它未在任何後續Dockerfile指令中使用,也將建立它。WORKDIR指令能夠在Dockerfile中屢次使用。 若是提供了相對路徑,則它將相對於先前WORKDIR指令的路徑。 例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
複製代碼

此Dockerfile中最終pwd命令的輸出爲/a/b/c。 WORKDIR指令能夠解析先前使用ENV設置的環境變量。 您只能使用Dockerfile中顯式設置的環境變量。 例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
複製代碼

此Dockerfile中最後一個pwd命令的輸出將是/path/$DIRNAME 爲了清晰和可靠,您應該始終使用WORKDIR的絕對路徑。 此外,您應該使用WORKDIR而不是像RUN CD ...&& do-something,這些指令難以閱讀,故障排除和維護。

ONBUILD

在當前Dockerfile構建完成後執行ONBUILD命令。 ONBUILD在從當前圖像派生的任何子圖像中執行。 將ONBUILD命令視爲父Dockerfile爲子Dockerfile提供的指令。

Docker構建在子Dockerfile中的任何命令以前執行ONBUILD命令。

ONBUILD對於將從給定圖像構建的圖像很是有用。 例如,您可使用ONBUILD做爲語言堆棧映像,在Dockerfile中構建使用該語言編寫的任意用戶軟件,如Ruby的ONBUILD變體中所示。

從ONBUILD構建的圖像應該得到一個單獨的標記,例如:ruby:1.9-onbuild或ruby:2.0-onbuild。

將ADD或COPY放入ONBUILD時要當心。 若是新構建的上下文缺乏正在添加的資源,則「onbuild」映像將發生災難性故障。 如上所述,添加單獨的標記有助於經過容許Dockerfile做者作出選擇來緩解這種狀況。

相關文章
相關標籤/搜索