跳到主要内容

Dockerfile 与镜像构建

Dockerfile 描述如何从基础镜像构建出应用镜像。前端/全栈最常改的是:钉版本、依赖与源码分层、多阶段把构建工具留在 builder。

概念回顾见 Docker 基础与概念。

通用习惯​

与语言无关的常见结构:

  1. 选准并钉住基础镜像(含主版本,避免无脑 latest)
  2. 先复制依赖清单、再安装依赖、最后复制源码 → 源码变动不致整层依赖重装(缓存友好)
  3. 多阶段构建:构建工具留在 builder,运行镜像只留运行所需
  4. .dockerignore 排除本地依赖目录、VCS、测试缓存,减小上下文、避免脏文件进镜像
  5. 配置走环境变量;密钥不要写进镜像层;默认非 root 运行(以团队规范为准)

通用 .dockerignore 思路(按栈增删):

.git/
**/node_modules/
**/.venv/
**/__pycache__/
**/dist/
**/build/
**/.pytest_cache/
**/.mypy_cache/

最小骨架​

FROM <runtime>:<version>-slim

WORKDIR /app

# 1) 只拷依赖声明
COPY <lock-or-manifest> ./
RUN <install-prod-deps>

# 2) 再拷源码
COPY . .
CMD ["<start-command>"]

层缓存何时失效​

做法结果
先 COPY 整个仓库再 npm ci / pip install改一行业务代码也重装依赖
先拷 lock/manifest,装依赖,再拷源码仅依赖变更时重装
未写 .dockerignore,把 node_modules 打进上下文上下文巨大、偶发脏文件覆盖

各语言示例(对照)​

Node(npm)​

FROM node:22-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]

Node 前端(多阶段 → nginx)​

FROM node:22-bookworm-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html

构建阶段产出静态资源;运行镜像只留 nginx + dist。

Python(pip)​

FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Python(uv,多阶段)​

FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:0.11.32 /uv /uvx /bin/
ENV UV_PYTHON_DOWNLOADS=0
WORKDIR /app

RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-dev --no-install-project

COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev --no-editable

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /app /app
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "main.py"]

说明:钉 uv 版本;--locked 按提交的锁文件安装;--no-dev 去掉开发依赖;本地 .venv 必须进 .dockerignore。

Python 侧解释器与包管理约定见知识库「编程语言 → Python」;容器实践以本篇为准。

Go(多阶段)​

FROM golang:1.22-bookworm AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

构建阶段用完整 Go 工具链;运行镜像只留静态二进制。需要 CGO / 动态链接时,运行阶段改用 debian/alpine 等带 libc 的基础镜像。

静态站点 / 反向代理​

若 dist 已在 CI 打好:

FROM nginx:1.27-alpine
COPY ./dist /usr/share/nginx/html

要点​

  1. 钉基础镜像;依赖与源码分层;务必 .dockerignore
  2. 多阶段:builder 装工具,runtime 只留运行所需
  3. 锁文件与 CI 安装命令一致,比追求「最新写法」更重要
  4. 密钥与可变配置不要写进镜像层

面试速记​

  1. 为什么不要随便用 latest? 构建不可复现,今天和明天可能不是同一基础镜像。
  2. 层缓存为什么失效? 某层输入(指令或 COPY 的文件)变了,该层及之后都要重建。
  3. 多阶段构建解决什么? 最终镜像不含编译器/devDependencies,体积更小、攻击面更小。
  4. .dockerignore 干什么? 缩小 build context,避免把 node_modules / .git 等脏进镜像。
  5. 依赖为什么要先于源码 COPY? 源码常变、依赖少变;分开才能复用依赖安装层。