【翻譯】Dockerfile參考

Dockerfile參考

來自docker官方網址:https://docs.docker.com/engine/reference/builder/linux

docker可以從Dockerfile中讀取指令並自動構建一個鏡像。Dockerfile是一個文本文檔,它包含用戶能夠在命令行上調用的全部命令來組裝一個鏡像。使用docker構建,用戶能夠建立一個連續執行多個命令行指令的自動化構建。nginx

這個頁面描述了你能夠在Dockerfile中使用的命令。讀完此頁後,請參閱Dockerfile最佳實踐(Dockerfile Best Practices)以得到面向tip的指南。git

前言

翻譯這篇文章前咱們先闡述一個重要的概念,那就是上下文(context):docker構建時會將上下文的全部內容(.dockerignore中指定的內容除外)發送到daemon中進行處理。那麼如何指定這個上下文呢?咱們看一個命令來更容易的理解:github

docker build -t xxxxx .

注意這行命令後面有一個.(小點),這個小點的做用不是指定dockerfile的位置,指定dockerfile的位置是用-f參數來指定的,那你覺得這個小點是幹嗎的?他就是用來指定構建鏡像的上下文的。golang

理解了以上的例子後,咱們再看看COPY這個命令的真是含義:docker

COPY ./package.json /app/

COPY是將源目標複製到鏡像中的一個命令,那麼上面這個./package.json是指什麼路徑呢?這並非要複製執行 docker build 命令所在的目錄下的 package.json,也不是複製 Dockerfile 所在目錄下的 package.json,而是複製 上下文(context) 目錄下的 package.jsonshell

用法描述

docker構建命令(docker build)從Dockerfile和上下文構建一個映像。構建的上下文是位於指定位置路徑或URL的文件集。路徑是本地文件系統上的一個目錄。URL是一個Git存儲庫位置。apache

上下文會被遞歸的處理。所以,路徑包含任何子目錄,URL包含存儲庫及其子模塊。這個例子顯示了一個使用當前目錄做爲上下文的構建命令:json

$ docker build .
Sending build context to Docker daemon  6.51 MB
...
View Code

構建由Docker守護進程運行,而不是由CLI運行。構建過程要作的第一件事是將整個上下文(遞歸地)發送給守護進程。在大多數狀況下,最好從一個空目錄做爲上下文開始,並將Dockerfile保存在該目錄中。而且只添加構建Dockerfile所需的文件。ubuntu

警告:不要使用根目錄/做爲路徑,由於它會致使構建將硬盤驅動器的所有內容傳輸到Docker守護進程。

要在構建上下文中使用文件,Dockerfile引用一條指令中指定的文件,例如一條COPY指令。要提升構建的性能,能夠經過向上下文目錄添加.dockerignore文件來排除文件和目錄。有關如何建立.dockerignore文件的信息,請參閱此頁上的文檔。 create a .dockerignore file

一般,Dockerfile的文件名稱默認就是Dockerfile(首字母D大寫),位於上下文的根目錄中。在docker build中使用-f標誌指向文件系統中任何位置的Dockerfile。

docker build -f /path/to/a/Dockerfile .

您能夠指定一個存儲庫(repository)和用於保存新映像的標記(tag),這會在鏡像構建成功以後應用:

 docker build -t shykes/myapp .

要在構建以後將映像標記到多個存儲庫中,請在運行構建命令時添加多個-t參數:

docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

在Docker守護進程運行Dockerfile中的指令以前,它對Dockerfile進行初步驗證,若是語法不正確,則返回一個錯誤:

$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD

Docker守護進程逐個運行Dockerfile中的指令,若是須要,將每一個指令的結果提交到一個新鏡像中,最後輸出新鏡像的ID。Docker守護進程將自動清理您發送的上下文。

注意,每條指令都是獨立運行的,而且會建立一個新鏡像——所以,運行cd /tmp不會對接下來的指令產生任何影響。

只要有可能,Docker將重用中間鏡像(緩存),以顯著加快Docker的構建過程。這由控制檯輸出中的Using cache消息表示。(有關更多信息,請參閱Dockerfile最佳實踐指南中的Build cache部分 Build cache section):

$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
 ---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
 ---> Using cache
