鏡像的定製實際上就是定製每一層所添加的配置、文件。
若是咱們能夠把每一層修改、安裝、構建、操做的命令都寫入一個腳本,用這個腳原本構建、定製鏡像,那麼以前說起的沒法重複的問題、鏡像構建透明性的問題、體積的問題就都會解決。
這個腳本就是 Dockerfile。php
Dockerfile 是一個文本文件,其內包含了一條條的 指令(Instruction),每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。前端
由於每一條指令構建一層,並且每一層構建好後,就不會再變化。爲了使鏡像儘量地小並且層次清晰,每一層都應該圍繞一個特定的目標進行構建,而且在構建結束前,要清理掉全部緩存和其餘無關的東西!node
Docker 如今最多隻能支持 127 層,儘可能讓每一條命令都完成一個完整的目標,不要每條 shell 命令都對應一個 RUN,這是至關糟糕的作法。python
在撰寫 Dockerfile 的時候,要常常提醒本身,這並非在寫 Shell 腳本,而是在定義每一層該如何構建。mysql
docker commit
用法相似 git commit
,用於將當前容器層的修改,固化成一個新的鏡像層。linux
# 1. 首先啓動了一個容器 # 2. 經過 exec 命令登入該容器作一些修改 # 3. 可使用下列命令查看容器層的具體改動 docker diff <container id> # 4. 使用 commit 命令提交容器層的改動 docker commit [選項] <容器ID或容器名> [<倉庫名>[:<標籤>]] # 模板 docker commit \ --author "Tao Wang <twang2218@gmail.com>" \ --message "修改了默認網頁" \ webserver \ nginx:v2 # 5. 使用 history 命令查看鏡像的構建歷史 docker history nginx:v2
利用 Dockerfile 定製鏡像,其實就是在基礎鏡像上啓動一個臨時容器,而後在該容器上一條條地運行 Dockerfile 內的指令。android
每跑完一個指令,就將當前的修改固化層一個新的鏡像層(這就相似在此時執行 docker commit
)。nginx
指令跑完了,一個分層的鏡像也就生成了,這時再清除掉構建用的臨時容器。git
Dockerfile 經常使用的有十多個指令:github
FROM
:指定基礎鏡像LABEL
:鏡像的一些標籤,如 maintainer/licenceUSER
:能用普通用戶,就不要用 root 來作。建議使用普通用戶來運行不須要 root 權限的服務。ENV
:設置環境變量,可用於設置 PATH 或者其餘環境變量。
--env XXX=xxx
來設置或者修改環境變量。--env
可屢次重複使用ARG
:構建時的參數,只在構建期有用。(而 ENV 就至關於運行期參數)
docker build -build-arg <varname>=<value> xxx
來修改構建參數。WORKDIR
:用於制定下一個鏡像層的工做目錄(容器內部的),類比 cd xxx
。
ADD/COPY
:都是添加文件的命令,更推薦使用 COPY,ADD 最好只用在 tar.gz/tar.xz
等文件的添加上(會自動解壓)。
RUN
:最經常使用的構建指令,會建立新的鏡像層,因此最好讓每條 RUN 命令都完成一個目標的構建,減小層數。VOLUME
:指定數據層掛載點。
VOLUME ["/data", "/var/log/"]
EXPOSE
:暴露端口。
-p xx:xx
作端口映射,才能和本機的端口綁定!ENTRYPOINT
:鏡像的「入口」,也就是啓動鏡像時會執行的命令。
ENTRYPOINT ["executable", "param1", "param2"]
docker run
命令的全部其餘參數,都會被看成 "入口"命令的參數傳入!CMD
:在不使用 ENTRYPOINT
的狀況下,它就是鏡像的默認命令。
CMD ["executable","param1","param2"]
ENTRYPOINT
的狀況下,CMD
建議設置爲 CMD ["--help"]
,而且緊跟在 ENTRYPOINT
命令以後。ONBUILD
:該指令適合用在基礎鏡像的構建中。
FROM
一個使用了 ONBUILD
指令的鏡像,會先執行該指令,而後才執行 Dockerfile 裏面的指令。須要注意的是,如今只有 RUN/ADD/COPY
這三條指令,纔會建立新的鏡像層。其餘的指令只會在構建過程當中建立臨時鏡像層,它們不會出如今最終的鏡像中。
所謂定製鏡像,那必定是以一個鏡像爲基礎,在其上進行定製。
在 Docker Hub 上有很是多的高質量的官方鏡像,有能夠直接拿來使用的服務類的鏡像,如 nginx
、redis
、mongo
、mysql
、httpd
、php
、tomcat
等;也有一些方便開發、構建、運行各類語言應用的鏡像,如 node
、openjdk
、python
、ruby
、golang
等。能夠在其中尋找一個最符合咱們最終目標的鏡像爲基礎鏡像進行定製。
若是沒有找到對應服務的鏡像,官方鏡像中還提供了一些更爲基礎的操做系統鏡像,如 ubuntu
、debian
、centos
、fedora
、alpine
等,這些操做系統的軟件庫爲咱們提供了更廣闊的擴展空間。
除了選擇現有鏡像爲基礎鏡像外,Docker 還存在一個特殊的鏡像,名爲 scratch
。這個鏡像是虛擬的概念,並不實際存在,它表示一個空白的鏡像。
FROM scratch ...
若是你以 scratch
爲基礎鏡像的話,意味着你不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在。
不以任何系統爲基礎,直接將可執行文件複製進鏡像的作法並不罕見,好比 swarm
、coreos/etcd
。對於 Linux 下靜態編譯的程序來講,並不須要有操做系統提供運行時支持,所需的一切庫都已經在可執行文件裏了,所以直接 FROM scratch
會讓鏡像體積更加小巧。使用 Go 語言 開發的應用不少會使用這種方式來製做鏡像,這也是爲何有人認爲 Go 是特別適合容器微服務架構的語言的緣由之一。
docker build --tag <image name>:tag .
中的 .
並不只僅指 Dockerfile 的路徑!
build 命令的最後一個參數,是鏡像構建上下文的路徑,這個路徑能夠是文件夾路徑,能夠是一個 tar 壓縮包,也能夠是一個 url,甚至 git 倉庫地址也是支持的。
Docker 是 Client/Server 模式的程序,build 命令會將該 [文件夾/tar 壓縮包/url] 的內容發送給 Server 端(Docker 引擎)用於構建,
所以後面構建中的 COPY/ADD
指令,只能使用上下文裏面的內容,更不支持 ../xxx
這樣的路徑。
多階段構建中,不一樣的階段使用不一樣的基礎鏡像(所以有多個 FROM),前面的階段大都是爲了生成一些須要的文件(先後端編譯等)。
在最後一個階段,使用 COPY
將須要的文件從前幾個階段生成的鏡像中 COPY 過來,這樣就獲得了一個只包含運行時的鏡像。
前端編譯基於前端相關的鏡像,後端用後端的編譯鏡像,最後放到只包含運行時的鏡像裏。
FROM golang:1.9-alpine as builder RUN apk --no-cache add git WORKDIR /go/src/github.com/go/helloworld/ RUN go get -d -v github.com/go-sql-driver/mysql COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest as prod RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/go/helloworld/app . CMD ["./app"]
一些 Jenkins 構建,可供參考/使用的 Dockerfile: