一、概述
1.1 背景介紹
Dockerfile寫得好不好,直接影響三件事:鏡像大小、構(gòu)建速度、運(yùn)行安全性。我見過太多團(tuán)隊(duì)的Dockerfile是"能跑就行"的水平——基礎(chǔ)鏡像用ubuntu:latest,一個(gè)RUN裝幾十個(gè)包不清理緩存,最終鏡像1.2GB,構(gòu)建一次15分鐘,里面還帶著gcc和make這些生產(chǎn)環(huán)境根本不需要的東西。
一個(gè)優(yōu)化過的Dockerfile能把鏡像從1.2GB壓縮到80MB,構(gòu)建時(shí)間從15分鐘降到2分鐘(利用緩存后30秒),同時(shí)減少90%的安全漏洞面。這不是理論數(shù)字,是我在實(shí)際項(xiàng)目中反復(fù)驗(yàn)證過的。
Dockerfile本質(zhì)上是一系列指令的集合,Docker按順序執(zhí)行每條指令,每條指令生成一個(gè)鏡像層(Layer)。理解分層機(jī)制是寫好Dockerfile的基礎(chǔ)——層可以被緩存和復(fù)用,合理的指令順序能大幅提升構(gòu)建速度;但層太多會(huì)增加鏡像體積和拉取時(shí)間。
1.2 技術(shù)特點(diǎn)
分層緩存:每條指令生成一層,未變更的層直接使用緩存,構(gòu)建速度從分鐘級(jí)降到秒級(jí)
多階段構(gòu)建:編譯環(huán)境和運(yùn)行環(huán)境分離,最終鏡像只包含運(yùn)行時(shí)必需的文件,體積減少70%-90%
BuildKit引擎:Docker 18.09引入的新構(gòu)建引擎,支持并行構(gòu)建、緩存掛載、Secret掛載,構(gòu)建速度提升2-3倍
可重復(fù)構(gòu)建:同一個(gè)Dockerfile在任何機(jī)器上構(gòu)建出相同的鏡像,消除"我機(jī)器上能構(gòu)建"的問題
安全掃描集成:構(gòu)建時(shí)可以集成Trivy等掃描工具,在CI階段攔截有漏洞的鏡像
1.3 適用場景
Java/Go/Node.js/Python等各語言應(yīng)用的容器化打包
CI/CD流水線中的自動(dòng)化鏡像構(gòu)建
基礎(chǔ)鏡像定制(在官方鏡像基礎(chǔ)上添加公司內(nèi)部工具和配置)
開發(fā)環(huán)境標(biāo)準(zhǔn)化(統(tǒng)一開發(fā)工具鏈版本)
多架構(gòu)鏡像構(gòu)建(同時(shí)支持amd64和arm64)
1.4 環(huán)境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| Docker Engine | 23.0+(推薦24.0+) | 需要BuildKit支持 |
| BuildKit | 內(nèi)置于Docker 23.0+ | 默認(rèn)啟用,舊版本需手動(dòng)開啟 |
| 操作系統(tǒng) | Linux/macOS/Windows | 構(gòu)建環(huán)境不限,生產(chǎn)鏡像建議基于Linux |
| 磁盤空間 | 20GB+可用空間 | 構(gòu)建緩存和中間層需要空間 |
| 內(nèi)存 | 4GB+(編譯型語言建議8GB+) | Go/Java編譯消耗內(nèi)存較大 |
二、詳細(xì)步驟
2.1 準(zhǔn)備工作
2.1.1 確認(rèn)BuildKit已啟用
# 檢查Docker版本
docker version
# 檢查BuildKit是否啟用(Docker 23.0+默認(rèn)啟用)
docker buildx version
# 如果是舊版本Docker,手動(dòng)啟用BuildKit
exportDOCKER_BUILDKIT=1
# 或者在daemon.json中永久啟用
# "features": { "buildkit": true }
# 驗(yàn)證BuildKit工作正常
docker build --progress=plain -ttest-buildkit -f- . <<'EOF'
FROM alpine:3.19
RUN?echo?"BuildKit is working"
EOF
2.1.2 準(zhǔn)備.dockerignore文件
.dockerignore的作用和.gitignore類似,排除不需要發(fā)送到構(gòu)建上下文的文件。構(gòu)建上下文越小,構(gòu)建越快。我見過因?yàn)闆]有.dockerignore,把node_modules(500MB)和.git目錄(200MB)都發(fā)送到構(gòu)建上下文,導(dǎo)致每次構(gòu)建光傳輸上下文就要30秒。
# 文件路徑:項(xiàng)目根目錄/.dockerignore .git .gitignore .dockerignore Dockerfile docker-compose*.yml README.md LICENSE docs/ tests/ *.md *.log *.tmp *.swp # Node.js項(xiàng)目 node_modules/ npm-debug.log .npm/ # Java項(xiàng)目 target/ *.jar *.class .gradle/ build/ # Python項(xiàng)目 __pycache__/ *.pyc .venv/ venv/ *.egg-info/ # IDE文件 .idea/ .vscode/ *.iml # 操作系統(tǒng)文件 .DS_Store Thumbs.db
2.2 核心配置
2.2.1 基礎(chǔ)鏡像選擇
基礎(chǔ)鏡像的選擇直接決定了最終鏡像的大小和安全性。
# 錯(cuò)誤示范:用ubuntu作為基礎(chǔ)鏡像,體積77MB,包含大量不需要的包 FROMubuntu:22.04 # 錯(cuò)誤示范:用latest標(biāo)簽,每次構(gòu)建可能拉到不同版本 FROMnode:latest # 正確:用alpine變體,體積只有5MB FROMnode:20.11-alpine3.19 # 正確:用distroless鏡像,只包含運(yùn)行時(shí),沒有shell和包管理器 FROMgcr.io/distroless/java17-debian12 # 正確:用slim變體,比完整版小但比alpine兼容性好 FROMpython:3.12-slim-bookworm
各基礎(chǔ)鏡像大小對(duì)比:
| 基礎(chǔ)鏡像 | 大小 | 適用場景 |
|---|---|---|
| ubuntu:22.04 | 77MB | 需要apt安裝大量系統(tǒng)包的場景 |
| debian:bookworm-slim | 74MB | 需要glibc但想控制體積 |
| alpine:3.19 | 7MB | 追求極致小體積,注意musl libc兼容性 |
| distroless | 2-20MB | 生產(chǎn)環(huán)境最安全,沒有shell無法exec進(jìn)入 |
| scratch | 0MB | 靜態(tài)編譯的Go程序 |
注意:alpine使用musl libc而不是glibc,部分C語言編寫的程序可能有兼容性問題。典型案例:Python的某些C擴(kuò)展在alpine上編譯失敗或運(yùn)行時(shí)段錯(cuò)誤。遇到這種情況換slim變體。
2.2.2 指令順序優(yōu)化(利用構(gòu)建緩存)
Docker構(gòu)建緩存的規(guī)則:從第一條變更的指令開始,后續(xù)所有層的緩存全部失效。所以要把變化頻率低的指令放前面,變化頻率高的放后面。
# 錯(cuò)誤示范:COPY . 放在安裝依賴之前 # 任何源碼文件變更都會(huì)導(dǎo)致依賴重新安裝 FROMnode:20.11-alpine3.19 WORKDIR/app COPY. . RUNnpm ci --production EXPOSE3000 CMD["node","server.js"] # 正確:先復(fù)制依賴文件,安裝依賴,再復(fù)制源碼 # 只有package.json變更才會(huì)重新安裝依賴 FROMnode:20.11-alpine3.19 WORKDIR/app COPYpackage.json package-lock.json ./ RUNnpm ci --production COPY. . EXPOSE3000 CMD["node","server.js"]
緩存利用的最佳順序:
FROM(基礎(chǔ)鏡像,幾乎不變)
安裝系統(tǒng)依賴(apt/apk install,偶爾變)
復(fù)制依賴描述文件(package.json/pom.xml/go.mod)
安裝應(yīng)用依賴(npm ci/mvn install/go mod download)
復(fù)制源代碼(每次提交都變)
構(gòu)建應(yīng)用
配置運(yùn)行參數(shù)(CMD/ENTRYPOINT)
2.2.3 RUN指令優(yōu)化
# 錯(cuò)誤示范:每個(gè)命令一個(gè)RUN,產(chǎn)生多個(gè)層,且沒有清理緩存 FROMubuntu:22.04 RUNapt update RUNapt install -y curl RUNapt install -y wget RUNapt install -y vim # 錯(cuò)誤示范:安裝了不需要的推薦包,沒有清理apt緩存 FROMubuntu:22.04 RUNapt update && apt install -y curl wget # 正確:合并RUN,使用--no-install-recommends,清理緩存 FROMubuntu:22.04 RUNapt-get update && apt-get install -y --no-install-recommends curl wget ca-certificates && rm -rf /var/lib/apt/lists/*
# Alpine鏡像的正確寫法 FROMalpine:3.19 RUNapk add --no-cache curl tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&echo"Asia/Shanghai"> /etc/timezone && apk del tzdata
關(guān)鍵點(diǎn):
--no-install-recommends:不安裝推薦包,能減少30%-50%的安裝體積
rm -rf /var/lib/apt/lists/*:清理apt緩存,節(jié)省約30MB
apk add --no-cache:alpine的等價(jià)寫法,不緩存索引文件
安裝和清理必須在同一個(gè)RUN中,否則清理操作只是在新層中標(biāo)記刪除,不會(huì)減小鏡像體積
2.2.4 COPY和ADD的區(qū)別
# COPY:簡單復(fù)制文件,推薦使用 COPYapp.jar /app/ COPY--chown=app:app config/ /app/config/ # ADD:有額外功能,但不推薦日常使用 # ADD會(huì)自動(dòng)解壓tar文件 ADDarchive.tar.gz /app/ # ADD可以從URL下載文件(但不推薦,用curl更可控) # ADD https://example.com/file.tar.gz /app/ # 推薦:用curl下載,可以在同一層中下載、解壓、清理 RUNcurl -fsSL https://example.com/file.tar.gz -o /tmp/file.tar.gz && tar xzf /tmp/file.tar.gz -C /app/ && rm /tmp/file.tar.gz
原則:除非需要自動(dòng)解壓tar文件,否則一律用COPY。COPY的行為更明確,不會(huì)有意外的自動(dòng)解壓。
2.2.5 多階段構(gòu)建
多階段構(gòu)建是Dockerfile優(yōu)化的核心技術(shù)。編譯環(huán)境可能需要JDK、Maven、gcc等工具(幾百M(fèi)B),但運(yùn)行時(shí)只需要JRE或一個(gè)二進(jìn)制文件。
# Go應(yīng)用的多階段構(gòu)建 # 階段1:編譯(使用完整的Go SDK,約800MB) FROMgolang:1.22-alpine AS builder WORKDIR/build COPYgo.mod go.sum ./ RUNgo mod download COPY. . RUNCGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w"-o /app/server ./cmd/server # 階段2:運(yùn)行(使用scratch,0MB基礎(chǔ)鏡像) FROMscratch COPY--from=builder /app/server /server COPY--from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ EXPOSE8080 ENTRYPOINT["/server"] # 最終鏡像大?。杭s10-20MB(只有一個(gè)靜態(tài)二進(jìn)制文件+CA證書)
# Java應(yīng)用的多階段構(gòu)建 # 階段1:編譯 FROMmaven:3.9-eclipse-temurin-17AS builder WORKDIR/build COPYpom.xml . RUNmvn dependency:go-offline -B COPYsrc ./src RUNmvn package -DskipTests -B # 階段2:運(yùn)行 FROMeclipse-temurin:17-jre-alpine RUNaddgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app WORKDIR/app COPY--from=builder --chown=app:app /build/target/*.jar app.jar USERapp EXPOSE8080 HEALTHCHECK--interval=30s --timeout=5s --start-period=60s --retries=3 CMD wget -qO- http://localhost:8080/actuator/health ||exit1 ENTRYPOINT["java","-XX:MaxRAMPercentage=75.0","-jar","app.jar"] # 編譯階段鏡像約800MB,最終運(yùn)行鏡像約180MB
2.3 啟動(dòng)和驗(yàn)證
2.3.1 構(gòu)建鏡像
# 基本構(gòu)建 docker build -t myapp:1.0.0 . # 指定Dockerfile路徑 docker build -t myapp:1.0.0 -f deploy/Dockerfile . # 使用BuildKit并顯示詳細(xì)輸出 DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp:1.0.0 . # 構(gòu)建時(shí)傳入?yún)?shù) docker build --build-arg APP_VERSION=1.0.0 --build-arg BUILD_ENV=prod -t myapp:1.0.0 . # 不使用緩存構(gòu)建(排查緩存問題時(shí)用) docker build --no-cache -t myapp:1.0.0 . # 多平臺(tái)構(gòu)建(同時(shí)構(gòu)建amd64和arm64) docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0.0 --push .
2.3.2 驗(yàn)證鏡像
# 查看鏡像大小 docker images myapp:1.0.0 # 查看鏡像分層(每層大小和指令) dockerhistorymyapp:1.0.0 # 查看鏡像詳細(xì)信息 docker inspect myapp:1.0.0 # 用dive工具分析鏡像層(推薦) # 安裝:https://github.com/wagoodman/dive dive myapp:1.0.0 # 安全掃描 docker scout cves myapp:1.0.0 # 或使用Trivy trivy image myapp:1.0.0 # 運(yùn)行測(cè)試 docker run --rm myapp:1.0.0 --version docker run --rm -p 8080:8080 myapp:1.0.0 curl http://localhost:8080/health
三、示例代碼和配置
3.1 完整配置示例
3.1.1 Node.js應(yīng)用Dockerfile(生產(chǎn)級(jí))
# 文件路徑:Dockerfile # Node.js生產(chǎn)環(huán)境Dockerfile - 多階段構(gòu)建 # 階段1:安裝依賴 FROMnode:20.11-alpine3.19AS deps WORKDIR/app COPYpackage.json package-lock.json ./ RUNnpm ci --production --ignore-scripts && npm cache clean --force # 階段2:構(gòu)建(如果有TypeScript編譯或前端構(gòu)建) FROMnode:20.11-alpine3.19AS builder WORKDIR/app COPYpackage.json package-lock.json ./ RUNnpm ci --ignore-scripts COPY. . RUNnpm run build # 階段3:運(yùn)行 FROMnode:20.11-alpine3.19AS runner LABELmaintainer="ops@example.com" LABELversion="1.0.0" # 安裝tini作為PID 1進(jìn)程,正確處理信號(hào)和僵尸進(jìn)程 RUNapk add --no-cache tini tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo"Asia/Shanghai"> /etc/timezone && apk del tzdata # 創(chuàng)建非root用戶 RUNaddgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app WORKDIR/app # 只復(fù)制生產(chǎn)依賴和構(gòu)建產(chǎn)物 COPY--from=deps --chown=app:app /app/node_modules ./node_modules COPY--from=builder --chown=app:app /app/dist ./dist COPY--chown=app:app package.json ./ USERapp ENVNODE_ENV=production ENVPORT=3000 EXPOSE3000 HEALTHCHECK--interval=30s --timeout=5s --start-period=10s --retries=3 CMD wget -qO- http://localhost:3000/health ||exit1 ENTRYPOINT["/sbin/tini","--"] CMD["node","dist/server.js"]
說明:
三階段構(gòu)建:deps階段只裝生產(chǎn)依賴,builder階段編譯TypeScript,runner階段只復(fù)制需要的文件
tini作為PID 1:Node.js不擅長處理信號(hào)和僵尸進(jìn)程回收,tini只有幾十KB,專門干這個(gè)事
npm ci而不是npm install:ci嚴(yán)格按照lock文件安裝,保證可重復(fù)構(gòu)建
3.1.2 Python應(yīng)用Dockerfile(生產(chǎn)級(jí))
# 文件路徑:Dockerfile
# Python生產(chǎn)環(huán)境Dockerfile - 多階段構(gòu)建
# 階段1:構(gòu)建wheel包
FROMpython:3.12-slim-bookworm AS builder
RUNapt-get update &&
apt-get install -y --no-install-recommends gcc libpq-dev &&
rm -rf /var/lib/apt/lists/*
WORKDIR/build
COPYrequirements.txt .
RUNpip install --no-cache-dir --prefix=/install -r requirements.txt
# 階段2:運(yùn)行
FROMpython:3.12-slim-bookworm AS runner
# 安裝運(yùn)行時(shí)依賴(不需要gcc)
RUNapt-get update &&
apt-get install -y --no-install-recommends
libpq5
curl
tini
&& rm -rf /var/lib/apt/lists/*
# 創(chuàng)建非root用戶
RUNgroupadd -g 1000 app && useradd -u 1000 -g app -s /bin/bash -m app
# 從builder階段復(fù)制已安裝的Python包
COPY--from=builder /install /usr/local
WORKDIR/app
COPY--chown=app:app . .
USERapp
ENVPYTHONUNBUFFERED=1
ENVPYTHONDONTWRITEBYTECODE=1
EXPOSE8000
HEALTHCHECK--interval=30s --timeout=5s --start-period=15s --retries=3
CMD curl -f http://localhost:8000/health ||exit1
ENTRYPOINT["tini","--"]
CMD["gunicorn","app.wsgi:application",
"--bind","0.0.0.0:8000",
"--workers","4",
"--worker-class","gvicorn.workers.UvicornWorker",
"--timeout","120",
"--access-logfile","-",
"--error-logfile","-"]
說明:
PYTHONUNBUFFERED=1:禁用Python輸出緩沖,確保日志實(shí)時(shí)輸出到docker logs
PYTHONDONTWRITEBYTECODE=1:不生成.pyc文件,減少容器層大小
--prefix=/install:pip安裝到獨(dú)立目錄,方便多階段構(gòu)建復(fù)制
gunicorn的worker數(shù)一般設(shè)為2 * CPU核心數(shù) + 1,容器限制2核就設(shè)5個(gè)worker
3.1.3 CI/CD構(gòu)建腳本
#!/bin/bash
# 文件名:build.sh
# CI/CD流水線中的鏡像構(gòu)建腳本
set-euo pipefail
# 變量
APP_NAME="myapp"
REGISTRY="registry.example.com"
GIT_COMMIT=$(git rev-parse --short HEAD)
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
VERSION=${CI_COMMIT_TAG:-${GIT_BRANCH}-${GIT_COMMIT}}
IMAGE_NAME="${REGISTRY}/${APP_NAME}"
IMAGE_TAG="${IMAGE_NAME}:${VERSION}"
IMAGE_LATEST="${IMAGE_NAME}:latest"
echo"Building${IMAGE_TAG}"
# 構(gòu)建鏡像
docker build
--build-arg BUILD_TIME="${BUILD_TIME}"
--build-arg GIT_COMMIT="${GIT_COMMIT}"
--build-arg VERSION="${VERSION}"
--label"org.opencontainers.image.created=${BUILD_TIME}"
--label"org.opencontainers.image.revision=${GIT_COMMIT}"
--label"org.opencontainers.image.version=${VERSION}"
-t"${IMAGE_TAG}"
-t"${IMAGE_LATEST}"
.
# 安全掃描
echo"Scanning image for vulnerabilities..."
trivy image --exit-code 1 --severity HIGH,CRITICAL"${IMAGE_TAG}"
if[ $? -ne 0 ];then
echo"ERROR: High/Critical vulnerabilities found, blocking push"
exit1
fi
# 推送鏡像
docker push"${IMAGE_TAG}"
docker push"${IMAGE_LATEST}"
echo"Successfully built and pushed${IMAGE_TAG}"
3.2 實(shí)際應(yīng)用案例
案例一:鏡像瘦身實(shí)戰(zhàn)——從1.2GB到45MB
場景描述:一個(gè)Go微服務(wù)項(xiàng)目,原始Dockerfile直接在golang鏡像中編譯和運(yùn)行,鏡像1.2GB。通過多階段構(gòu)建+scratch基礎(chǔ)鏡像,壓縮到45MB。
優(yōu)化前的Dockerfile:
# 優(yōu)化前:1.2GB FROMgolang:1.22 WORKDIR/app COPY. . RUNgo build -o server ./cmd/server EXPOSE8080 CMD["./server"]
優(yōu)化后的Dockerfile:
# 優(yōu)化后:45MB FROMgolang:1.22-alpine AS builder RUNapk add --no-cache ca-certificates git WORKDIR/build # 先下載依賴(利用緩存) COPYgo.mod go.sum ./ RUNgo mod download # 編譯 COPY. . RUNCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.0.0" -o /app/server ./cmd/server # 用UPX進(jìn)一步壓縮二進(jìn)制文件(可選,壓縮率約60%) RUNapk add --no-cache upx && upx --best /app/server # 運(yùn)行階段 FROMscratch COPY--from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY--from=builder /app/server /server EXPOSE8080 ENTRYPOINT["/server"]
優(yōu)化效果對(duì)比:
優(yōu)化前: REPOSITORY TAG SIZE myapp v1 1.2GB 構(gòu)建時(shí)間:3分12秒 優(yōu)化后: REPOSITORY TAG SIZE myapp v2 45MB 構(gòu)建時(shí)間:1分05秒(有緩存時(shí):8秒)
關(guān)鍵優(yōu)化點(diǎn):
-ldflags="-s -w":去掉調(diào)試信息和符號(hào)表,二進(jìn)制文件減小約30%
CGO_ENABLED=0:禁用CGO,生成靜態(tài)鏈接的二進(jìn)制文件,可以在scratch上運(yùn)行
UPX壓縮:二進(jìn)制文件從50MB壓縮到20MB,啟動(dòng)時(shí)有約100ms的解壓開銷,生產(chǎn)環(huán)境可以不用
scratch基礎(chǔ)鏡像:0字節(jié),沒有shell、沒有包管理器、沒有任何多余的東西
案例二:BuildKit緩存掛載加速構(gòu)建
場景描述:Java項(xiàng)目每次構(gòu)建都要下載Maven依賴,耗時(shí)5-8分鐘。使用BuildKit的緩存掛載功能,依賴緩存在構(gòu)建主機(jī)上,重復(fù)構(gòu)建時(shí)間從8分鐘降到40秒。
# syntax=docker/dockerfile:1 # 注意第一行的syntax指令,啟用BuildKit擴(kuò)展語法 FROMmaven:3.9-eclipse-temurin-17AS builder WORKDIR/build COPYpom.xml . # --mount=type=cache 將Maven本地倉庫緩存到構(gòu)建主機(jī) # 即使鏡像層緩存失效,Maven依賴緩存仍然有效 RUN--mount=type=cache,target=/root/.m2/repository mvn dependency:go-offline -B COPYsrc ./src RUN--mount=type=cache,target=/root/.m2/repository mvn package -DskipTests -B FROMeclipse-temurin:17-jre-alpine RUNaddgroup -g 1000 app && adduser -u 1000 -G app -s /bin/sh -D app WORKDIR/app COPY--from=builder --chown=app:app /build/target/*.jar app.jar USERapp EXPOSE8080 ENTRYPOINT["java","-XX:MaxRAMPercentage=75.0","-jar","app.jar"]
# 構(gòu)建命令(BuildKit默認(rèn)啟用) docker build -t myapp:1.0.0 . # 第一次構(gòu)建:下載所有依賴,約8分鐘 # 第二次構(gòu)建(修改了源碼):依賴從緩存讀取,約40秒 # 第三次構(gòu)建(修改了pom.xml):只下載新增的依賴,約1分鐘
BuildKit緩存掛載類型:
type=cache:持久化緩存目錄,跨構(gòu)建保留。適合包管理器緩存(Maven、npm、pip)
type=secret:掛載密鑰文件,不會(huì)寫入鏡像層。適合私有倉庫認(rèn)證
type=ssh:轉(zhuǎn)發(fā)SSH agent,用于拉取私有Git倉庫
# Secret掛載示例:拉取私有npm包 RUN--mount=type=secret,id=npmrc,target=/root/.npmrc npm ci --production # 構(gòu)建時(shí)傳入secret # docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp:1.0.0 . # SSH掛載示例:拉取私有Git倉庫 RUN--mount=type=ssh gitclonegit@github.com:company/private-lib.git # 構(gòu)建時(shí)轉(zhuǎn)發(fā)SSH # docker build --ssh default -t myapp:1.0.0 .
四、最佳實(shí)踐和注意事項(xiàng)
4.1 最佳實(shí)踐
4.1.1 性能優(yōu)化
合理利用構(gòu)建緩存:把變化頻率低的指令放前面(系統(tǒng)依賴安裝),變化頻率高的放后面(源碼復(fù)制)。一個(gè)典型的Node.js項(xiàng)目,合理利用緩存后構(gòu)建時(shí)間從3分鐘降到15秒(只有源碼變更時(shí)):
# 依賴文件單獨(dú)復(fù)制,變更頻率低 COPYpackage.json package-lock.json ./ RUNnpm ci --production # 源碼最后復(fù)制,變更頻率高 COPY. .
使用BuildKit并行構(gòu)建:多階段構(gòu)建中,沒有依賴關(guān)系的階段會(huì)自動(dòng)并行執(zhí)行。把獨(dú)立的構(gòu)建任務(wù)拆成不同階段:
# 這兩個(gè)階段會(huì)并行執(zhí)行 FROMnode:20-alpine AS frontend-builder COPYfrontend/ . RUNnpm run build FROMgolang:1.22-alpine AS backend-builder COPYbackend/ . RUNgo build -o server # 最終階段合并 FROMalpine:3.19 COPY--from=frontend-builder /app/dist /www COPY--from=backend-builder /app/server /server
減少鏡像層數(shù):合并相關(guān)的RUN指令。Docker限制最多127層,雖然一般不會(huì)超,但層數(shù)越少拉取越快。每一層都有元數(shù)據(jù)開銷,合并后鏡像通常小5%-10%。
4.1.2 安全加固
不在鏡像中存儲(chǔ)密鑰:構(gòu)建參數(shù)(ARG)和環(huán)境變量(ENV)都會(huì)被記錄在鏡像層中,docker history可以看到。密鑰用BuildKit的secret掛載:
# 錯(cuò)誤:密鑰會(huì)留在鏡像歷史中
ARGNPM_TOKEN
RUNecho"http://registry.npmjs.org/:_authToken=${NPM_TOKEN}"> .npmrc &&
npm ci && rm .npmrc
# 正確:secret不會(huì)寫入鏡像層
RUN--mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
使用固定版本的基礎(chǔ)鏡像:不要用latest,不要用只有主版本號(hào)的tag(如node:20)。用完整的版本號(hào)+變體(如node:20.11.1-alpine3.19),確保每次構(gòu)建基礎(chǔ)鏡像一致:
# 不確定性高 FROMpython:3 FROMnode:latest # 版本鎖定 FROMpython:3.12.1-slim-bookworm FROMnode:20.11.1-alpine3.19
鏡像安全掃描集成到CI:每次構(gòu)建后自動(dòng)掃描,HIGH和CRITICAL級(jí)別漏洞阻斷發(fā)布:
# Trivy掃描,發(fā)現(xiàn)高危漏洞返回非0退出碼 trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:1.0.0
4.1.3 高可用配置
鏡像倉庫高可用:生產(chǎn)環(huán)境用Harbor搭建私有倉庫,配置主從復(fù)制。構(gòu)建機(jī)推送到主倉庫,各機(jī)房從本地倉庫拉取,避免跨機(jī)房拉取鏡像的網(wǎng)絡(luò)延遲
構(gòu)建緩存持久化:CI/CD環(huán)境中,構(gòu)建緩存默認(rèn)在構(gòu)建機(jī)本地。用docker buildx的遠(yuǎn)程緩存功能,把緩存存到倉庫:
docker buildx build --cache-fromtype=registry,ref=registry.example.com/myapp:buildcache --cache-totype=registry,ref=registry.example.com/myapp:buildcache,mode=max -t myapp:1.0.0 .
多架構(gòu)支持:生產(chǎn)環(huán)境可能有x86和ARM混合部署,用buildx構(gòu)建多架構(gòu)鏡像,一個(gè)tag同時(shí)支持amd64和arm64
4.2 注意事項(xiàng)
4.2.1 配置注意事項(xiàng)
警告:Dockerfile中的每個(gè)RUN、COPY、ADD指令都會(huì)創(chuàng)建新的鏡像層。刪除文件的操作如果不在同一層中執(zhí)行,不會(huì)減小鏡像體積——文件在上一層已經(jīng)存在,新層只是標(biāo)記刪除。
注意ENTRYPOINT和CMD的區(qū)別:ENTRYPOINT定義容器的主進(jìn)程,CMD提供默認(rèn)參數(shù)。docker run后面的參數(shù)會(huì)覆蓋CMD但不會(huì)覆蓋ENTRYPOINT:
# ENTRYPOINT + CMD組合 ENTRYPOINT["java","-jar","app.jar"] CMD["--spring.profiles.active=prod"] # docker run myapp 會(huì)執(zhí)行:java -jar app.jar --spring.profiles.active=prod # docker run myapp --spring.profiles.active=dev 會(huì)執(zhí)行:java -jar app.jar --spring.profiles.active=dev
注意shell形式和exec形式的區(qū)別:exec形式(JSON數(shù)組)直接執(zhí)行命令,shell形式會(huì)通過/bin/sh -c執(zhí)行。shell形式的進(jìn)程不是PID 1,收不到SIGTERM信號(hào):
# shell形式:sh是PID 1,java是子進(jìn)程,收不到SIGTERM ENTRYPOINTjava -jar app.jar # exec形式:java是PID 1,能正確接收信號(hào) ENTRYPOINT["java","-jar","app.jar"]
注意ARG的作用域:ARG在FROM之前定義的只能在FROM中使用,F(xiàn)ROM之后需要重新聲明:
ARGBASE_IMAGE=alpine:3.19
FROM${BASE_IMAGE}
# 這里ARG BASE_IMAGE已經(jīng)失效,需要重新聲明
ARGAPP_VERSION
RUNecho${APP_VERSION}
4.2.2 常見錯(cuò)誤
| 錯(cuò)誤現(xiàn)象 | 原因分析 | 解決方案 |
|---|---|---|
| 鏡像體積異常大 | 沒有清理包管理器緩存,或者刪除操作不在同一層 | 安裝和清理放在同一個(gè)RUN中 |
| 構(gòu)建緩存總是失效 | COPY . . 放在安裝依賴之前,任何文件變更都導(dǎo)致緩存失效 | 先COPY依賴文件,安裝依賴,再COPY源碼 |
| 容器啟動(dòng)后立即退出 | CMD/ENTRYPOINT寫成了shell形式,前臺(tái)進(jìn)程變成后臺(tái) | 用exec形式,確保主進(jìn)程在前臺(tái)運(yùn)行 |
| 構(gòu)建時(shí)網(wǎng)絡(luò)超時(shí) | 構(gòu)建環(huán)境無法訪問外網(wǎng)或鏡像源 | 配置鏡像源加速,或用--network=host構(gòu)建 |
| 權(quán)限拒絕錯(cuò)誤 | USER指令切換了用戶但文件屬主還是root | COPY --chown=user:group 或 RUN chown |
| alpine上程序段錯(cuò)誤 | musl libc和glibc不兼容 | 換成slim變體或用靜態(tài)編譯 |
4.2.3 兼容性問題
版本兼容:BuildKit的--mount語法需要Docker 18.09+,# syntax=docker/dockerfile:1指令需要BuildKit啟用。舊版Docker不支持這些特性
平臺(tái)兼容:多架構(gòu)構(gòu)建需要QEMU模擬器支持非本機(jī)架構(gòu)。在x86機(jī)器上構(gòu)建arm64鏡像,編譯速度會(huì)慢5-10倍
基礎(chǔ)鏡像兼容:alpine 3.19使用musl libc 1.2.4,部分依賴glibc的二進(jìn)制文件無法運(yùn)行。Node.js和Go的alpine變體沒問題,Python和Java的某些native擴(kuò)展可能有問題
五、故障排查和監(jiān)控
5.1 故障排查
5.1.1 日志查看
# 查看構(gòu)建詳細(xì)日志 docker build --progress=plain -t myapp:1.0.0 . 2>&1 | tee build.log # 查看構(gòu)建歷史(每層的指令和大?。?dockerhistorymyapp:1.0.0 # 查看鏡像元數(shù)據(jù) docker inspect myapp:1.0.0 # 查看構(gòu)建緩存使用情況 docker buildx du # 查看BuildKit構(gòu)建日志 sudo journalctl -u docker.service | grep buildkit
5.1.2 常見問題排查
問題一:構(gòu)建緩存不生效
# 檢查構(gòu)建上下文是否有變化 # .dockerignore沒有排除的文件變更會(huì)導(dǎo)致COPY指令緩存失效 docker build --progress=plain -t myapp:1.0.0 . 2>&1 | grep -E"CACHED|RUN|COPY" # 查看哪一步開始緩存失效 # 輸出中從"CACHED"變成非CACHED的那一步就是緩存失效點(diǎn) # 常見原因: # 1. COPY . . 之前的文件有變更(檢查.dockerignore) # 2. ARG值變了(ARG變更會(huì)導(dǎo)致后續(xù)所有層緩存失效) # 3. 基礎(chǔ)鏡像更新了(FROM的鏡像有新版本)
解決方案:
完善.dockerignore,排除不需要的文件
把COPY拆分,先復(fù)制依賴文件,再復(fù)制源碼
基礎(chǔ)鏡像用完整版本號(hào)鎖定
問題二:構(gòu)建過程中網(wǎng)絡(luò)超時(shí)
# 診斷:檢查構(gòu)建環(huán)境網(wǎng)絡(luò) docker run --rm alpine ping -c 3 registry.npmjs.org docker run --rm alpine wget -qO- https://registry.npmjs.org/ | head -1 # 使用宿主機(jī)網(wǎng)絡(luò)構(gòu)建(繞過Docker網(wǎng)絡(luò)) docker build --network=host -t myapp:1.0.0 . # 配置構(gòu)建時(shí)的代理 docker build --build-arg HTTP_PROXY=http://proxy.example.com:8080 --build-arg HTTPS_PROXY=http://proxy.example.com:8080 --build-arg NO_PROXY=localhost,127.0.0.1,.example.com -t myapp:1.0.0 .
解決方案:配置鏡像源加速(npm用淘寶源,pip用清華源,Maven用阿里云源),或者在Dockerfile中設(shè)置代理環(huán)境變量。
問題三:鏡像體積異常大
癥狀:鏡像大小遠(yuǎn)超預(yù)期,比如一個(gè)Go應(yīng)用鏡像超過500MB
排查:
# 用dive分析每一層的內(nèi)容和大小 dive myapp:1.0.0 # 查看每層大小 dockerhistory--no-trunc myapp:1.0.0 # 檢查是否有不必要的文件 docker run --rm myapp:1.0.0 du -sh /* 2>/dev/null | sort -rh docker run --rm myapp:1.0.0 find / -size +10M -typef 2>/dev/null
解決:
檢查是否用了多階段構(gòu)建,編譯工具不應(yīng)該出現(xiàn)在最終鏡像
檢查RUN指令是否在同一層中清理了緩存
檢查是否復(fù)制了不需要的文件(完善.dockerignore)
5.1.3 調(diào)試模式
# 在構(gòu)建失敗的層啟動(dòng)一個(gè)臨時(shí)容器進(jìn)行調(diào)試 # 方法1:用最后一個(gè)成功的層啟動(dòng)容器 docker build -t myapp:debug . 2>&1 # 找到最后成功的層ID,然后 docker run --rm -it/bin/sh # 方法2:在Dockerfile中插入調(diào)試指令 # 在失敗的RUN之前加一個(gè)RUN ls -la /app/ 查看文件狀態(tài) # 方法3:用BuildKit的調(diào)試功能 BUILDKIT_PROGRESS=plain docker build -t myapp:1.0.0 . 2>&1 | tee build.log # 方法4:交互式調(diào)試(Docker Desktop 4.27+) docker debug myapp:1.0.0
5.2 性能監(jiān)控
5.2.1 關(guān)鍵指標(biāo)監(jiān)控
# 監(jiān)控構(gòu)建時(shí)間
time docker build -t myapp:1.0.0 .
# 監(jiān)控鏡像大小趨勢(shì)
docker images --format"{{.Repository}}:{{.Tag}} {{.Size}}"| sort
# 監(jiān)控構(gòu)建緩存大小
docker buildx du
docker system df
# 監(jiān)控構(gòu)建機(jī)磁盤使用
df -h /var/lib/docker
5.2.2 監(jiān)控指標(biāo)說明
| 指標(biāo)名稱 | 正常范圍 | 告警閾值 | 說明 |
|---|---|---|---|
| 鏡像構(gòu)建時(shí)間 | <5分鐘 | >10分鐘 | 超過10分鐘檢查緩存是否失效 |
| 最終鏡像大小 | <200MB | >500MB | 超過500MB檢查是否有多余文件 |
| 構(gòu)建緩存大小 | <20GB | >50GB | 定期清理構(gòu)建緩存 |
| 鏡像層數(shù) | <20層 | >40層 | 層數(shù)過多影響拉取速度 |
| 安全漏洞數(shù)(HIGH+) | 0 | >0 | 高危漏洞必須修復(fù) |
| 構(gòu)建成功率 | >95% | <90% | 低于90%檢查構(gòu)建環(huán)境穩(wěn)定性 |
5.2.3 CI/CD構(gòu)建監(jiān)控配置
# GitLab CI中的構(gòu)建監(jiān)控示例:.gitlab-ci.yml
build:
stage:build
script:
-BUILD_START=$(date+%s)
-dockerbuild-t${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}.
-BUILD_END=$(date+%s)
-BUILD_TIME=$((BUILD_END-BUILD_START))
-echo"Build time: ${BUILD_TIME}s"
# 推送構(gòu)建指標(biāo)到Prometheus Pushgateway
-|
cat <
# Prometheus告警規(guī)則:dockerfile-build-alerts.yml
groups:
-name:docker_build_alerts
rules:
-alert:DockerBuildSlow
expr:docker_build_duration_seconds>600
for:0m
labels:
severity:warning
annotations:
summary:"項(xiàng)目{{ $labels.instance }}構(gòu)建時(shí)間過長"
description:"構(gòu)建耗時(shí){{ $value }}秒,超過10分鐘閾值"
-alert:DockerImageTooLarge
expr:docker_image_size_bytes>524288000
for:0m
labels:
severity:warning
annotations:
summary:"項(xiàng)目{{ $labels.instance }}鏡像體積過大"
description:"鏡像大小{{ $value | humanize }},超過500MB"
-alert:BuildCacheUsageHigh
expr:docker_builder_cache_bytes/docker_builder_cache_limit_bytes>0.85
for:5m
labels:
severity:warning
annotations:
summary:"構(gòu)建緩存使用率過高"
description:"緩存使用率{{ $value | humanizePercentage }}"
5.3 備份與恢復(fù)
5.3.1 備份策略
#!/bin/bash
# Dockerfile和構(gòu)建配置備份腳本
# 建議納入Git版本管理,這里是額外的備份
BACKUP_DIR="/backup/dockerfile/$(date +%Y%m%d)"
mkdir -p${BACKUP_DIR}
# 備份所有項(xiàng)目的Dockerfile
find /data/projects -name"Dockerfile*"-execcp --parents {}${BACKUP_DIR}/ ;
# 備份.dockerignore
find /data/projects -name".dockerignore"-execcp --parents {}${BACKUP_DIR}/ ;
# 備份構(gòu)建腳本
find /data/projects -name"build.sh"-execcp --parents {}${BACKUP_DIR}/ ;
# 導(dǎo)出構(gòu)建緩存(可選,體積可能很大)
# docker buildx prune --keep-storage 10GB
echo"Backup completed:${BACKUP_DIR}"
5.3.2 恢復(fù)流程
恢復(fù)Dockerfile:從Git倉庫或備份目錄恢復(fù)
重建構(gòu)建緩存:第一次構(gòu)建會(huì)比較慢,后續(xù)構(gòu)建會(huì)自動(dòng)建立緩存
驗(yàn)證構(gòu)建:docker build -t test:latest .確認(rèn)構(gòu)建正常
驗(yàn)證鏡像:運(yùn)行容器并執(zhí)行健康檢查
六、總結(jié)
6.1 技術(shù)要點(diǎn)回顧
基礎(chǔ)鏡像選擇:alpine變體體積最?。?-7MB),slim變體兼容性最好,distroless最安全。根據(jù)應(yīng)用語言和依賴選擇合適的基礎(chǔ)鏡像
多階段構(gòu)建:編譯環(huán)境和運(yùn)行環(huán)境分離,Go應(yīng)用可以從800MB壓縮到20MB,Java應(yīng)用從800MB壓縮到180MB
構(gòu)建緩存利用:指令順序按變更頻率從低到高排列,依賴安裝和源碼復(fù)制分開,緩存命中時(shí)構(gòu)建時(shí)間從分鐘級(jí)降到秒級(jí)
安全基線:非root用戶運(yùn)行、固定版本基礎(chǔ)鏡像、不在鏡像中存儲(chǔ)密鑰、集成安全掃描
BuildKit特性:緩存掛載(--mount=type=cache)、密鑰掛載(--mount=type=secret)、并行構(gòu)建,是現(xiàn)代Dockerfile的標(biāo)配
6.2 進(jìn)階學(xué)習(xí)方向
多架構(gòu)構(gòu)建:使用docker buildx構(gòu)建同時(shí)支持amd64和arm64的鏡像,適配混合架構(gòu)部署
學(xué)習(xí)資源:Docker官方文檔 Multi-platform images
實(shí)踐建議:在CI/CD中配置多架構(gòu)構(gòu)建流水線
鏡像供應(yīng)鏈安全:鏡像簽名(Cosign/Notary)、SBOM生成、漏洞掃描集成
學(xué)習(xí)資源:Sigstore項(xiàng)目、Trivy文檔
實(shí)踐建議:在Harbor中啟用鏡像簽名驗(yàn)證策略
構(gòu)建性能優(yōu)化:遠(yuǎn)程構(gòu)建緩存、分布式構(gòu)建、構(gòu)建集群
學(xué)習(xí)資源:BuildKit GitHub倉庫
實(shí)踐建議:配置registry類型的遠(yuǎn)程緩存,多個(gè)CI Runner共享構(gòu)建緩存
6.3 參考資料
Dockerfile reference- 官方指令參考
Best practices for writing Dockerfiles- 官方最佳實(shí)踐
BuildKit- BuildKit源碼和文檔
dive- 鏡像層分析工具
Trivy- 容器安全掃描工具
distroless- Google的最小化基礎(chǔ)鏡像
附錄
A. 命令速查表
# 構(gòu)建命令
docker build -t 名稱:tag . # 基本構(gòu)建
docker build -f Dockerfile.prod -t 名稱:tag . # 指定Dockerfile
docker build --no-cache -t 名稱:tag . # 不使用緩存
docker build --build-arg KEY=VALUE -t 名稱:tag .# 傳入構(gòu)建參數(shù)
docker build --target stage-name -t 名稱:tag . # 構(gòu)建到指定階段
docker buildx build --platform linux/amd64,linux/arm64 -t 名稱:tag --push .# 多架構(gòu)構(gòu)建
# 鏡像分析
dockerhistory鏡像:tag # 查看分層歷史
docker inspect 鏡像:tag # 查看鏡像元數(shù)據(jù)
docker images --filter"dangling=true" # 查看dangling鏡像
dive 鏡像:tag # 交互式分析鏡像層
# 緩存管理
docker builder prune # 清理構(gòu)建緩存
docker buildx du # 查看緩存使用量
docker buildx prune --keep-storage 10GB # 保留10GB緩存
# 安全掃描
trivy image 鏡像:tag # 掃描鏡像漏洞
docker scout cves 鏡像:tag # Docker官方掃描
B. Dockerfile指令詳解
指令
作用
示例
注意事項(xiàng)
FROM
指定基礎(chǔ)鏡像
FROM alpine:3.19
必須是第一條指令(ARG除外)
RUN
執(zhí)行命令
RUN apt-get update
每條RUN創(chuàng)建一層,合并減少層數(shù)
COPY
復(fù)制文件
COPY src/ /app/src/
推薦用COPY而不是ADD
ADD
復(fù)制文件(支持解壓和URL)
ADD app.tar.gz /app/
僅在需要自動(dòng)解壓時(shí)使用
WORKDIR
設(shè)置工作目錄
WORKDIR /app
不要用RUN cd,用WORKDIR
ENV
設(shè)置環(huán)境變量
ENV NODE_ENV=production
會(huì)寫入鏡像元數(shù)據(jù),不要放密鑰
ARG
構(gòu)建時(shí)參數(shù)
ARG VERSION=1.0
只在構(gòu)建時(shí)有效,運(yùn)行時(shí)不存在
EXPOSE
聲明端口
EXPOSE 8080
僅聲明作用,不實(shí)際映射端口
USER
切換用戶
USER app
之后的指令以該用戶身份執(zhí)行
ENTRYPOINT
容器入口點(diǎn)
ENTRYPOINT ["java","-jar","app.jar"]
用exec形式(JSON數(shù)組)
CMD
默認(rèn)命令/參數(shù)
CMD ["--port","8080"]
可被docker run參數(shù)覆蓋
HEALTHCHECK
健康檢查
HEALTHCHECK CMD curl -f http://localhost/
生產(chǎn)環(huán)境必須配置
LABEL
元數(shù)據(jù)標(biāo)簽
LABEL version="1.0"
用于鏡像管理和追溯
VOLUME
聲明卷
VOLUME /data
僅聲明,實(shí)際掛載在run時(shí)指定
STOPSIGNAL
停止信號(hào)
STOPSIGNAL SIGTERM
默認(rèn)SIGTERM,一般不需要改
C. 術(shù)語表
術(shù)語
英文
解釋
構(gòu)建上下文
Build Context
docker build時(shí)發(fā)送給Docker daemon的文件集合,由.dockerignore控制范圍
鏡像層
Image Layer
Dockerfile中每條指令生成的只讀文件系統(tǒng)層,多層疊加組成完整鏡像
多階段構(gòu)建
Multi-stage Build
一個(gè)Dockerfile中使用多個(gè)FROM,前面階段的產(chǎn)物可以復(fù)制到后面階段
BuildKit
BuildKit
Docker新一代構(gòu)建引擎,支持并行構(gòu)建、緩存掛載等高級(jí)特性
構(gòu)建緩存
Build Cache
Docker緩存已構(gòu)建的層,未變更的層直接復(fù)用,加速構(gòu)建
distroless
Distroless
Google維護(hù)的最小化容器鏡像,只包含應(yīng)用運(yùn)行時(shí),沒有shell和包管理器
scratch
Scratch
Docker的空白基礎(chǔ)鏡像,0字節(jié),用于靜態(tài)編譯的程序
dangling鏡像
Dangling Image
沒有tag的鏡像,通常是被新構(gòu)建覆蓋的舊鏡像
OCI
Open Container Initiative
容器鏡像和運(yùn)行時(shí)的開放標(biāo)準(zhǔn)
-
Linux
+關(guān)注
關(guān)注
88文章
11763瀏覽量
219079 -
python
+關(guān)注
關(guān)注
57文章
4877瀏覽量
90071 -
鏡像
+關(guān)注
關(guān)注
0文章
180瀏覽量
11650
原文標(biāo)題:Dockerfile 最佳實(shí)踐:構(gòu)建高效、輕量、安全鏡像的完整指南
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
構(gòu)建ARM64版本nacos docker鏡像
一文詳解DockerFile基礎(chǔ)知識(shí)
Dockerfile構(gòu)建環(huán)境報(bào)錯(cuò)如何解決?
全面詳解Dockerfile文件
鏡像構(gòu)建Dockerfile的介紹
Dockerfile的最佳實(shí)踐
Docker入門指南之什么是Dockerfile
新一代更強(qiáng)大的鏡像構(gòu)建工具Earthly
Dockerfile定義Docker鏡像的構(gòu)建過程
如何使用dockerfile創(chuàng)建鏡像
手動(dòng)構(gòu)建Docker鏡像的方法
提升DevOps效率,從基礎(chǔ)到進(jìn)階的Dockerfile編寫技巧
Dockerfile鏡像制作與Docker-Compose容器編排
Docker-鏡像的分層-busybox鏡像制作
基于Docker鏡像逆向生成Dockerfile
使用Dockerfile構(gòu)建鏡像的詳細(xì)步驟
評(píng)論