---> 2a1c91448f5f
Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
 ---> Using cache
---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
 ---> Using cache
---> 7ea8aef582cc
Successfully built 7ea8aef582cc

構建緩存僅用於具備本地父鏈(parent chain)的映像。這意味着這些映像是由之前的構建建立的,或者整個映像鏈是用docker加載的。若是但願使用特定映像的構建緩存,可使用--cache-from選項指定它。使用--cache-from指定的鏡像不須要父鏈,能夠從其餘倉庫(registries)獲取。

完成構建以後,就能夠考慮瀏覽Pushing a repository to its registry

構建工具BuildKit

 

從18.09版本開始,Docker支持一個新的moby/buildkit項目提供的後端來執行構建(build)。與舊的實現相比,BuildKit後端提供了許多優勢。例如,BuildKit能夠:

  • 檢測並跳過未使用的構建階段(stage)

  • 並行化構建獨立的構建階段

  • 在構建之間只會增量地傳輸構建上下文中更改的文件

  • 檢測並跳過在構建上下文中傳輸未使用的文件

  • 使用帶有許多新特性的外部Dockerfile實現

  • 避免API其他部分的反作用(中間鏡像和容器)

  • 爲自動修剪(prune)設置構建緩存的優先級

要使用BuildKit後端,您須要在調用docker構建以前在CLI上設置一個環境變量DOCKER_BUILDKIT=1。

要了解基於BuildKit的構建可用的實驗性Dockerfile語法,請參考BuildKit存儲庫中的文檔: refer to the documentation in the BuildKit repository.

格式

下面是Dockerfile的格式:

# 註釋
指令 參數

指令不區分大小寫。可是,習慣上它們是大寫的,以便更容易地將它們與參數區分開。

Docker按順序在Dockerfile中運行指令。Dockerfile必須以「FROM」指令開頭。它可能會在解析器指令、註釋和全局做用域的參數以後。FROM指令指定要從中構建的父映像。FROM以前可能只有一個或多個ARG指令,它們聲明在Dockerfile的FROM行中使用的參數。

Docker將以#開頭的行做爲註釋,行中任何位置的#標記都被視爲註釋,除非該行是有效的解析器指令。這容許這樣的語句:

# Comment
RUN echo 'we are running some # of cool things'

註釋中不支持行延續字符。

解析器指令

解析器指令是可選的,而且影響Dockerfile中後續行的處理方式。解析器指令不向構建中添加層,也不會顯示爲構建步驟。解析器指令被編寫爲形式# directive=value中的一種特殊類型的註釋。單個指令只能使用一次。

一旦一個註釋,空的行和構建指令被執行,Docker就再也不尋找解析器指令。相反,它將任何格式化爲解析器指令的內容視爲註釋,而且不嘗試驗證它是否多是解析器指令。所以,全部解析器指令必須位於Dockerfile的最頂層。

解析器指令不區分大小寫。可是,習慣上它們都是小寫的。約定還包括任何解析器指令後面的空行。解析器指令不支持行延續字符。

 

因爲這些規則,下面的例子都是無效的:

  • 因爲行延續無效:

# direc \
tive=value
  • 無效,由於出現兩次:

# directive=value1
# directive=value2

FROM ImageName
  • 因爲出如今構建指令以後而被視爲註釋:
FROM ImageName
# directive=value
  • 因爲出如今非解析器指令的註釋以後,因此被視爲註釋:
# About my dockerfile
# directive=value
FROM ImageName
  • 未知指令將被視爲註釋,由於它不被識別。此外,因爲出如今註釋以後,已知的指令被視爲註釋,而註釋不是解析器指令。
# unknowndirective=value
# knowndirective=value
  • 解析器指令中容許非斷行空白。所以,下列各行均被同等對待:
#directive=value
# directive =value
#    directive= value
# directive = value
#      dIrEcTiVe=value
  • 支持如下解析器指令:
  1. syntax
  2.  escape

syntax

格式:

# syntax=[remote image reference]
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...

只有在使用BuildKit後端時該指令纔有用。

syntax指令定義用於構建當前Dockerfile的構建器的位置。BuildKit後端容許無縫地使用構建器的外部實現,這些構建器以Docker鏡像的形式分發,並在容器沙箱環境中執行。

