定製鏡像,是以一個鏡像爲基礎,在其上進行定製所需添加的配置、文件。html
Dockerfile
是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。node
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
複製代碼
使用 build
構建鏡像,docker build [選項] <上下文路徑/URL/->
linux
-t
參數用來指定鏡像文件的名字,後面還能夠用冒號指定標籤。若是不指定,默認的標籤就是latest。nginx
$ docker build -t nginx:v3 .
複製代碼
docker build
的工做原理:Docker在運行時分爲Docker引擎(也就是服務端守護進程)和客戶端工具。Docker的引擎提供了一組REST API ,而如docker命令這樣的客戶端工具,則是經過這組API與Docker引擎交互,從而完成各類功能。所以,雖然表面上咱們好像是在本機執行各類docker功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker引擎)完成。redis
docker build
命令構建鏡像,其實並不是在本地構建,而是在服務端,也就是 Docker 引擎中構建的。docker
當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build
命令得知這個路徑後,會將路徑下的全部內容打包,而後上傳給Docker引擎。這樣 Docker引擎收到這個上下文包後,展開就會得到構建鏡像所需的一切文件。shell
如在 Dockerfile
中這麼寫:數據庫
COPY ./package.json /app/
複製代碼
這並非要複製執行 docker build
命令所在的目錄下的 package.json,也不是複製 Dockerfile
所在目錄下的 package.json,而是複製 上下文(context)目錄下的 package.json。json
通常來講,應該會將 Dockerfile
置於一個空目錄下,或者項目根目錄下。若是該目錄下沒有所需文件,那麼應該把所需文件複製一份過來。若是目錄下有些東西確實不但願構建時傳給 Docker 引擎,那麼能夠用 .dockerignore
文件,該文件是用於剔除不須要做爲上下文傳遞給 Docker 引擎的。數組
FROM
就是指定基礎鏡像,所以一個 Dockerfile
中 FROM
是必備的指令,而且必須是第一條指令
。
FROM nginx
# 空白鏡像,不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在
FROM scratch
複製代碼
# shell格式:RUN <命令>
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
複製代碼
不要每條命令對應一個 RUN
,由於每條指令會建一層,這樣不少運行時不須要的東西,都會裝進鏡像裏面,好比編譯環境,更新的軟件包,最後鏡像有很是多層,很是臃腫。
可使用一個 RUN
的指令,並使用 &&
將所需命令串聯起來,使用 \
命令換行,變成一層。
還能夠在最後使用清理工做的命令,刪除不須要的文件。
RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
複製代碼
COPY
指令將從構建上下文目錄中 <源路徑>
的文件/目錄複製到新的一層的鏡像內的 <目標路徑>
位置
# COPY [--chown=<user>:<group>] <源路徑> <目標路徑>
COPY package.json /usr/src/app/
# --chown=<user>:<group> 選項來改變文件的所屬用戶及所屬組。
COPY --chown=55:mygroup files* /mydir/
複製代碼
<目標路徑>
能夠是容器內的絕對路徑,也能夠是相對於工做目錄的相對路徑(工做目錄能夠用 WORKDIR 指令來指定)。
使用 COPY
指令,源文件的各類元數據都會保留。好比讀、寫、執行權限、文件變動時間等。
ADD
指令和 COPY
的格式和性質基本一致。可是在 COPY
基礎上增長了一些功能。
<源路徑>
爲一個tar壓縮文件的話,壓縮格式爲gzip,bzip2以及xz的狀況下,ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑>
去。
COPY
和 ADD
指令中選擇的時候,遵循原則,全部的文件複製均使用 COPY
指令,僅在須要自動解壓縮的場合使用 ADD
。
CMD
指令是在 build
完成後,爲了給 docker run
啓動到容器時提供默認命令或參數,這些默認值能夠包含可執行的命令,也能夠只是參數(此時可執行命令就必須提早在ENTRYPOINT中指定)。
docker run
後面出現與 CMD
指定的相同命令,那麼 CMD
會被覆蓋。
一個 Dockerfile
裏只能有一個 CMD
,若是有多個,只有最後一個生效。
推薦使用 exec 格式,這類格式在解析時會被解析爲 JSON 數組,所以必定要使用雙引號,而不要使用單引號。
CMD ["可執行文件", "參數1", "參數2"...]
複製代碼
ENTRYPOINT ["executable", "param1", "param2"]
複製代碼
ENTRYPOINT
的目的和 CMD
同樣,都是容器啓動時提供默認的命令及參數。
ENTRYPOINT
在運行時也能夠替代,須要經過 docker run 的參數 --entrypoint 來指定。
當指定了 ENTRYPOINT
後,CMD
的含義就發生了改變,再也不是直接的運行其命令,而是將 CMD
的內容做爲參數傳給 ENTRYPOINT
指令。
場景一:讓鏡像變成像命令同樣使用
# Dockerfile文件
ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
# 執行Dockerfile文件
$ docker run myip -i
# 若是不實用ENTRYPOINT 而使用 CMD [ "curl", "-s", "https://ip.cn" ]
# 那麼 -i 替換了原來的CMD ,從而報錯
複製代碼
當存在 ENTRYPOINT
後,CMD
的內容將會做爲參數傳給 ENTRYPOINT
,而這裏 -i
就是新的 CMD
,所以會做爲參數傳給 curl,最終結果至關於:
$ docker run myip curl -s https://ip.cn -i
複製代碼
場景二:應用運行前的準備工做
啓動容器就是啓動主進程,有些時候,啓動主進程前,須要一些準備工做。
好比,可能但願避免使用 root 用戶去啓動服務,從而提升安全性,而在啓動服務前還須要以 root 身份執行一些必要的準備工做,最後切換到服務用戶身份啓動服務。
這種狀況下,能夠寫一個腳本,而後放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 )做爲命令,在腳本最後執行。
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
複製代碼
能夠看到其中爲了 redis 服務建立了 redis 用戶,並在最後指定了 ENTRYPOINT 爲 docker-entrypoint.sh 腳本。
該腳本的內容就是根據 CMD 的內容來判斷,若是是 redis-server 的話,則切換到 redis 用戶身份啓動服務器,不然依舊使用 root 身份執行
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"
複製代碼
設置環境變量,不管是後面的其它指令,如 RUN,仍是運行時的應用,均可以直接使用這裏定義的環境變量。
# 格式
ENV <key1>=<value1> <key2>=<value2>...
# 環境變量含有空格,須要使用雙引號
ENV VERSION=v1.0 DEBUG=on \
NAME="Happy Feet"
# 其餘命令使用須要在環境變量加上$
RUN rm "node-$VERSION-linux-x64.tar.xz"
複製代碼
ARG <參數名>[=<默認值>]
複製代碼
ARG
設置的構建環境的環境變量,在未來容器運行時是不會存在這些環境變量的。
不要使用 ARG
保存密碼之類的信息,由於 docker history 仍是能夠看到全部值的。
Dockerfile
中的 ARG
指令是定義參數名稱,以及定義其默認值。該默認值能夠在構建命令 docker build 中用 --build-arg <參數名>=<值>
來覆蓋。
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
複製代碼
容器運行時應該儘可能保持容器存儲層不發生寫操做,對於數據庫類須要保存動態數據的應用,其數據庫文件應該保存於卷(volume)中。
爲了防止運行時用戶忘記將動態文件所保存目錄掛載爲卷,在 Dockerfile
中,能夠事先指定某些目錄掛載爲匿名卷,這樣在運行時若是用戶不指定掛載,其應用也能夠正常運行,不會向容器存儲層寫入大量數據。
# 這裏的/data目錄就會在運行時自動掛載爲匿名卷。
# 任何向/data中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。
VOLUME /data
# 使用mydata這個命名卷掛載到了/data這個位置,替代了Dockerfile中定義的匿名卷的掛載配置。
docker run -d -v mydata:/data xxxx
複製代碼
EXPOSE
指令是聲明運行時容器提供服務端口,只是一個聲明,在運行時並不會由於這個聲明應用就會開啓這個端口的服務。
EXPOSE <端口1> [<端口2>...]
複製代碼
使用 docker run -P
時,會自動隨機映射 EXPOSE
的端口到宿主機的端口上。
EXPOSE
和在運行時使用 -p <宿主端口>:<容器端口>
區別:
EXPOSE
僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。WORKDIR
指令能夠來指定工做目錄(或者稱爲當前目錄),之後各層的當前目錄就被改成指定的目錄,如該目錄不存在,WORKDIR 會幫你創建目錄。
WORKDIR <工做目錄路徑>
複製代碼
例子:
RUN cd /app
RUN echo "hello" > world.txt
複製代碼
若是將這個 Dockerfile
進行構建鏡像運行後,會發現找不到 /app/world.txt 文件,或者其內容不是 hello。
緣由每個 RUN
都是啓動一個容器、執行命令、而後提交存儲層文件變動。第一層 RUN cd /app 的執行僅僅是當前進程的工做目錄變動,一個內存上的變化而已,其結果不會形成任何文件變動。而到第二層的時候,啓動的是一個全新的容器,跟第一層的容器更徹底不要緊,天然不可能繼承前一層構建過程當中的內存變化。
所以若是須要改變之後各層的工做目錄的位置,那麼應該使用 WORKDIR 指令。
USER
指令和 WORKDIR
類似,都是改變環境狀態並影響之後的層。
WORKDIR
是改變工做目錄,USER
則是改變以後層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。
USER <用戶名>[:<用戶組>]
複製代碼
LABEL
用於爲鏡像添加元數據,元數以鍵值對的形式指定
LABEL <key>=<value> <key>=<value> <key>=<value>
複製代碼
如,經過 LABEL
指定一些元數據:
LABEL version="1.0" description="這是一個Web服務器" by="IT筆錄"
# 指定鏡像做者
LABEL maintainer="itbilu.com"
複製代碼
指定後能夠經過 docker inspect
查看:
$sudo docker inspect itbilu/test
"Labels": {
"version": "1.0",
"description": "這是一個Web服務器",
"by": "IT筆錄"
},
複製代碼