Docker
鏡像
最小的鏡像
hello-world 是 Docker 官方提供的一個鏡像,通常用來驗證 Docker 是否安裝成功。
先通過 docker pull 從 Docker Hub 下載它。
[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:a0dfb02aac212703bfcb339d77d47ec32c8706ff250850ecc0e19c8737b18567
Status: Image is up to date for hello-world:latest
docker.io/library/hello-world:latest
用 docker images 命令查看鏡像的信息。
[root@docker ~]# docker images hello-world
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 3 weeks ago 10.1kB
通過 docker run 運行。
[root@docker ~]# docker run hello-worldHello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/
base鏡像
base 鏡像有兩層含義:
-
不依賴其他鏡像,從 scratch 構建。
-
其他鏡像可以之為基礎進行擴展。
所以,能稱作 base 鏡像的通常都是各種 Linux 發行版的 Docker 鏡像,比如 Ubuntu, Debian, CentOS 等。
以 CentOS 為例考察 base 鏡像包含哪些內容。
下載鏡像:
docker pull centos:7
[root@docker ~]# docker pull centos:7
7: Pulling from library/centos
2d473b07cdd5: Pull complete
Digest: sha256:be65f488b7764ad3638f236b7b515b3678369a5124c47b8d32916d6487418ea4
Status: Downloaded newer image for centos:7
docker.io/library/centos:7
查看鏡像信息:
[root@docker ~]# docker images centos:7
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7 eeb6ee3f44bd 3 years ago 204MB
Linux 操作系統由內核空間和用戶空間組成。如下圖所示:
rootfs
內核空間是 kernel,Linux 剛啟動時會加載 bootfs 文件系統,之后 bootfs 會被卸載掉。
用戶空間的文件系統是 rootfs,包含我們熟悉的 /dev, /proc, /bin 等目錄。
對于 base 鏡像來說,底層直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而對于一個精簡的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序庫就可以了。相比其他 Linux 發行版,CentOS 的 rootfs 已經算臃腫的了,alpine 還不到 10MB。
我們平時安裝的 CentOS 除了 rootfs 還會選裝很多軟件、服務、圖形桌面等,需要好幾個 GB 就不足為 奇了。
base 鏡像提供的是最小安裝的 Linux 發行版。
支持運行多種 Linux OS
不同 Linux 發行版的區別主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服務,apt 管理軟件包;而 CentOS 7 使用 systemd 和 yum。這 些都是用戶空間上的區別,Linux kernel 差別不大。
所以 Docker 可以同時支持多種 Linux 鏡像,模擬出多種操作系統環境。
上圖 Debian 和 BusyBox(一種嵌入式 Linux)上層提供各自的 rootfs,底層共用 Docker Host 的 kernel。
額外1
- base 鏡像只是在用戶空間與發行版一致,kernel 版本與發型版是不同的。 例如 ubuntu使用 3.x.x 的 kernel,如果 Docker Host 是 CentOS Stream 8(比如我們的實驗環 境),那么在 CentOS 容器中使用的實際是是 Host 4.18.0 的 kernel。
[root@docker ~]# uname -r
4.18.0-553.6.1.el8.x86_64
#Host os kernel 為 4.18.0
啟動一個ubuntu,ubuntu內核正常與host os(centos stream 8)不一致
[root@docker ~]# docker run -it ubuntu
root@65a155bed367:/# uname -r
4.18.0-553.6.1.el8.x86_64
啟動一個centos:7,centos:7內核正常為3.10
[root@docker ~]# docker run -it centos:7
[root@1068e82c836b /]# uname -r
4.18.0-553.6.1.el8.x86_64
- 容器只能使用 Host 的 kernel,并且不能修改。 所有容器都共用 host 的 kernel,在容器中沒辦法對 kernel 升級。如果容器對 kernel 版本有要求 (比如應用只能在某個 kernel 版本下運行),則不建議用容器,這種場景虛擬機可能更合適。
額外2
# 查看內核版本
[root@docker ~]# uname -r
4.18.0-553.6.1.el8.x86_64# 查看操作系統版本
[root@docker ~]# cat /etc/os-release
NAME="CentOS Stream"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Stream 8"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://centos.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream"
鏡像的分層結構
Docker 支持通過擴展現有鏡像,創建新的鏡像。
構建過程如下圖所示:
可寫的容器層
當容器啟動時,一個新的可寫層被加載到鏡像的頂部。 這一層通常被稱作“容器層”,“容器層”之下的都叫“鏡像層”。
所有對容器的改動 - 無論添加、刪除、還是修改文件都只會發生在容器層中。
只有容器層是可寫的,容器層下面的所有鏡像層都是只讀的。
對容器增刪改差操作如下:
操作 | 具體執行 |
---|---|
創建 文件 | 新文件只能被添加在容器層中。 |
刪除 文件 | 依據容器分層結構由上往下依次查找。找到后,在容器層中記錄該刪除操作。 具體實現 是,UnionFS會在容器層創建一個”whiteout”文件,將被刪除的文件“遮擋”起來。 |
修改 文件 | 依據容器分層結構由上往下依次查找。找到后,將鏡像層中的數據復制到容器層進行修 改,修改后的數據保存在容器層中。(copy-on-write) |
讀取 文件 | 依據容器分層結構由上往下依次查找。 |
只有當需要修改時才復制一份數據,這種特性被稱作 Copy-on-Write。可見,容器層保存的是鏡像變化 的部分,不會對鏡像本身進行任何修改。
構建鏡像
Docker 提供了兩種構建鏡像的方法:
-
docker commit 命令
-
Dockerfile 構建文件
docker commit
docker commit 命令是創建新鏡像最直觀的方法,其過程包含三個步驟:
運行容器–修改容器 --將容器保存為新的鏡像
示例:
運行容器
[root@docker ~]# docker run -it ubuntu
root@dfcf8c139cb1:/#
安裝 vim
本身不帶vim
root@dfcf8c139cb1:/# vim
bash: vim: command not found
root@dfcf8c139cb1:/# apt-get update
Get:1 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]
Get:2 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:3 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [1408 kB]
Get:4 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:5 http://archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
Get:6 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages [19.3 MB]
Get:7 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [2159 kB]
Get:8 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Packages [23.0 kB]
Get:9 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Packages [1135 kB]
Get:10 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages [1808 kB]
Get:11 http://archive.ubuntu.com/ubuntu noble/restricted amd64 Packages [117 kB]
Get:12 http://archive.ubuntu.com/ubuntu noble/multiverse amd64 Packages [331 kB]
Get:13 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [1760 kB]
Get:14 http://archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [2269 kB]
Get:15 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [1918 kB]
Get:16 http://archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Packages [45.2 kB]
Get:17 http://archive.ubuntu.com/ubuntu noble-backports/main amd64 Packages [48.8 kB]
Get:18 http://archive.ubuntu.com/ubuntu noble-backports/universe amd64 Packages [35.6 kB]
Fetched 33.0 MB in 37s (896 kB/s)
Reading package lists... Doneroot@dfcf8c139cb1:/# apt-get install -y vim
5
69
保存為新鏡像
一定要新開一個窗口查看!!!
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dfcf8c139cb1 ubuntu "/bin/bash" 3 minutes ago Up 3 minutes distracted_lehmann
dfcf8c139cb1
是新創建容器的ID
distracted_lehmann
是 Docker 為我們的容器隨機分配的名字。
將容器保存為鏡像
[root@docker ~]# docker commit distracted_lehmann ubuntu-with-vim
sha256:ba23f0e4a93f623c832ff90087f23979680e1b42e5c381b49978c29849ee9c04
新鏡像的命名為ubuntu-with-vim
查看新鏡像的屬性
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest ba23f0e4a93f 58 seconds ago 203MB
ubuntu latest 802541663949 2 weeks ago 78.1MB
hello-world latest 1b44b5a3e06a 3 weeks ago 10.1kB
httpd latest 199e3a035264 3 weeks ago 117MB
centos 7 eeb6ee3f44bd 3 years ago 204MB
使用新鏡像
[root@docker ~]# docker run -it ubuntu-with-vim
root@03907d15a3e0:/# which vim
/usr/bin/vim
root@03907d15a3e0:/# vim file1
root@03907d15a3e0:/# cat file1
123
Dockerfile構建鏡像
Dockerfile 是一個文本文件,記錄了鏡像構建的所有步驟。
Dockerfile內容基礎知識:
- 每條保留字指令都必須為大寫字母且后面要跟隨至少一個參數
- 指令按照從上到下,順序執行
- #表示注釋
- 每條指令都會創建一個新的鏡像層并對鏡像進行提交
常用參數:
docker build -f [Dockerfile路徑] [構建上下文路徑]
參數 | 作用 |
---|---|
-f 或 --file | 標志符,聲明要使用自定義 Dockerfile |
[Dockerfile路徑] | 絕對路徑或相對于構建上下文的路徑(如 subdir/Dockerfile.dev ) |
[構建上下文路徑] | Docker 打包發送給守護進程的目錄(通常用 . 表示當前目錄) |
第一個 Dockerfile
用 Dockerfile 創建上節的 ubuntu-with-vim
[root@docker ~]# cd /root/
[root@docker ~]# ls
anaconda-ks.cfg
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .
查看鏡像分層結構 ubuntu-with-vim-dockerfile 是通過在 base 鏡像的頂部添加一個新的鏡像層而得到的。
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile latest e22fcd50ee9e About a minute ago 203MB
ubuntu-with-vim latest ba23f0e4a93f 52 minutes ago 203MB
ubuntu latest 802541663949 2 weeks ago 78.1MB
hello-world latest 1b44b5a3e06a 3 weeks ago 10.1kB
httpd latest 199e3a035264 3 weeks ago 117MB
centos 7 eeb6ee3f44bd 3 years ago 204MB[root@docker ~]# docker history ubuntu
IMAGE CREATED CREATED BY SIZE COMMENT
802541663949 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:e67907c77897d2719… 78.1MB
<missing> 2 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ARG RELEASE 0B
[root@docker ~]# docker history ubuntu-with-vim-dockerfile:latest
IMAGE CREATED CREATED BY SIZE COMMENT
e22fcd50ee9e 2 minutes ago RUN /bin/sh -c apt-get update && apt-get ins… 125MB buildkit.dockerfile.v0
<missing> 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:e67907c77897d2719… 78.1MB
<missing> 2 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ARG RELEASE 0B
鏡像的緩存特性
Docker 會緩存已有鏡像的鏡像層,構建新鏡像時,如果某鏡像層已經存在,就直接使用,無需重新創建。
示例1:
在前面的 Dockerfile 中添加一點新內容,往鏡像中復制一個文件:
[root@docker ~]# cd /root
[root@docker ~]# pwd
/root
[root@docker ~]# ls
anaconda-ks.cfg Dockerfile
[root@docker ~]# touch testfile
[root@docker ~]# ls
anaconda-ks.cfg Dockerfile testfile
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
COPY testfile /
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-2 .
在 ubuntu-with-vi-dockerfile 鏡像上直接添加一層就得到了新的鏡像 ubuntu-with-vim-dockerfile-2。
示例2:
如果我們改變 Dockerfile 指令的執行順序,或者修改或添加指令,都會使緩存失效。
交換前面 RUN 和 COPY 的順序:
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM ubuntu
COPY testfile /
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-3 .#雖然在邏輯上這種改動對鏡像的內容沒有影響,但由于分層的結構特性,Docker 必須重建受影響的鏡像層。
上圖中看到[2/3],[3/3]都沒有使用緩存,最后生成了新的鏡像層,緩存已經失效。
調試Dockerfile
如果Dockerfile出現錯誤該如何解決?
示例:
先pull busybox
[root@docker ~]# ls
anaconda-ks.cfg Dockerfile testfile
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /[root@docker ~]# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
80bfbb8a41a2: Pull complete
Digest: sha256:ab33eacc8251e3807b85bb6dba570e4698c3998eca6f0fc2ccb60575a563ea74
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile-3 latest f95f014aa80f 10 minutes ago 203MB
ubuntu-with-vim-dockerfile-2 latest 7de0cc8476dd 17 minutes ago 203MB
ubuntu-with-vim-dockerfile latest e22fcd50ee9e 2 hours ago 203MB
ubuntu-with-vim latest ba23f0e4a93f 3 hours ago 203MB
ubuntu latest 802541663949 2 weeks ago 78.1MB
hello-world latest 1b44b5a3e06a 3 weeks ago 10.1kB
httpd latest 199e3a035264 3 weeks ago 117MB
busybox latest 0ed463b26dae 11 months ago 4.43MB
centos 7 eeb6ee3f44bd 3 years ago 204MB#基于剛才寫的Dockerfile構建鏡像image-debug
[root@docker ~]# docker build -t image-debug .
Dockerfile 在執行第三步 RUN 指令時失敗。我們可以利用busybox的鏡像進行調試,方式是通過 docker run -it 啟動鏡像的一個容器。
錯誤原因就是鏡像中沒有bash,busybox用的是sh,改錯就是把Dockerfile中的bash改成sh就可以了
Dockerfile常用指令
FROM:鏡像構建的 “地基”
這是 Dockerfile 里必須放在第一行的指令,作用是指定一個基礎鏡像(base 鏡像)。后續所有指令都會基于這個基礎鏡像來疊加操作,相當于給鏡像構建找了個 “起點”。比如想基于 Ubuntu 系統構建鏡像,就可以寫FROM ubuntu:22.04
(22.04 是具體的版本標簽,避免用 latest 以防版本變動)。
MAINTAINER:標注鏡像 “作者信息”
用來設置鏡像的作者,內容可以是任意字符串,比如個人姓名、郵箱,或者團隊名稱,主要作用是方便后續維護時識別鏡像歸屬。例如MAINTAINER Zhang San <zhangsan@example.com>
,不過現在更推薦用LABEL maintainer="..."
(LABEL 能承載更多元的元數據),但 MAINTAINER 作為傳統指令仍可使用。
COPY:簡單的 “文件復制工具”
功能是把 Docker 構建上下文(build context,也就是執行docker build
時指定的目錄下的文件)里的文件或目錄,復制到鏡像內部的指定路徑。
它支持兩種寫法:
- 簡潔版:
COPY 源路徑 目標路徑
(比如COPY app.jar /opt/
) - 數組版:
COPY ["源路徑", "目標路徑"]
(當路徑包含空格或特殊字符時用,比如COPY ["my app", "/opt/my app"]
)
關鍵注意點:源路徑只能是構建上下文里的文件 / 目錄,不能寫上下文之外的路徑(比如../file.txt
這種上級目錄路徑是無效的)。
ADD:帶 “自動解壓” 的復制指令
和 COPY 的核心功能一致(從構建上下文復制到鏡像),但多了一個特殊能力:如果源文件是常見的歸檔格式(比如 tar、zip、tgz、xz 等),ADD 會自動把文件解壓到鏡像的目標路徑下,不用額外寫解壓命令。
比如ADD app.tar.gz /opt/
,會直接把 app.tar.gz 里的所有文件解壓到 /opt/ 目錄下;但如果只是想單純復制歸檔文件(不想解壓),就用 COPY 更合適,避免 ADD 的 “自動解壓” 功能干擾。
ENV:給鏡像設置 “環境變量”
用來定義環境變量,這些變量不僅在構建鏡像時(后續的指令中)可以使用,容器運行時也能生效。
比如先定義ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk
,之后的 RUN 指令就可以直接用這個變量:RUN echo $JAVA_HOME
,容器啟動后也能通過echo $JAVA_HOME
看到這個路徑,避免硬編碼路徑導致的維護麻煩。
EXPOSE:聲明容器 “監聽端口”
用來告訴 Docker,這個鏡像構建出的容器,內部進程會監聽某個或某些端口(比如 Web 服務監聽 80 端口)。
比如EXPOSE 80 443
,表示容器內會監聽 80(HTTP)和 443(HTTPS)端口。
注意:EXPOSE 只是 “聲明”,不會自動把端口映射到主機;要實現主機和容器的端口映射,需要在docker run
時用-p 主機端口:容器端口
(比如-p 8080:80
)來手動配置,這部分后續在容器網絡章節會詳細講。
VOLUME:聲明鏡像的 “數據卷”
把鏡像中的某個文件或目錄聲明為 “數據卷”(volume),數據卷的作用是讓容器的數據脫離容器本身存儲,方便數據持久化(比如數據庫的數據目錄),或者實現多個容器間共享數據。
比如VOLUME /var/lib/mysql
,表示把容器內的 /var/lib/mysql 目錄(MySQL 的數據目錄)設為數據卷,后續容器運行時,這個目錄的數據會存在宿主機的指定位置,不會隨著容器刪除而丟失。具體的使用細節會在容器存儲章節展開。
WORKDIR:設置后續指令的 “工作目錄”
為 Dockerfile 中后面的 RUN、CMD、ENTRYPOINT、ADD、COPY 指令設置 “當前工作目錄”,相當于在鏡像內部切換到指定目錄,后續這些指令的操作都會基于這個目錄進行,不用每次都寫完整路徑。
比如先寫WORKDIR /opt/app
,之后的COPY app.jar .
就相當于COPY app.jar /opt/app/
,RUN java -jar app.jar
也會在 /opt/app 目錄下執行,讓指令更簡潔。如果多次使用 WORKDIR,后續的指令會基于前一次的目錄疊加(比如先WORKDIR /opt
,再WORKDIR app
,最終工作目錄是 /opt/app)。
RUN:構建時 “執行命令”
在鏡像構建過程中(也就是docker build
的時候),在容器環境里執行指定的命令,執行結果會被保存到鏡像的分層中。
比如需要安裝軟件時,就用RUN apt update && apt install -y nginx
(把多個命令用 && 連起來,減少鏡像分層);或者創建目錄RUN mkdir -p /opt/app
。簡單說,RUN 是 “構建鏡像時要做的操作”。
CMD:容器 “啟動時執行的命令”
定義容器啟動時要運行的命令,比如啟動 Web 服務、數據庫服務等。
有幾個關鍵特性:
- Dockerfile 里可以寫多個 CMD 指令,但只有最后一個會生效(前面的都會被覆蓋);
- CMD 指定的命令可以被
docker run
后面的參數 “替換”。比如 Dockerfile 里寫CMD ["nginx", "-g", "daemon off;"]
,如果執行docker run 鏡像名 /bin/bash
,就會用/bin/bash
替換原來的 CMD 命令,容器啟動后會進入 bash 交互環境,而不是啟動 nginx。
ENTRYPOINT:容器 “固定啟動命令”
和 CMD 類似,也是設置容器啟動時運行的命令,但它的核心特點是 “不可被輕易替換”,更適合作為容器的 “固定入口程序”。
關鍵特性:
- 同樣支持多個指令,但只有最后一個生效;
- CMD 的內容,或者
docker run
后面跟的參數,會被當作 “參數” 傳遞給 ENTRYPOINT 指定的命令。比如 Dockerfile 里寫ENTRYPOINT ["echo", "Hello"]
,再寫CMD ["World"]
,容器啟動后會執行echo Hello World
;如果執行docker run 鏡像名 Docker
,就會執行echo Hello Docker
(用 “Docker” 替換了 CMD 的 “World” 作為參數)。
簡單說,ENTRYPOINT 定義 “要做什么”,CMD 或docker run
參數定義 “做這件事的參數”,適合需要固定核心程序、靈活調整參數的場景(比如工具類鏡像)。
綜合示例:
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
# my dockerfile
FROM busybox #從bustbox開始創建
MAINTAINER 123456@qq.com #聲明作者信息
WORKDIR /testdir #設置工作目錄
RUN touch tmpfile1 #在新鏡像中創建
COPY ["tmpfile2","."] #將Dockerfile文件所在目錄中的tmpfile2文件拷貝到新鏡像中
ADD ["passwd.tar.gz","."] #將Dockerfile文件所在目錄中的passwd.tar.gz文件拷貝到新鏡像中并壓縮
ENV WELCOME "You are in my container,welcome! hahahahaha" #設置環境變量WELCOME# 構建鏡像:# tmpfile2 用touch命令產生
[root@docker ~]# touch tmpfile2# passwd.tar.gz 用tar命令產生
[root@docker ~]# cp /etc/passwd .
[root@docker ~]# tar -cvzf passwd.tar.gz passwd
passwd
[root@docker ~]# pwd
/root
[root@docker ~]# rm passwd
[root@docker ~]# ls
anaconda-ks.cfg Dockerfile passwd.tar.gz testfile tmpfile2#構建新鏡像my-image
[root@docker ~]# docker build -t my-image .
驗證:
[root@docker ~]# docker run -it my-image
/testdir # pwd
/testdir
/testdir # ls
passwd tmpfile1 tmpfile2
/testdir # echo $WELCOME
You are in my container,welcome! hahahahaha# 進入容器,當前目錄即為 WORKDIR
# WORKDIR 中保存了我們希望的文件和目錄
# ENV 指令定義的環境變量已經生效
RUN vs CMD vs ENTRYPOINT
Shell 格式
<instruction> <command>
例如
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
當指令執行時,shell 格式底層會調用 /bin/sh -c 。
示例:
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
ENV name gqd
ENTRYPOINT echo "Hello, $name"[root@docker ~]# docker build -t dockerfile1 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 97B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:3dc1f7ae4dd8e013ea883e40fd6af1eec21184e015ebfc68a83e00b 0.0s=> => naming to docker.io/library/dockerfile1 0.0s
[root@docker ~]# docker run dockerfile1
Hello, gqd# 環境變量 name 已經被值 gqd 替換。
Exec 格式
<instruction> ["executable", "param1", "param2", ...]
例如
RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
示例
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
ENV name yuxb
ENTRYPOINT ["/bin/echo", "Hello, $name"][root@docker ~]# docker build -t dockerfile2 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 107B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:d5d961b3b298e94273bcf4f50b903d2403549027d7baa757db3c6b7 0.0s=> => naming to docker.io/library/dockerfile2 0.0s
[root@docker ~]# docker run dockerfile2
Hello, $name# 環境變量“name”沒有被替換。
如果想替換,如下:
FROM busybox
ENV name yuxb
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
ENV name yuxb
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"][root@docker ~]# docker build -t dockerfile3 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 116B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:cb5385aa427e4ce20cf11448cc7710404040b99cc45f8a7112ac5cc 0.0s=> => naming to docker.io/library/dockerfile3 0.0s
[root@docker ~]# docker run dockerfile3
Hello, yuxb
小結
CMD 和 ENTRYPOINT 推薦使用 Exec 格式,因為指令可讀性更強,更容易理解。RUN 則兩種格式都可 以。
RUN
RUN 指令通常用于安裝應用和軟件包。
RUN 在當前鏡像的頂部執行命令,并通過創建新的鏡像層。Dockerfile 中常常包含多個 RUN 指令。
RUN 有兩種格式:
-
Shell 格式:RUN
-
Exec 格式:RUN [“executable”, “param1”, “param2”]
示例:
使用 RUN 安裝多個包
FROM ubuntu
RUN apt-get update && apt-get install -y bzr cvs git mercurial subversion
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y bzr cvs git mercurial subversion
[root@docker ~]# docker build -t dockerfile4 .
[+] Building 236.5s (6/6) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 123B 0.0s=> [internal] load metadata for docker.io/library/ubuntu:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/2] FROM docker.io/library/ubuntu:latest 0.0s=> [2/2] RUN apt-get update && apt-get install -y bzr cvs git mercurial subversi 235.3s=> exporting to image 1.2s=> => exporting layers 1.1s=> => writing image sha256:f5d2b0a3ad23038c70cc1a18294f558c9bd74ae1a3bccb995a27946 0.0s=> => naming to docker.io/library/dockerfile4 0.0s
驗證
[root@docker ~]# docker run -it dockerfile4
root@d3cd686296e4:/# apt list install brz cvs git mercurial subversion
Listing... Done
brz/noble,now 3.3.5-6build2 amd64 [installed,automatic]
cvs/noble,now 2:1.12.13+real-30build1 amd64 [installed]
git/noble-updates,noble-security,now 1:2.43.0-1ubuntu7.3 amd64 [installed]
mercurial/noble-updates,now 6.7.2-1ubuntu2.2 amd64 [installed]
subversion/noble,now 1.14.3-1build4 amd64 [installed]
root@d3cd686296e4:/#
CMD
CMD 指令允許用戶指定容器的默認執行的命令。
此命令會在容器啟動且 docker run 沒有指定其他命令時運行。
-
如果 docker run 指定了其他命令,CMD 指定的默認命令將被忽略。
-
如果 Dockerfile 中有多個 CMD 指令,只有最后一個 CMD 有效。
CMD 有三種格式:
-
Exec 格式:CMD [“executable”,“param1”,“param2”] 這是 CMD 的推薦格式。
-
CMD [“param1”,“param2”] 為 ENTRYPOINT 提供額外的參數,此時 ENTRYPOINT 必須使用 Exec 格式。
-
Shell 格式:CMD command param1 param2
第二種格式 CMD [“param1”,“param2”] 要與 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是為 ENTRYPOINT 設置默認的參數。在后面討論 ENTRYPOINT 時舉例說明。
下面看看 CMD 是如何工作的。Dockerfile 如下:
FROM busybox
CMD echo "Hello,world"
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
CMD echo "Hello,world"
[root@docker ~]# docker build -t dockerfile5 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 73B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:e43571cba5b2433e067af8e01a3c850a4f93219a89b25744643c8a3 0.0s=> => naming to docker.io/library/dockerfile5 0.0s
[root@docker ~]# docker run -it dockerfile5
Hello,world[root@docker ~]# docker run -it dockerfile5 echo 666
666
[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #
ENTRYPOINT
ENTRYPOINT 指令的核心作用是讓容器能以應用程序或服務的形式運行,它和 CMD 有相似之處 —— 都能指定要執行的命令及參數,但兩者的關鍵區別在于:ENTRYPOINT 指定的命令不會被忽略,無論運行docker run
時是否額外指定了其他命令,它都會被執行。
ENTRYPOINT 有兩種使用格式,選擇時要特別注意,因為不同格式的實際效果差異很大:
- Exec 格式(推薦):
寫法是ENTRYPOINT ["可執行文件", "參數1", "參數2"]
,比如ENTRYPOINT ["nginx", "-g", "daemon off;"]
。這種格式會直接調用指定的可執行文件,能夠正確接收后續的參數(包括 CMD 里的參數或docker run
時附加的參數)。 - Shell 格式:
寫法是ENTRYPOINT 命令 參數1 參數2
,比如ENTRYPOINT nginx -g "daemon off;"
。這種格式會在 shell 中執行命令,此時docker run
附加的參數無法傳遞給 ENTRYPOINT,實際使用中靈活性較低,不如 Exec 格式常用。
Exec 格式
ENTRYPOINT 的 Exec 格式用于設置要執行的命令及其參數,同時可通過 CMD 提供額外的參數。
ENTRYPOINT 中的參數始終會被使用,而 CMD 的額外參數可以在容器啟動時動態替換掉。
示例Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
[root@docker ~]# docker build -t dockerfile6 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 98B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:066e84f20225466825115eda2b0a68331ddb8d7d628844f37994c1a 0.0s=> => naming to docker.io/library/dockerfile6 0.0s
[root@docker ~]# docker run -it dockerfile6
Hello world[root@docker ~]# docker run -it dockerfile6 666
Hello 666
Shell 格式
ENTRYPOINT 的 Shell 格式會忽略任何 CMD 或 docker run 提供的參數。
示例Dockerfile
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"][root@docker ~]# docker build -t dockerfile7 .
[+] Building 0.1s (5/5) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 89B 0.0s=> [internal] load metadata for docker.io/library/busybox:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> CACHED [1/1] FROM docker.io/library/busybox:latest 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:452cb29c2d513ff74ac8744c4c99a9e521c2da78edeee10829035e9 0.0s=> => naming to docker.io/library/dockerfile7 0.0s
[root@docker ~]# docker run -it dockerfile7
Hello,[root@docker ~]# docker run -it dockerfile7 666
Hello,
最佳實踐
-
使用 RUN 指令安裝應用和軟件包,構建鏡像。
-
如果 Docker 鏡像的用途是運行應用程序或服務,比如運行一個 MySQL,應該優先使用 Exec 格式 的 ENTRYPOINT 指令。CMD 可為 ENTRYPOINT 提供額外的默認參數,同時可利用 docker run 命 令行替換默認參數。
-
如果想為容器設置默認的啟動命令,可使用 CMD 指令。用戶可在 docker run 命令行中替換此默認 命令。
Dockerfile案例:配置SSH鏡像
先創建Dockerfile
[root@docker ~]# vim centos.ssh.dockerfile
[root@docker ~]# cat centos.ssh.dockerfile
FROM centos:8.4.2105
MAINTAINER yuxb
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors .aliyun.com/centos-vault/$minorver|g" \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:123" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]
構建鏡像
[root@docker ~]# docker build -t centos:ssh -f centos.ssh.dockerfile .# docker build:Docker 的構建命令,用于根據 Dockerfile 構建鏡像
# -t centos:ssh:指定構建出的鏡像的標簽(tag),格式為名稱:標簽,這里表示鏡像名為centos,標簽為ssh
# -f centos.ssh.dockerfile:指定要使用的 Dockerfile 文件名為centos.ssh.dockerfile(默認情況下會尋找名為Dockerfile的文件,這里用-f參數指定了自定義文件名)
# .:表示構建上下文的路徑為當前目錄,Docker 會將該目錄下的文件發送給 Docker 引擎用于構建鏡像
查看現象
# docker history centos:ssh 這條命令的作用是查看 centos:ssh 這個 Docker 鏡像的構建歷史,即該鏡像是由哪些步驟(層)組成的。
[root@docker ~]# docker history centos:ssh
IMAGE CREATED CREATED BY SIZE COMMENT
0572296974cc About a minute ago CMD ["/usr/sbin/sshd" "-D"] 0B buildkit.dockerfile.v0
<missing> About a minute ago EXPOSE map[22/tcp:{}] 0B buildkit.dockerfile.v0
<missing> About a minute ago RUN /bin/sh -c echo "root:123" | chpasswd # … 1.77kB buildkit.dockerfile.v0
<missing> About a minute ago RUN /bin/sh -c ssh-keygen -t ecdsa -f /etc/s… 695B buildkit.dockerfile.v0
<missing> About a minute ago RUN /bin/sh -c ssh-keygen -t rsa -f /etc/ssh… 3.18kB buildkit.dockerfile.v0
<missing> About a minute ago RUN /bin/sh -c yum install -y openssh-server… 51.9MB buildkit.dockerfile.v0
<missing> About a minute ago RUN /bin/sh -c minorver=8.4.2105 && sed -e "… 17.6kB buildkit.dockerfile.v0
<missing> About a minute ago MAINTAINER yuxb 0B buildkit.dockerfile.v0
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0… 231MB
測試
#基于剛才dockerfile創建的鏡像centos:ssh創建容器sshtest
[root@docker ~]# docker run -d -p 2022:22 --name sshtest centos:ssh
8f20df0046a8aecddd618265261b4d0db17f2e6f4f3605d5e1ef247b36b7518d
# -p 2022:22:端口映射,將容器內部的 22 端口(SSH 默認端口)映射到主機的 2022 端口。這意味著可以通過主機的 2022 端口訪問容器內的 SSH 服務
# --name sshtest:為容器指定一個名稱 sshtest,方便后續通過名稱管理容器(如停止、刪除等操作)
# centos:ssh:指定使用的鏡像,即之前構建的帶有 SSH 服務的 CentOS 鏡像#查看創建出來的容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f20df0046a8 centos:ssh "/usr/sbin/sshd -D" 14 seconds ago Up 13 seconds 0.0.0.0:2022->22/tcp, :::2022->22/tcp sshtest# ssh登錄容器測試ssh,能夠成功登錄
# 通過 SSH 協議 連接到容器內部。
[root@docker ~]# ssh root@localhost -p 2022# 第二種登陸方法
# 通過 Docker 自身的 exec 命令 直接進入容器內部的交互終端。
[root@docker ~]# docker exec -it sshtest bash
ssh直連192.168.108.30,端口號輸入2022也可以登錄