自定義Dockerfile的實現容許你:

  • 在不用更新後臺守護程序的狀況下自動的獲取錯誤修正
  • 確保全部用戶使用相同的實現來構建Dockerfile
  • 在不升級後臺守護程序的狀況下獲取最新的功能
  • 嘗試新的實驗性或第三方特性

官方的版本

escape轉義符

environment replacement環境的替換

.dockerignore文件

.dockerignore文件的主旨是要「排除」一些髮網daemon的文件,可是裏面也有一些邏輯是不排除的,你要先明確這一點。

在docker CLI將上下文發送到docker守護進程以前,它在上下文的根目錄中查找一個名爲.dockerignore的文件。若是該文件存在,CLI將修改上下文以排除與其中模式匹配的文件和目錄。這有助於避免沒必要要地向守護進程發送大型或敏感的文件和目錄,並可能使用ADD或COPY將它們添加到映像中。

CLI將.dockerignore文件解釋爲一個新行分隔的模式列表,相似於Unix shell的文件globs。爲了進行匹配,上下文的根被認爲是工做目錄和根目錄。例如,模式/foo/bar和foo/bar都排除了路徑的foo子目錄或位於URL的git存儲庫根目錄中名爲bar的文件或目錄。二者都不排斥其餘任何東西。

若是.dockerignore文件中的一行以第1列中的#開始,那麼這一行將被視爲註釋,並在CLI解釋以前被忽略。

# comment
*/temp*
*/*/temp*
temp?

該文件致使如下構建行爲:

規則 行爲
# comment 忽略掉.
*/temp* 排除在根目錄的任何直接子目錄中名稱以temp開頭的文件和目錄。例如,排除了普通文件/somedir/temporary.txt,以及/somedir/temp目錄。
*/*/temp* 從根目錄下兩層的任何子目錄中排除以temp開頭的文件和目錄。例如,/somedir/subdir/temporary.txt被排除。
temp? 排除根目錄中名稱爲temp的單字符擴展名的文件和目錄。例如,排除/tempa和/tempb。

使用Go語言的filepath的Match方法來匹配規則。預處理步驟刪除開頭和結尾的空白並使用Go語言的filepath.Clean方法消除.和. .預處理後爲空的行將被忽略。

依賴於filepath.Match提供的規則,Docker使用一個特殊的通配符**來匹配任意數量的目錄,好比:**/*.go這個會匹配全部目錄中.go擴展名的文件,包括構建的根目錄。

以!(感嘆號)開始的行可用於排除的例外狀況。下面是一個例子,.dockerignore文件使用這個機制:

  *.md
    !README.md

除了README.md外全部的markdown文件都被排除在上下文外了。

放置!的地方影響以下行爲:.dockerignore中與特定文件匹配的最後一行決定它是被包含仍是被排除。考慮下面的例子:

 *.md
 !README*.md
  README-secret.md

首先全部的markdown文件都被排除了。

在!README*.md下面還有一行 README-secret.md那麼結果就是除了這個README-secret.md的全部README開頭的markdown文件不會被排除。

在來看下面這個例子:

 *.md
    README-secret.md
    !README*.md

首先全部的markdown文件都被排除了

而後第二行README-secret.md這個能夠不寫,由於第一行已經指定了規則,它也符合第一行的規則。

第三行又將全部README開頭的markdown文件包含了進來,也就是說第二行根本不會起做用了,它指定的這個文件由於第三行的規則會被包括進去。

甚至可使用.dockerignore文件來排除Dockerfile和.dockerignore文件。這些文件仍然被髮送到守護進程,由於守護進程須要它們來完成本身的工做。可是ADD和COPY指令不會將它們複製到鏡像。

最後,你可能但願指定要在上下文中包含哪些文件,而不是要排除哪些文件。要實現這一點,將*指定爲第一個模式,而後是一個或多個模式!例外模式。

note:做爲歷史緣由你應該瞭解,.模式被忽略了。

FROM

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

