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


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

