Dockerfile 基础
Dockerfile 是一个文本文件,包含了构建 Docker 镜像所需的所有指令。本文将教你如何编写高质量的 Dockerfile。
什么是 Dockerfile?
Dockerfile 是构建 Docker 镜像的配方(Recipe),它定义了:
- 🏗️ 使用哪个基础镜像
- 📦 安装哪些软件包
- 📁 复制哪些文件
- ⚙️ 设置哪些环境变量
- ▶️ 容器启动时执行什么命令
第一个 Dockerfile
创建一个简单的 Node.js 应用:
# 使用官方 Node.js 镜像作为基础FROM node:18-alpine
# 设置工作目录WORKDIR /app
# 复制 package.jsonCOPY package.json .
# 安装依赖RUN npm install
# 复制应用代码COPY . .
# 暴露端口EXPOSE 3000
# 启动命令CMD ["node", "app.js"]构建镜像
# 在 Dockerfile 所在目录执行docker build -t my-node-app .
# -t: 指定镜像名称和标签# .: 构建上下文路径(当前目录)运行容器
docker run -d -p 3000:3000 my-node-app常用 Dockerfile 指令
FROM - 指定基础镜像
# 使用官方镜像FROM node:18-alpine
# 使用特定版本FROM python:3.11-slim
# 多阶段构建FROM golang:1.21 AS builderWORKDIR - 设置工作目录
# 设置工作目录WORKDIR /app
# 后续的 RUN, COPY, CMD 等命令都在此目录下执行等价于:
cd /appCOPY - 复制文件
# 复制单个文件COPY app.js /app/
# 复制多个文件COPY app.js config.json /app/
# 复制目录COPY src/ /app/src/
# 复制所有文件COPY . /app/
# 使用通配符COPY *.json /app/ADD - 高级复制
# 基本用法同 COPYADD app.js /app/
# 自动解压 tar 文件ADD archive.tar.gz /app/
# 从 URL 下载(不推荐)ADD https://example.com/file.txt /app/RUN - 执行命令
# 安装软件包RUN apt-get update && apt-get install -y curl
# 多行命令RUN npm install && \ npm cache clean --force
# 多个 RUN 指令RUN apk add --no-cache python3RUN pip3 install flaskCMD - 默认启动命令
# exec 形式(推荐)CMD ["node", "app.js"]
# shell 形式CMD node app.js
# 为 ENTRYPOINT 提供默认参数CMD ["--help"]ENTRYPOINT - 入口点
# exec 形式ENTRYPOINT ["python", "app.py"]
# 配合 CMD 使用ENTRYPOINT ["python", "app.py"]CMD ["--port", "5000"]
# 运行时可以追加参数# docker run myapp --debug# 实际执行: python app.py --debugENV - 环境变量
# 设置单个变量ENV NODE_ENV production
# 设置多个变量ENV NODE_ENV=production \ PORT=3000 \ LOG_LEVEL=info
# 在后续指令中使用RUN echo $NODE_ENVARG - 构建参数
# 定义构建参数ARG NODE_VERSION=18
# 使用参数FROM node:${NODE_VERSION}-alpine
# 也可以设置默认值ARG APP_ENV=developmentENV APP_ENV=${APP_ENV}构建时传递参数:
docker build --build-arg NODE_VERSION=20 -t myapp .EXPOSE - 声明端口
# 声明容器监听的端口EXPOSE 3000
# 声明多个端口EXPOSE 3000 8080
# 指定协议EXPOSE 8080/tcpEXPOSE 8080/udpVOLUME - 定义卷
# 定义数据卷VOLUME /data
# 定义多个卷VOLUME ["/data", "/logs"]USER - 指定运行用户
# 创建用户RUN addgroup -g 1001 appgroup && \ adduser -D -u 1001 -G appgroup appuser
# 切换用户USER appuser
# 后续命令以 appuser 身份运行LABEL - 添加元数据
# 添加标签LABEL maintainer="your@email.com"LABEL version="1.0"LABEL description="My awesome app"
# 多行标签LABEL org.opencontainers.image.authors="your@email.com" \ org.opencontainers.image.version="1.0.0" \ org.opencontainers.image.description="Production app"完整示例
Node.js 应用
# 使用官方 Node.js 镜像FROM node:18-alpine
# 设置元数据LABEL maintainer="dev@example.com"LABEL version="1.0"
# 设置环境变量ENV NODE_ENV=production \ PORT=3000
# 创建应用目录WORKDIR /app
# 复制依赖文件COPY package*.json ./
# 安装生产依赖RUN npm ci --only=production && \ npm cache clean --force
# 复制应用代码COPY . .
# 创建非 root 用户RUN addgroup -g 1001 nodejs && \ adduser -D -u 1001 -G nodejs nodejs && \ chown -R nodejs:nodejs /app
# 切换到非 root 用户USER nodejs
# 暴露端口EXPOSE 3000
# 健康检查HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node healthcheck.js
# 启动命令CMD ["node", "server.js"]Python Flask 应用
FROM python:3.11-slim
# 设置工作目录WORKDIR /app
# 安装系统依赖RUN apt-get update && \ apt-get install -y --no-install-recommends \ gcc \ && rm -rf /var/lib/apt/lists/*
# 复制依赖文件COPY requirements.txt .
# 安装 Python 依赖RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码COPY . .
# 创建非 root 用户RUN useradd -m -u 1001 appuser && \ chown -R appuser:appuser /app
USER appuser
# 暴露端口EXPOSE 5000
# 启动命令CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]Go 应用(多阶段构建)
# 构建阶段FROM golang:1.21-alpine AS builder
WORKDIR /build
# 复制 go mod 文件COPY go.mod go.sum ./RUN go mod download
# 复制代码COPY . .
# 编译RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# 运行阶段FROM alpine:latest
# 安装 ca 证书RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 从构建阶段复制二进制文件COPY --from=builder /build/app .
# 暴露端口EXPOSE 8080
# 运行CMD ["./app"]Dockerfile 最佳实践
1. 使用 .dockerignore
创建 .dockerignore 文件排除不需要的文件:
node_modulesnpm-debug.log.git.env.DS_Store*.md2. 合理排序指令
# ✅ 好:不常变的指令放前面FROM node:18-alpineWORKDIR /appCOPY package*.json ./ # 依赖不常变RUN npm installCOPY . . # 代码经常变
# ❌ 不好:顺序不对,每次代码改变都要重新安装依赖FROM node:18-alpineWORKDIR /appCOPY . .RUN npm install3. 利用构建缓存
# 分离依赖安装和代码复制COPY package.json .RUN npm install # 只有 package.json 变化时才重新执行COPY . . # 代码变化不影响依赖安装4. 减少镜像层数
# ❌ 不好:多层RUN apt-get updateRUN apt-get install -y curlRUN apt-get install -y vim
# ✅ 好:合并RUN apt-get update && \ apt-get install -y \ curl \ vim && \ rm -rf /var/lib/apt/lists/*5. 清理临时文件
RUN apt-get update && \ apt-get install -y build-essential && \ # ... 构建操作 ... && \ apt-get purge -y build-essential && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/*6. 使用多阶段构建
# 构建阶段 - 体积大FROM node:18 AS builderWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .RUN npm run build
# 运行阶段 - 体积小FROM node:18-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCMD ["node", "dist/server.js"]构建上下文
什么是构建上下文?
执行 docker build 时,Docker 会将指定目录(通常是 .)的所有内容发送给 Docker daemon,这就是构建上下文。
# . 是构建上下文docker build -t myapp .
# 可以指定其他目录docker build -t myapp /path/to/context优化构建上下文
node_modules/.git/*.log.envcoverage/调试 Dockerfile
查看构建过程
# 显示详细构建过程docker build -t myapp . --progress=plain
# 不使用缓存docker build -t myapp . --no-cache进入中间层调试
# 构建到某一步失败后,使用最后成功的镜像docker run -it <image_id> sh使用 dive 分析镜像
# 安装 divebrew install dive # macOS
# 分析镜像dive myapp:latest小结
关键要点
- ✅ 使用官方基础镜像
- ✅ 选择轻量级变体(alpine/slim)
- ✅ 合理排序指令,利用缓存
- ✅ 合并 RUN 指令减少层数
- ✅ 使用 .dockerignore 排除文件
- ✅ 不要使用 root 用户运行应用
- ✅ 使用多阶段构建优化体积
- ✅ 清理临时文件和缓存