FROM指令初始化一個新的構建階段,併爲後續指令設置基本鏡像。所以,一個有效的Dockerfile必須以FROM指令開始。鏡像能夠是任何有效的鏡像—從公共存儲庫(docker hub)中提取鏡像尤爲容易。

  • ARG是Dockerfile中惟一可能在前面的指令。瞭解ARG和FROM如何交互: Understand how ARG and FROM interact.。
  • FROM能夠在一個Dockerfile中出現屢次,用來建立多個鏡像或者使用一個構建階段看成另一個構建階段的依賴項。只需在每條新的FROM指令以前,經過提交來記錄最後一個鏡像ID的輸出。每一個FROM指令清除之前的指令建立的任何狀態。
  • 能夠經過將 AS name添加到FROM指令中,就是給這個構建階段指定了一個名稱,固然這是可選的。該名稱可用於後續的FROM和COPY --from=<name|index>指令,在此引用此階段構建的鏡像
  • tag或digest的值是可選的。若是省略其中任何一個,構建器默認使用latest做爲標記。若是生成器找不到tag的值,則返回一個錯誤。

理解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

在FROM以前聲明的ARG在構建階段以外,所以在FROM以後的任何指令中都不能使用它。若要使用在第一個以前聲明的ARG的默認值,請使用在構建階段內沒有值的ARG指令:

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN有兩種格式:

#shellform,命令在shell中運行,默認是Linux上的/bin/sh -c或Windows上的cmd /S /C
RUN <command> 
#execform 
RUN ["executable", "param1", "param2"]

RUN指令將在當前鏡像之上的新層中執行任何命令並提交結果。生成的提交鏡像將用於Dockerfile中的下一步。

分層RUN指令和生成提交符合Docker的核心概念,其中提交很廉價,能夠從鏡像歷史中的任何點建立容器,很像源代碼控制。

execform格式的RUN指令在避免shell字符串轉換和在一個不包含指定的可執行shell的基礎鏡像上運行RUN指令成爲可能。

shellform格式下的默認shell能夠經過SHELL命令來修改。

在shellform的格式下,可使用\(反斜槓)將單個運行指令延續到下一行。例如,考慮這兩行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

與下面這一行是等同的:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

note:使用exec form格式時,能夠將目標shell傳入,這可使你使用一個不一樣的shell,而不是‘/bin/sh’。以下例:

RUN ["/bin/bash", "-c", "echo hello"]

note:exec from格式命令會被解析成json數組,這意味着你必須使用雙引號而不是單引號來括住數組中的每個元素。

與shellform不一樣,execform不調用命令shell。這意味着不會發生正常的shell處理。例如,RUN ["echo", "$HOME"]不會對$HOME執行變量替換。若是想要shell處理,那麼要麼使用shellform,要麼直接執行shell,例如:RUN ["sh", "-c", "echo $HOME"]。當使用execform並直接執行shell時(如shellform的狀況),執行環境變量擴展的是shell,而不是docker。

