Dockerfile 是容器的"配方"。写好 Dockerfile,是高效使用容器的基础。
Dockerfile 基础#
第一个 Dockerfile
# 基于什么镜像开始
FROM python:3.12-slim
# 设置工作目录(后续命令都在这个目录执行)
WORKDIR /app
# 复制依赖文件(先复制 requirements 是为了利用层缓存)
COPY requirements.txt .
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 声明容器运行时监听的端口(文档性质,不影响运行)
EXPOSE 8000
# 容器启动时执行的命令
CMD ["python", "app.py"]构建与运行
# 构建镜像
docker build -t myapp:1.0 .
# └─┘ └─────┘ └─┘
# 打标签 名称:版本 构建上下文(此例为当前目录;Dockerfile 可由 -f 另选)
# 运行
docker run -d -p 8000:8000 myapp:1.0核心指令速查#
| 指令 | 作用 | 示例 |
|---|---|---|
FROM |
指定阶段基础镜像;全局 ARG 和 parser directives 可出现在首个 FROM 前 | FROM node:20-alpine |
WORKDIR |
设置工作目录,不存在则自动创建 | WORKDIR /app |
COPY |
从构建上下文或指定 stage/image/context 复制 | COPY . /app/ |
ADD |
类似 COPY,还支持 URL 下载和自动解压 tar | ADD archive.tar.gz /app/ |
RUN |
在镜像构建时执行命令 | RUN apt update && apt install -y curl |
ENV |
设置环境变量(构建时和运行时都生效) | ENV NODE_ENV=production |
ARG |
构建参数(只在构建时可用) | ARG VERSION=1.0 |
EXPOSE |
声明端口(文档性质,实际映射需 -p) |
EXPOSE 3000 |
CMD |
容器启动时执行的命令(可被 docker run 末尾参数覆盖) |
CMD ["node", "server.js"] |
ENTRYPOINT |
容器入口程序(可用 --entrypoint 覆盖;exec 形式 CMD 通常作默认参数) | ENTRYPOINT ["python"] |
USER |
切换运行用户(安全加固) | USER appuser |
HEALTHCHECK |
健康检查命令 | HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1 |
CMD vs ENTRYPOINT
# 方式 1:只有 CMD(可被覆盖)
CMD ["python", "app.py"]
# docker run myapp node server.js ← 会覆盖 CMD
# 方式 2:ENTRYPOINT + CMD(可用 --entrypoint 覆盖 ENTRYPOINT,CMD 作为默认参数)
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run myapp --version ← 相当于 python --version
# 方式 3:ENTRYPOINT 执行脚本
ENTRYPOINT ["/docker-entrypoint.sh"]
# 脚本可以做初始化(数据库迁移、权限设置),最后 exec "$@" 启动 CMD多阶段构建(Multi-stage Build)#
以下片段彼此独立,基于示例项目的 requirements/package-lock 与入口文件;不能直接拼接成一个通用镜像。
目标:构建阶段用大镜像(含编译工具),最终产物用小镜像(只含运行时)。
单阶段(差)
FROM node:20
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["node", "dist/index.js"]
# 最终镜像包含构建依赖和源码,具体大小以实际构建测量多阶段(好)
# 阶段 1:构建(build stage)
FROM node:20 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# 阶段 2:运行(production stage)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json .
RUN npm ci --omit=dev
USER node
CMD ["node", "dist/index.js"]
# 最终阶段只装生产依赖和运行产物;需验证本机模块 ABI 与运行需求效果:可减少构建工具和无关文件,实际体积、漏洞数量及运行兼容性需构建后检查。
层缓存优化#
Dockerfile 指令形成构建步骤与元数据,不是每条都生成文件系统层;RUN/COPY 等通常改变文件系统。缓存命中取决于指令、上下文及依赖输入。
层缓存规则
FROM python:3.12-slim # 层 1
WORKDIR /app # 层 2
COPY requirements.txt . # 层 3 ← 如果 requirements.txt 没变,这层用缓存
RUN pip install -r requirements.txt # 层 4 ← 依赖不装在上层,也用缓存
COPY . . # 层 5 ← 源代码变了,这层和后面都重新构建
CMD ["python", "app.py"] # 层 6关键原则:把不常变的放前面,常变的放后面。
# ✅ 好:先 COPY 依赖文件(不常变),再 COPY 代码(常变)
COPY package.json package-lock.json .
RUN npm ci
COPY . .
# ❌ 差:先 COPY 全部(轻微改动就废掉所有缓存)
COPY . .
RUN npm ci并行构建
# 启用 BuildKit(Docker 23+ 默认, 更老版本需要设置)
export DOCKER_BUILDKIT=1
# BuildKit 可并行不相互依赖的阶段/构建图节点;同阶段有先后依赖的 RUN 仍顺序执行
docker build -t myapp .优化技巧速查#
# 1. 合并 RUN 减少层数(用 && 连接)
RUN apt-get update && apt-get install -y curl vim && rm -rf /var/lib/apt/lists/*
# 2. 使用 .dockerignore 排除不需要的文件
# .dockerignore:
# node_modules
# .git
# *.md
# .env
# 3. 指定非 root 用户
RUN useradd -m appuser
USER appuser
# 4. 用 Alpine 等精简镜像(需验证 musl、原生依赖与调试工具兼容性)
FROM node:20-alpine # 大小依版本及架构
# vs FROM node:20 # 实际大小需检查
# 5. 用 slim 变体(平衡大小和兼容性)
FROM python:3.12-slim # 大小依版本及架构
# vs FROM python:3.12 # 实际大小需检查
# 6. 明确版本;示例版本是历史示意,标签仍可变,生产可进一步锁 digest
FROM node:20.11.1-alpine3.19
# 而不是 FROM node:latest(今天和明天拉的镜像可能不同)构建秘密与入口行为#
ARG/ENV、COPY 和普通 RUN 都不能安全保存秘密;删除镜像中的文件也不保证旧层无秘密。构建凭据采用 BuildKit secret/SSH mounts,并检查是否被构建命令写入产物。用 exec 形式入口或初始化脚本末尾的 exec "$@" 正确传递停止信号。示例镜像标签是教学输入,并非当前受支持版本推荐。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。