什麼是 Dockerfile 呢?
Dockerfile 是一個文字文件,其中包含使用者可以在命令列上呼叫以組裝映像的所有命令。Docker 可以通過閱讀該檔案中的指令來自動構建映像。(類似於 Linux 上的 bash 指令碼,Docker 通過該指令碼構建映象)
使用 dockerfile 製作一個 nginx 映象
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile //首字母必須大寫
Dockerfile 檔案內容如下
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
這個 Dockerfile 很簡單,一共就兩行。涉及到了兩條指令,FROM 和 RUN。
FROM
功能:指定基礎映象
所謂定製映象,那一定是以一個映象為基礎,在其上進行定製。就像我們之前執行了一個 nginx 映象的容器,再進行修改一樣,基礎映象是必須指定的。而 FROM 就是指定 基礎映象,因此一個 Dockerfile 中 FROM 是必備的指令,並且必須是第一條指令。
一般使用中我們通過 Docker Hub 來查詢相關映象。如下圖所示,紅標中標識的為官方映象
除了選擇現有映象為基礎映象外,Docker 還存在一個特殊的映象,名為 scratch(該映象不能通過 docker pull 命令直接拉取)。這個映象是虛擬的概念,並不實際存在,它表示一個空白的映象。
因為本人只對 PHP 較為熟悉,沒有使用過 go,這個也不是很瞭解,就先跳過了
RUN
功能:執行命令
用來執行命令列命令的
實際使用下有兩種格式
shell 格式:
RUN <命令>
exec 格式:
RUN ["可執行檔案", "引數1", "引數2"]
Dockerfile 中每一個指令都會建立一層,RUN 也不例外。每一個 RUN 執行結束後,都會 commit 這一層的修改,構成新的映象。所以在使用中盡力減少指令。
FROM debian:stretch
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
上面的這種寫法,建立了 7 層映象。這是完全沒有意義的,而且很多執行時不需要的東西,都被裝進了映象裡,比如編譯環境、更新的軟體包等等。結果就是產生非常臃腫、非常多層的映象,不僅僅增加了構建部署的時間,也很容易出錯。我們在以後的使用應該避免。正確的寫法如下所示:
FROM debian:stretch
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
在上一個 Dockerfile 中所有的命令只有一個目的,就是編譯、安裝 redis 可執行檔案。因此沒有必要建立很多層,這只是一層的事情。因此,這裡我們僅僅使用一個 RUN 指令,並使用 && 將各個所需命令串聯起來。將之前的 7 層,簡化為了 1 層。
在撰寫 Dockerfile 的時候,要經常提醒自己,這並不是在寫 Shell 指令碼,而是在定義每一層該如何構建。
構建
命裡格式: docker build [選項] <上下文路徑/URL/->
$ docker build -t mynginx:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM nginx
---> 08393e824c32
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 1d7edd724b5f
Removing intermediate container 1d7edd724b5f
---> e29ba82c8e43
Successfully built e29ba82c8e43
Successfully tagged mynginx:v1
// 檢視剛剛建立的映象
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx v1 e29ba82c8e43 3 minutes ago 132MB
-t 表示指定映象的名稱和標籤,格式為 name:tag
最後有個點,表示構建的上下文路徑為當前目錄
其他構建方式
通過 Git repo 進行構建
使用 tar 包進行構建
常用指令介紹
COPY 複製檔案
格式:
COPY [--chown=<user>:<group>] <源路徑> <目標路徑>
(常用)COPY [--chown=<user>:<group>] ["<源路徑1>", "<目標路徑>"]
構建上下文目錄中 <源路徑> 的檔案/目錄複製到新的一層的映象內的 <目標路徑> 位置。
COPY package.json /usr/src/app/
加上 –chown=: 選項來改變檔案的所屬使用者及所屬組
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
ADD 更高階的複製
和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。
<源路徑> 可以是一個 URL。Docker 引擎會試圖去下載這個連結的檔案放到 <目標路徑> 去。下載後的檔案許可權自動設定為 600
<源路徑> 為一個 tar 壓縮檔案的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會自動解壓縮這個壓縮檔案到 <目標路徑> 去。
在 Docker 官方的 Dockerfile 最佳實踐文件 中要求,儘可能的使用 COPY,因為 COPY 的語義很明確,就是複製檔案而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。
CMD 容器啟動命令
Docker 不是虛擬機器,容器就是程式。既然是程式,那麼在啟動容器的時候,需要指定所執行的程式及引數。CMD 指令就是用於指定預設的容器主程式的啟動命令的。
格式:
shell 格式:CMD <命令>
exec 格式:CMD [“可執行檔案”, “引數1”, “引數2”…] (推薦,一定要使用雙引號)
ENV 設定環境變數
用於設定環境變數,無論是後面的其它指令,如 RUN,還是執行時的應用,都可以直接使用這裡定義的環境變數。
格式:
ENV
<key> <value>
ENV
<key1>=<value1> <key2>=<value2>...
ARG 構建引數
格式:
ARG <引數名>[=<預設值>]
與 ENV 指令一樣,都是設定環境變數。所不同的是,ARG 所設定的構建環境的環境變數,在將來容器執行時是不會存在這些環境變數的。
Dockerfile 中的 ARG 指令是定義引數名稱,以及定義其預設值。該預設值可以在構建命令 docker build 中用 --build-arg <引數名>=<值>
來覆蓋。
VOLUME 定義匿名卷
格式:
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
示例:
容器中的 /data 目錄自動掛載到匿名卷中
VOLUME /data
該指令可以在執行時被覆蓋
docker run -d -v mydata:/data xxxx
EXPOSE 暴露埠
格式:
EXPOSE <埠1> [<埠2>...]
宣告執行時容器提供服務埠,這只是一個宣告,在執行時並不會因為這個宣告就會開啟這個埠的服務。
寫入這樣的宣告有兩個好處,一個是幫助映象使用者理解這個映象服務的守護埠,以方便配置對映;另一個用處則是在執行時使用隨機埠對映時,也就是 docker run -P 時,會自動隨機對映 EXPOSE 的埠。
WORKDIR 指定工作目錄
格式:
- WORKDIR <工作目錄路徑>。
使用 WORKDIR 指令可以來指定工作目錄(或者稱為當前目錄),以後各層的當前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會幫你建立目錄。
RUN cd /app
RUN echo "hello" > world.txt
使用上面的內容構建映象後會發現根本找不到 /app/world.txt
檔案。原因其實很簡單,在 Shell 中,連續兩行是同一個程式執行環境,因此前一個命令修改的記憶體狀態,會直接影響後一個命令;而在 Dockerfile 中,這兩行 RUN 命令的執行環境根本不同,是兩個完全不同的容器。這就是對 Dockerfile 構建分層儲存的概念不瞭解所導致的錯誤。
因此如果需要改變以後各層的工作目錄的位置,那麼應該使用 WORKDIR 指令。
USER 指定當前使用者
格式:
USER <使用者名稱>[:<使用者組>]
USER 指令和 WORKDIR 相似,都是改變環境狀態並影響以後的層。WORKDIR 是改變工作目錄,USER 則是改變之後層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。當然,和 WORKDIR 一樣,USER 只是幫助你切換到指定使用者而已,這個使用者必須是事先建立好的,否則無法切換。
其他指令參考官方文件
本作品採用《CC 協議》,轉載必須註明作者和本文連結