在JSON格式中(execform會被看成json處理,還記得嗎),必須轉義反斜槓。這在以反斜槓爲路徑分隔符的windows系統特別相關。下面這行代碼因爲不是有效的JSON,將被視爲shellform進行處理,並以一種意外的方式失敗:運行["c:\windows\system32\tasklist。本例的正確語法是:RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN指令的緩存不會在下一次構建期間自動失效。像RUN apt-get distt -upgrade -y這樣的指令的緩存將在下一個構建過程當中重用。RUN指令的緩存能夠經過使用--no-cache標誌來失效,例如docker build --no-cache。

 

從 Dockerfile Best Practices guide 查看更多信息。

運行指令的緩存能夠經過添加指令失效。詳見下文below

關於RUN指令已提交的ISSUE

Issue 783 is about file permissions problems that can occur when using the AUFS file system. You might notice it during an attempt to rm a file, for example.

For systems that have recent aufs version (i.e., dirperm1 mount option can be set), docker will attempt to fix the issue automatically by mounting the layers with dirperm1 option. More details on dirperm1 option can be found at aufs man page

If your system doesn’t have support for dirperm1, the issue describes a workaround.

 CMD

CMD有三種格式:

#exec form, 推薦格式
CMD ["executable","param1","param2"] 
#做爲ENTRYPOINT的默認參數
CMD ["param1","param2"] 
#shell form
CMD command param1 param2 (shell form)

Dockerfile中只能存在一個CMD,若是你有多個CMD,那麼最後那個CMD命令會生效。

CMD的主要用途是爲執行容器提供缺省值。這些缺省值能夠包括可執行文件,也能夠省略可執行文件,在這種狀況下,您還必須指定一個ENTRYPOINT指令。

NOTE:若是CMD用於爲ENTRYPOINT指令提供默認參數,那麼應該使用JSON數組格式指定CMD和ENTRYPOINT指令。

NOTE:exec form格式的會被看成JSON數組來對待,這意味着你必須在數組裏面使用雙引號而不是單引號。

NOTE:exec form不會像shell form那樣會調用shell命令,這意味着不會發生普通的shell處理,好比CMD [ "echo", "$HOME" ]這條命令不會在$HOME上發生變量替換。若是想要shell處理,那麼要麼使用shell form,要麼直接執行shell,好比:CMD [ "sh", "-c", "echo $HOME" ]。當使用exec form並直接執行shell時(如shell form的狀況),執行環境變量擴展的是shell,而不是docker。

在shell或exec格式中使用時,CMD指令設置在運行鏡像時要執行的命令。

若是你使用的是shell form的格式,CMD會在/bin/sh -c裏執行:

FROM ubuntu
CMD echo "This is a test." | wc -

若是不想使用shell來運行命令,那麼必須將該命令表示爲JSON數組,並給出可執行文件的完整路徑。這種數組形式是CMD的推薦格式。任何附加參數必須單獨表示爲數組中的字符串:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

若是但願容器每次都運行相同的可執行文件,那麼應該考慮將ENTRYPOINT與CMD結合使用。查看這裏: ENTRYPOINT

若是用戶指定docker運行的參數,那麼他們將覆蓋CMD中指定的默認值。

不要將RUN與CMD混淆。RUN實際運行命令並提交結果;CMD在構建時不執行任何操做,可是爲鏡像指定想要的命令。

LABEL

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令將元數據添加到圖像中。LABEL是鍵值對。要在LABEL的值中包含空格,可使用引號和反斜槓,就像在命令行解析中同樣。一些用法示例:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

一個鏡像能夠有多個標籤。能夠在一行中指定多個標籤。在Docker 1.10以前,這下降了最終鏡像的大小,但如今再也不是這樣了。你可能仍然選擇以如下兩種方式在單個LABEL指令中指定多個標籤:

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

FROM行中指定的基礎鏡像或父鏡像中的LABEL會被你的鏡像繼承,若是一個LABEL已經存在可是有不一樣的值,那麼最近使用的LABEL的值會覆蓋以前聲明的那個值。

使用docker inspect命令來查看LABEL:

"Labels": {
    "com.example.vendor": "ACME Incorporated"
    "com.example.label-with-value": "foo",
    "version": "1.0",
    "description": "This text illustrates that label-values can span multiple lines.",
    "multi.label1": "value1",
    "multi.label2": "value2",
    "other": "value3"
},

MAINTAINER (已廢棄)

LABEL指令是這個指令的替代。

不描述這個指令了,不推薦用,若是你有新項目,那麼使用LABEL來代替它,沒有必要在使用這個指令。

EXPOSE

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

EXPOSE指令通知Docker容器在運行時監聽指定的網絡端口。能夠指定端口監聽TCP仍是UDP,若是沒有指定協議,則默認爲TCP。

EXPOSE指令實際上並不發佈端口。它的功能相似於構建鏡像的人員和運行容器的人員之間的一種文檔類型,關於要發佈哪些端口。也就是說這個命令就是一個提示做用。要在運行容器時實際發佈端口,可使用docker run上的-p標誌來發布和映射一個或多個端口,或者使用-p標誌來發布全部公開的端口並將它們映射到高階端口。

默認狀況下EXPOST假設使用的TCP協議,你能夠指定其餘協議:

EXPOSE 80/udp

要在相同端口上暴露兩個端口,能夠指定兩行:

EXPOSE 80/tcp
EXPOSE 80/udp

在這種狀況下,若是在docker run中使用-P,那麼TCP和UDP端口將分別公開一次。請記住,-P在主機上使用臨時的高階主機端口,所以TCP和UDP的端口將不相同。

不管EXPOSE作了怎麼樣的設置,你均可以在運行鏡像時經過-p標誌來覆蓋他們:

docker run -p 80:80/tcp -p 80:80/udp ...

要在主機系統上設置端口重定向,請參閱 using the -P flag.。docker network命令支持在容器之間建立通訊網絡,而不須要公開或發佈特定的端口,由於鏈接到docker network的容器能夠經過任何端口相互通訊。有關詳細信息,請參閱 overview of this feature

ENV

ENV <key> <value>
ENV <key>=<value> ...

ENV指令將環境變量<key>設置爲值<value>。此值將位於構建階段中全部後續指令的環境中,也能夠內聯地替換( 詳見上文環境替換)許多指令。

ENV指令有兩種形式。第一種形式是ENV <key> <value>,它將把單個變量設置爲一個值。第一個空格以後的整個字符串將被視爲<value>—包括空白字符。該值將被解釋爲其餘環境變量,所以若是沒有轉義,引號字符將被刪除。

第二種形式,ENV <key>=<value>…,容許同時設置多個變量。注意,第二種形式在語法中使用了等號(=),而第一種形式沒有。與命令行解析同樣,引號和反斜槓可用於在值中包含空格。

例子:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

上面這兩個例子會在最終的鏡像中產生同樣的結果。

當從結果鏡像運行容器時,使用ENV設置的環境變量將保持不變。可使用docker inspect查看這些值,並使用docker run --env <key>=<value>更改它們。

NOTE:環境持久性可能會致使意想不到的反作用。例如,設置ENV DEBIAN_FRONTEND noninteractive可能會使基於debian的圖像上的apt-get使用者感到困惑。要設置單個命令的值,使用RUN <key>=<value> <command>。

ADD

ADD有兩種格式:

ADD [--chown=<user>:<group>] <src>... <dest>
#包含空格的路徑須要此格式 ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

NOTE--chown特性只支持用於構建Linux容器的Dockerfiles,不能用於Windows容器。因爲用戶和組全部權概念不能在Linux和Windows之間轉換,所以使用/etc/passwd和/etc/group將用戶名和組名轉換爲IDs,這限制了該特性只能用於基於Linux os的容器。

ADD指令從<src>複製新的文件、目錄或遠程文件url,並將它們添加到路徑<dest>的鏡像文件系統中。

能夠指定多個<src>資源,可是若是它們是文件或目錄,它們的路徑將被解釋爲相對於構建上下文的源。

每個<src>能夠指定通配符,這個通配符會被GO語言的 filepath.Match 提供的邏輯進行處理。好比:

ADD hom* /mydir/        # 添加全部以"hom"開頭的文件
ADD hom?.txt /mydir/    # ? 會被一個單獨的字符代替, 好比, "home.txt"

<dest> 表示一個絕對路徑,獲取是相對於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標誌指定了一個給定的用戶名、groupname或UID/GID組合來請求添加內容的特定全部權--chown標誌的格式容許在任何組合中使用用戶名和groupname字符串或直接整數UID和GID。提供沒有groupname的用戶名或沒有GID的UID將使用與GID相同的數字UID。若是提供了用戶名或groupname,則將使用容器的根文件系統/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標誌中使用了用戶名或組名,那麼在添加操做時構建將失敗。使用數字id不須要查找,也不依賴於容器根文件系統的內容。

在<src>是遠程文件URL的狀況下,目的地的權限爲600。若是正在檢索的遠程文件具備HTTP Last-Modified標頭,則來自該標頭的時間戳將用於設置目標文件的時間。可是,與添加過程當中處理的任何其餘文件同樣,在肯定文件是否更改以及是否應該更新緩存時,並不包括mtime。

 

NOTE:若是經過STDIN (docker build - < somefile)傳遞Dockerfile進行構建,則沒有構建上下文,所以Dockerfile只能包含基於URL的ADD指令。您還能夠經過STDIN (docker build - < archive.tar.gz)傳遞壓縮的歸檔文件,歸檔文件根目錄下的Dockerfile和歸檔文件的其他部分將用做構建的上下文。注:STDIN是標準輸入的意思。

NOTE:若是你的URL文件使用身份驗證進行保護,那麼您將須要在容器中使用RUN wget、RUN curl或其餘工具,由於ADD指令不支持身份驗證。

NOTE:若是<src>的內容發生了變化,那麼第一個遇到的ADD指令將使Dockerfile中的全部後續指令的緩存無效。這包括使RUN指令的緩存無效。有關更多信息,請參閱Dockerfile最佳實踐指南:Dockerfile Best Practices guide 。

ADD指令遵循下列規則:

  • <src>路徑必須在構建的上下文中;你不能ADD一個 ../something/something/,由於docker構建的第一步是將上下文目錄(和子目錄)發送到docker守護進程而不是在本地執行。
  • 若是<src>是一個URL,而且<dest>沒有以斜槓結尾,則從該URL下載一個文件並複製到<dest>。
  • 若是<src>是一個URL,而且<dest>以一個斜槓結尾,那麼從URL推斷文件名,文件被下載到<dest>/<filename>。例如,ADD http://example.com/foobar  /(注意空格)將建立文件/foobar。URL必須有一個重要的路徑,這樣才能在這種狀況下找到一個合適的文件名(http://example.com將不起做用)。
  • 若是<src>是一個目錄,則複製目錄的所有內容,包括文件系統元數據。

NOTE:目錄自己不是複製的,只是它的內容。

  • 若是<src>是一個以可識別的壓縮格式(identity、gzip、bzip2或xz)進行壓縮的本地tar存檔,則將其做爲目錄解壓縮。來自遠程url的資源沒有解壓縮。當一個目錄被複制或解壓縮時,它的行爲與tar -x相同,結果是:

 

  1. 不管目標路徑上存在什麼

  2. The contents of the source tree, with conflicts resolved in favor of 「2.」 on a file-by-file basis.(不知道什麼狗屁意思,之後再來處理吧)
  • NOTE:一個文件是否被識別爲一種可識別的壓縮格式徹底取決於文件的內容,而不是文件名。例如,若是一個空文件以.tar.gz結尾,那麼它將不會被識別爲壓縮文件,也不會生成任何類型的解壓縮錯誤消息,而只是簡單地將該文件複製到目的地。
  • 若是<src>是任何其餘類型的文件,它將與元數據一塊兒單獨複製。在這種狀況下,若是<dest>以一個斜槓/結尾,它將被認爲是一個目錄,<src>的內容將寫在<dest>/base(<src>)。
  • 若是直接指定了多個<src>資源,或者因爲使用了通配符,那麼<dest>必須是一個目錄,而且必須以斜槓/結尾。
  • 若是<dest>沒有以斜槓結尾,它將被視爲一個常規文件,<src>的內容將被寫在<dest>。
  • 若是<dest>不存在,它將與路徑中全部缺失的目錄一塊兒建立。

COPY

COPY有兩種格式:

COPY [--chown=<user>:<group>] <src>... <dest>
#該格式是用於路徑中有空格的狀況
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

COPY指令基本與ADD指令差很少,差異就在ADD指令會在識別到壓縮文件後對文件進行解壓縮,COPY不會。COPY的內容就不翻譯了,之後有變化再說。

ENTRYPOINT

ENTRYPOINT有兩種格式:

#(exec form, 推薦的格式)
ENTRYPOINT ["executable", "param1", "param2"] 
#(shell form)
ENTRYPOINT command param1 param2 

ENTRYPOINT容許你配置一個可看成可執行程序運行的容器。

例如,下面的命令啓動一個nginx容器,使用默認的內容,而且監聽80端口:

docker run -i -t --rm -p 80:80 nginx

docker run <image>的命令行參數將追加到以exec form格式執行的ENTRYPOINT指令中的全部元素以後,並將覆蓋使用CMD指定的全部元素。這容許參數被傳遞到入口點,也就是說,docker run <image> -d將把-d參數傳遞給入口點。你可使用docker run - ENTRYPOINT標誌覆蓋ENTRYPOINT指令。

shell form格式的能夠防止使用任何CMD或run命令行參數,可是有一個缺點,即你的ENTRYPOINT 將做爲/bin/sh -c的子命令啓動,該命令不傳遞信號。這意味着可執行文件不是容器的PID 1,也不會收到Unix信號,因此你的可執行文件不會收到來自docker stop <container>的終止信號。

只有最後的ENTRYPOINT 會生效。

Exec form格式的ENTRYPOINT 例子

您可使用ENTRYPOINT的exec form格式來設置至關穩定的默認命令和參數,而後使用CMD的任何一種形式來設置更可能更改的其餘默認值。

FROM ubuntu
ENTRYPOINT ["top", "-b"]#相對穩定的命令和參數
CMD ["-c"]#可能會更改的參數

 當你是用上面的Dockfile運行容器,你能夠看到top是惟一的進程:

$ docker run -it --rm --name test  top -H
top - 08:25:00 up  7: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 USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

要進一步檢查結果,可使用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

你可使用docker stop測試優雅地請求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 --rm -p 80:80 --name test apache來運行這個鏡像,你能夠用docker exec或docker top來檢查容器的進程,而後讓腳本中止apache:

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux
$ docker top test
PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real    0m 0.27s
user    0m 0.03s
sys    0m 0.03s

NOTE:您可使用--ENTRYPOINT覆蓋ENTRYPOINT設置,但這隻能將二進制文件設置爲exec(不使用sh -c)。

NOTE:exec form的格式的命令會被解析爲json數組,意味着你必須將數組的元素用雙引號括住而不是單引號。

NOTE:與shell form不一樣,exec form不調用命令shell。這意味着不會發生正常的shell處理。例如,ENTRYPOINT ["echo", "$HOME"]不會對$HOME執行變量替換。若是您想要shell處理,那麼要麼使用shell form,要麼直接執行shell,例如:ENTRYPOINT ["sh", "-c", "echo $HOME"]。當使用exec form並直接執行shell時(如shell form的狀況),執行環境變量擴展的是shell,而不是docker。

Shell格式的ENTRYPOINT的例子

之後再說

理解CMD和ENTRYPOINT是如何交互的

CMD和ENTRYPOINT指令都定義了在運行容器時執行什麼命令。這裏有一些規則描述他們的協做關係。

  1. dockerfile應該至少指定一個CMD或者ENTRYPOINT指令
  2. 在將容器用做可執行文件時,應該定義ENTRYPOINT。
  3. CMD應該用做定義ENTRYPOINT命令的默認參數或在容器中執行特別命令的方法。
  4. 當運行帶有可選參數的容器時,CMD將被覆蓋。

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

  無ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [「exec_entry」, 「p1_entry」]
無CMD 錯誤,不被容許這樣作 /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

NOTE:若是CMD是從基礎鏡像定義的,那麼設置ENTRYPOINT將把CMD重置爲空值。在這種狀況下,必須在當前鏡像中定義CMD才能得到一個值。

VOLUME

VOLUME ["/data"]

VOLUME指令使用指定的名稱建立一個掛載點,並將其標記爲持有來自本機主機或其餘容器的外部掛載卷。該值能夠是一個JSON數組,VOLUME ["/var/log/"],也能夠是一個有多個參數的普通字符串,好比VOLUME /var/log或VOLUME /var/log/ var/db。有關經過Docker客戶端的更多信息/示例和安裝說明,請參閱經過卷文檔共享目錄: Share Directories via Volumes.。

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

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

這個Dockerfile會產生一個鏡像,這個鏡像會致使docker運行時在/myvol處建立一個新的掛載點,並將greeting文件複製到新建立的卷中。

 

關於指定VOLUME的說明

關於Dockerfile中的卷,請記住如下幾點。

  • 基於windows的容器上的卷:當使用基於windows的容器時,容器內的卷的目標必須是:
    • 不存在的或空目錄
    • 一個C盤之外的驅動器
  • 從Dockerfile中更改卷:若是任何構建步驟在聲明卷以後更改了其中的數據,那麼這些更改將被丟棄。
  • JSON格式:該列表被解析爲JSON數組。必須用雙引號(")而不是單引號(')括起單詞。
  • 主機目錄在容器運行時聲明:主機目錄(掛載點)本質上依賴於主機。這是爲了保持鏡像的可移植性,由於不能保證給定的主機目錄在全部主機上均可用。因爲這個緣由,您不能從Dockerfile中掛載主機目錄。VOLUME指令不支持指定主機目錄參數。在建立或運行容器時,必須指定掛載點。

USER

後續翻譯

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

相關文章
相關標籤/搜索