Docker核心技術(shù)之Dockerfile指令詳解及綜合案例
在前面的章節(jié)中,我們學(xué)會(huì)了如何拉取和運(yùn)行他人構(gòu)建好的鏡像。但要真正掌握 Docker,我們必須學(xué)會(huì)創(chuàng)建屬于自己的鏡像。Dockerfile 就是實(shí)現(xiàn)這一目標(biāo)的核心工具。它就像一張自動(dòng)化的“安裝說明書”或“構(gòu)建藍(lán)圖”,讓鏡像的創(chuàng)建過程變得透明、可重復(fù)且易于版本控制
思維導(dǎo)圖


一、什么是 Dockerfile?
Dockerfile 是一個(gè)包含一系列指令的文本文件。每一條指令都對(duì)應(yīng) Docker 鏡像中的一個(gè)層。當(dāng)我們執(zhí)行 docker build 命令時(shí),Docker 會(huì)逐一執(zhí)行 Dockerfile 中的指令,最終生成一個(gè)完整的、可運(yùn)行的自定義鏡像。
核心構(gòu)建命令:
docker build -t <image_name>:<tag> .
-t: 指定新鏡像的名稱和標(biāo)簽。
.: 指定構(gòu)建上下文的路徑,通常是包含 Dockerfile 的當(dāng)前目錄。構(gòu)建上下文中的所有文件都會(huì)被發(fā)送到 Docker 守護(hù)進(jìn)程,以便在構(gòu)建過程中使用。
二、Dockerfile 核心指令詳解
以下是最常用且最重要的 Dockerfile 指令,我們將逐一解析。
FROM
作用:指定新鏡像所基于的基礎(chǔ)鏡像。
語法:FROM <image>[:<tag>] [AS <name>]
說明:FROM 指令必須是 Dockerfile 中第一條非注釋的指令。AS <name> 用于在多階段構(gòu)建中為當(dāng)前構(gòu)建階段命名。
代碼案例:
FROM ubuntu:22.04
解析:這個(gè)鏡像將基于 Ubuntu 22.04 官方鏡像進(jìn)行構(gòu)建
WORKDIR
作用:設(shè)置后續(xù) RUN, CMD, ENTRYPOINT, COPY, ADD 指令的工作目錄。
語法:WORKDIR /path/to/workdir
說明:如果目錄不存在,WORKDIR 會(huì)自動(dòng)創(chuàng)建它。使用 WORKDIR 是一個(gè)非常好的實(shí)踐,可以避免在多個(gè)指令中使用 cd 命令。
代碼案例:
WORKDIR /app
解析:此后的所有指令,如 COPY . .,都會(huì)在容器內(nèi)的 /app 目錄下執(zhí)行。
COPY 與 ADD
作用:將構(gòu)建上下文中的文件或目錄復(fù)制到鏡像的文件系統(tǒng)中。
語法:COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>
核心區(qū)別:
COPY: 功能純粹,就是復(fù)制文件/目錄。
ADD: 功能更豐富,除了 COPY 的功能外,還支持:
自動(dòng)解壓:如果 <src> 是一個(gè)可識(shí)別的壓縮文件 (如 tar, gzip, bzip2),ADD 會(huì)自動(dòng)將其解壓到 <dest>。
URL支持:如果 <src> 是一個(gè)URL,ADD 會(huì)嘗試下載該文件。
最佳實(shí)踐:優(yōu)先使用 COPY。因?yàn)樗男袨楦鞔_、可預(yù)測(cè)。只在確實(shí)需要自動(dòng)解壓或遠(yuǎn)程下載時(shí)才考慮使用 ADD。
代碼案例:
# 將當(dāng)前目錄下的 app.jar 復(fù)制到鏡像的 /app/ 目錄下 COPY app.jar /app/ # 將 src 目錄下的所有內(nèi)容復(fù)制到鏡像的 /app/src/ 目錄下 COPY src/ /app/src/
RUN
作用:在鏡像構(gòu)建過程中執(zhí)行命令。
語法:
RUN <command> (shell 格式) RUN ["executable", "param1", "param2"] (exec 格式,推薦)
說明:每條 RUN 指令都會(huì)創(chuàng)建一個(gè)新的鏡像層。為了減小鏡像體積,通常建議將多個(gè)相關(guān)命令用 && 連接在同一條 RUN 指令中。
代碼案例:
# shell 格式:安裝依賴并清理緩存 RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* # exec 格式: RUN ["/bin/bash", "-c", "echo hello"]
EXPOSE
作用:聲明容器在運(yùn)行時(shí)會(huì)監(jiān)聽的網(wǎng)絡(luò)端口。
語法:EXPOSE <port> [<port>/<protocol>...]
重要說明:EXPOSE 僅僅是一個(gè)文檔性的指令,它并不會(huì)自動(dòng)將端口發(fā)布到宿主機(jī)。實(shí)際發(fā)布端口需要在運(yùn)行容器時(shí)使用 docker run -p <host_port>:<container_port> 參數(shù)。
代碼案例:
# 聲明容器將監(jiān)聽 8080 端口 EXPOSE 8080
CMD 與 ENTRYPOINT
作用:這兩個(gè)指令都用于指定容器啟動(dòng)時(shí)要執(zhí)行的命令。
| 指令 | 行為 | 語法 (推薦 exec 格式) |
|---|---|---|
| CMD | 提供容器啟動(dòng)時(shí)的默認(rèn)命令。如果 docker run 命令后面跟了其他命令,CMD 會(huì)被覆蓋。 | CMD ["executable", "param1", "param2"] |
| ENTRYPOINT | 配置容器使其像一個(gè)可執(zhí)行文件。docker run 后面跟的所有內(nèi)容都會(huì)被當(dāng)作參數(shù)傳遞給 ENTRYPOINT。 | ENTRYPOINT ["executable", "param1", "param2"] |
最佳實(shí)踐 (組合使用):
使用 ENTRYPOINT 定義容器的主執(zhí)行命令,使用 CMD 提供該命令的默認(rèn)參數(shù)。
ENTRYPOINT ["java", "-jar", "app.jar"] CMD ["--server.port=8080"]
docker run <image> -> 執(zhí)行 java -jar app.jar --server.port=8080
docker run <image> --server.port=9090 -> CMD 被覆蓋,執(zhí)行 java -jar app.jar --server.port=9090
ENV 與 ARG
作用:用于定義變量。
核心區(qū)別:
ENV: 設(shè)置環(huán)境變量。它在構(gòu)建過程中和容器運(yùn)行時(shí)都有效。
ARG: 設(shè)置構(gòu)建時(shí)變量。它只在 Dockerfile 構(gòu)建過程中有效,容器運(yùn)行后該變量不存在。
代碼案例:
ARG APP_VERSION=1.0
ENV APP_HOME=/app
ENV APP_VERSION=${APP_VERSION} # 將ARG的值持久化到ENV中
WORKDIR ${APP_HOME}
RUN echo "Building version ${APP_VERSION} in ${APP_HOME}"
其他常用指令
- VOLUME: 創(chuàng)建一個(gè)可以掛載數(shù)據(jù)卷的掛載點(diǎn)。
- USER: 指定后續(xù) RUN, CMD, ENTRYPOINT 指令所使用的用戶名或UID。出于安全考慮,推薦創(chuàng)建一個(gè)非root用戶來運(yùn)行應(yīng)用。
- LABEL: 為鏡像添加元數(shù)據(jù),如 LABEL maintainer="your.email@example.com"。
三、綜合案例
這個(gè)案例將使用 多階段構(gòu)建,這是現(xiàn)代Dockerfile 的最佳實(shí)踐,可以極大地減小最終鏡像的體積
前提條件:
你有一個(gè)可以正常打包的 Spring Boot Maven 項(xiàng)目。
項(xiàng)目根目錄下有 pom.xml 和 src 目錄。
最終打包生成的 JAR 文件位于 target/ 目錄下。
步驟一:在項(xiàng)目根目錄下創(chuàng)建 Dockerfile 文件
# ---- Build Stage ---- # 使用一個(gè)包含 Maven 和 JDK 的鏡像作為構(gòu)建環(huán)境 FROM maven:3.8.3-openjdk-11 AS builder # 設(shè)置工作目錄 WORKDIR /build # 復(fù)制 pom.xml 并下載依賴,利用 Docker 的層緩存機(jī)制 COPY pom.xml . RUN mvn dependency:go-offline # 復(fù)制源代碼并進(jìn)行打包 COPY src/ ./src/ RUN mvn package -DskipTests # ---- Runtime Stage ---- # 使用一個(gè)非常精簡(jiǎn)的、只包含Java運(yùn)行時(shí)的鏡像作為最終鏡像 FROM openjdk:11-jre-slim # 設(shè)置工作目錄 WORKDIR /app # 從構(gòu)建階段 (builder) 復(fù)制已打包好的 JAR 文件到當(dāng)前階段 COPY --from=builder /build/target/*.jar app.jar # 聲明應(yīng)用將監(jiān)聽的端口 EXPOSE 8080 # 定義容器啟動(dòng)時(shí)執(zhí)行的命令 ENTRYPOINT ["java", "-jar", "app.jar"]
解析多階段構(gòu)建:
FROM ... AS builder: 定義了第一個(gè)階段,并命名為 builder。這個(gè)階段包含了所有構(gòu)建工具 (Maven, JDK),它的唯一目的是生成 app.jar 文件。
FROM openjdk:11-jre-slim: 開始一個(gè)全新的、干凈的構(gòu)建階段。這個(gè)基礎(chǔ)鏡像非常小,只包含運(yùn)行Java應(yīng)用所必需的 JRE。
COPY --from=builder ...: 核心步驟。它從之前命名為 builder 的構(gòu)建階段中,只把我們需要的構(gòu)建產(chǎn)物 (app.jar) 復(fù)制到當(dāng)前階段。所有構(gòu)建工具和中間文件都被丟棄了。
步驟二:構(gòu)建鏡像
在包含 Dockerfile 和項(xiàng)目代碼的目錄下,執(zhí)行:
docker build -t my-springboot-app:1.0 .
步驟三:運(yùn)行容器
docker run -d -p 8080:8080 --name spring-app my-springboot-app:1.0
步驟四:驗(yàn)證應(yīng)用
# 查看容器日志,確認(rèn) Spring Boot 啟動(dòng)成功 docker logs spring-app # 使用 curl 測(cè)試應(yīng)用的某個(gè)端點(diǎn) (假設(shè)有一個(gè) /hello 端點(diǎn)) curl http://localhost:8080/hello
通過這個(gè)流程,我們成功地將一個(gè) Spring Boot 應(yīng)用打包成一個(gè)輕量級(jí)、可移植、自包含的Docker鏡像。
練習(xí)題
題目一:FROM 指令
一個(gè) Dockerfile 的第一條有效指令 (非注釋) 必須是什么?
題目二:COPY vs ADD
如果你只想簡(jiǎn)單地將本地的一個(gè) config.json 文件復(fù)制到鏡像中,應(yīng)該優(yōu)先選擇 COPY 還是 ADD?為什么?
題目三:RUN 指令優(yōu)化
以下 Dockerfile 寫法有什么潛在問題?應(yīng)該如何優(yōu)化以減小鏡像體積?
RUN apt-get update RUN apt-get install -y curl
題目四:CMD 與 ENTRYPOINT
假設(shè) Dockerfile 中有 ENTRYPOINT ["/bin/echo", "Hello"]。執(zhí)行 docker run <image> World 命令后,最終會(huì)執(zhí)行什么命令?
題目五:EXPOSE 指令的作用
執(zhí)行 EXPOSE 3000 指令后,在不使用 -p 參數(shù)的情況下運(yùn)行容器,宿主機(jī)是否可以通過 localhost:3000 訪問到容器?
題目六:WORKDIR 指令
以下 Dockerfile 執(zhí)行后,pwd 命令的輸出是什么?
WORKDIR /app WORKDIR client RUN pwd
題目七:ENV vs ARG
哪個(gè)指令設(shè)置的變量在容器運(yùn)行后依然可以通過 env 命令查看到?
題目八:USER 指令
為了提高安全性,在 Dockerfile 中通常會(huì)在什么時(shí)間點(diǎn)之后使用 USER 指令切換到非root用戶?
題目九:多階段構(gòu)建
在多階段構(gòu)建中,使用 COPY --from=<stage_name> 的主要目的是什么?
題目十:構(gòu)建命令
如何構(gòu)建一個(gè)名為 my-web-app,標(biāo)簽為 v2 的鏡像,Dockerfile 位于當(dāng)前目錄?
題目十一:運(yùn)行命令
如何以后臺(tái)模式運(yùn)行一個(gè)名為 webapp-instance 的容器,基于 my-web-app:v2 鏡像,并將宿主機(jī)的 8888 端口映射到容器的 80 端口?
題目十二:Dockerfile 最佳實(shí)踐
為什么在 RUN 指令中安裝軟件包后,通常會(huì)緊接著清理包管理器的緩存 (如 rm -rf /var/lib/apt/lists/*)?
題目十三:編寫一個(gè)簡(jiǎn)單的 Dockerfile
編寫一個(gè) Dockerfile,基于 alpine 鏡像,安裝 curl 工具,并在容器啟動(dòng)時(shí)執(zhí)行 curl ifconfig.me 命令。
題目十四:CMD 的覆蓋
一個(gè) Dockerfile 的最后一條指令是 CMD ["echo", "Default"]。如何運(yùn)行這個(gè)鏡像的容器,使其輸出 “Hello Docker” 而不是 “Default”?
答案與解析
答案一:
FROM 指令。
答案二:
應(yīng)該優(yōu)先選擇 COPY。
解析: COPY 的功能更單一、透明,就是復(fù)制文件。而 ADD 可能會(huì)有意想不到的自動(dòng)解壓行為,不夠明確。遵循最小權(quán)限和最明確原則,選擇 COPY。
答案三:
這會(huì)創(chuàng)建兩個(gè)獨(dú)立的鏡像層。第一層緩存了 apt-get update 的結(jié)果,第二層安裝了 curl。將它們合并到一條 RUN 指令中,以減少鏡像層數(shù),從而減小最終鏡像的體積。
RUN apt-get update && apt-get install -y curl
答案四:
最終會(huì)執(zhí)行 /bin/echo Hello World。
解析: 當(dāng) ENTRYPOINT 存在時(shí),docker run 后面的所有內(nèi)容 (World) 都被作為參數(shù)追加到 ENTRYPOINT 命令的末尾。
答案五:
不可以。
解析: EXPOSE 只起到聲明和文檔的作用,并方便容器間互聯(lián)。要從宿主機(jī)訪問,必須在 docker run 時(shí)使用 -p 或 -P 顯式發(fā)布端口。
答案六:
輸出是 /app/client。
解析: WORKDIR 指令可以使用相對(duì)路徑。第二個(gè) WORKDIR client 是相對(duì)于第一個(gè) WORKDIR /app 的,所以最終工作目錄是 /app/client。
答案七:
ENV 指令。
解析: ARG 是構(gòu)建時(shí)變量,在鏡像構(gòu)建完成后就消失了。ENV 設(shè)置的環(huán)境變量會(huì)持久化在鏡像中,并在容器運(yùn)行時(shí)存在。
答案八:
通常在所有需要 root 權(quán)限的操作完成之后,例如安裝軟件包、創(chuàng)建目錄、修改文件權(quán)限等 RUN 指令之后,在設(shè)置 ENTRYPOINT 或 CMD 之前。
# ... RUN chown -R myuser:mygroup /app USER myuser ENTRYPOINT ["./my-app"]
答案九:
主要目的是將前一個(gè)構(gòu)建階段的產(chǎn)物 (如編譯好的二進(jìn)制文件、打包好的JAR/WAR包) 復(fù)制到當(dāng)前新的、更精簡(jiǎn)的構(gòu)建階段中,從而實(shí)現(xiàn)最終鏡像的瘦身,去除所有不必要的構(gòu)建工具和中間文件。
答案十:
docker build -t my-web-app:v2 .
答案十一:
docker run -d -p 8888:80 --name webapp-instance my-web-app:v2
答案十二:
因?yàn)?Dockerfile 的每一條 RUN 指令都會(huì)創(chuàng)建一個(gè)新的鏡像層。如果在一條 RUN 指令中安裝了軟件包,然后在另一條 RUN 指令中清理緩存,那么包含緩存的那一層仍然存在于鏡像中,無法減小鏡像體積。將安裝和清理放在同一條 RUN 指令中,可以確保在該層提交之前,緩存就被清除了,不會(huì)占用最終鏡像的空間。
答案十三:
FROM alpine:latest RUN apk add --no-cache curl CMD ["curl", "ifconfig.me"]
解析: alpine 使用 apk 作為包管理器。--no-cache 選項(xiàng)可以在安裝時(shí)避免留下緩存,是減小鏡像體積的好習(xí)慣。CMD 使用 exec 格式定義啟動(dòng)命令。
答案十四:
docker run <image> echo "Hello Docker"
總結(jié)
到此這篇關(guān)于Docker核心技術(shù)之Dockerfile指令詳解及綜合案例的文章就介紹到這了,更多相關(guān)Docker Dockerfile指令詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Docker容器實(shí)現(xiàn)按順序啟動(dòng)的完整指南
在日常開發(fā)和運(yùn)維中,我們會(huì)遇到這樣的需求,即多個(gè) Docker 容器需要按特定順序啟動(dòng),下面小編就和大家詳細(xì)介紹一下具體的實(shí)現(xiàn)方法吧2025-12-12
一些常見的精簡(jiǎn)Docker?file規(guī)則總結(jié)
這篇文章主要介紹了一些常見的精簡(jiǎn)Docker?file規(guī)則的相關(guān)資料,這些精簡(jiǎn)規(guī)則有助于優(yōu)化Dockerfile,生成更高效、簡(jiǎn)潔的鏡像,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-11-11
詳解基于docker 如何部署surging分布式微服務(wù)引擎
這篇文章主要介紹了詳解基于docker 如何部署surging分布式微服務(wù)引擎,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
Docker數(shù)據(jù)管理之Volume與Bind?Mount的核心技術(shù)詳解
這篇文章主要為大家詳細(xì)介紹了Docker數(shù)據(jù)持久化的兩種核心技術(shù),主要是Volume(數(shù)據(jù)卷)和Bind?Mount(綁定掛載),有需要的小伙伴可以了解下2026-05-05

