Dockerfile
Dockerfile 是用于构建 Docker 镜像的配置文件,通过指令描述如何从基础镜像逐步构建出目标镜像。
构建流程
编写 Dockerfile → docker build → 镜像 → docker run → 容器镜像构建流程
- Docker 从 Dockerfile 读取指令
- 每条指令创建一个新层
- Docker 复用未变化的层(利用缓存)
- 最终打包成镜像
基础指令
| 指令 | 说明 | 示例 |
|---|---|---|
FROM | 指定基础镜像 | FROM nginx:alpine |
RUN | 构建时执行命令 | RUN apt-get update |
COPY | 复制文件到镜像 | COPY ./app /app |
ADD | 添加文件(支持 URL/解压) | ADD app.tar.gz /app |
WORKDIR | 设置工作目录 | WORKDIR /app |
ENV | 设置环境变量 | ENV NODE_ENV=prod |
EXPOSE | 声明端口 | EXPOSE 8080 |
VOLUME | 声明数据卷 | VOLUME /data |
CMD | 容器启动命令 | CMD ["nginx"] |
ENTRYPOINT | 入口命令 | ENTRYPOINT ["nginx"] |
ARG | 构建参数 | ARG VERSION=1.0 |
LABEL | 元数据标签 | LABEL version="1.0" |
ADD vs COPY
尽量使用 COPY,只有需要自动解压或从 URL 添加时才用 ADD。
CMD vs ENTRYPOINT
两者的区别在于如何处理 docker run 后面的参数:
# CMD:命令可被覆盖
CMD ["ls", "-a"]
# docker run xx → 执行 ls -a
# docker run xx -l → 报错(-l 不是有效参数)
# docker run xx ls -l → 执行 ls -l(完全替换 CMD)
# ENTRYPOINT:命令追加参数
ENTRYPOINT ["ls", "-a"]
# docker run xx → 执行 ls -a
# docker run xx -l → 执行 ls -a -l(追加 -l)
# 常见组合:ENTRYPOINT + CMD
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# 运行容器时使用默认参数最佳实践
- 应用容器(Web 服务):使用
ENTRYPOINT,让运行时参数可追加 - 工具容器(一次性任务):使用
CMD,便于覆盖执行其他命令
多阶段构建
多阶段构建可以将构建环境和运行环境分开,最终只保留运行所需的最小镜像。
# ===== 阶段 1:构建 =====
FROM golang:1.21 AS builder
WORKDIR /app
# 复制依赖文件(利用缓存)
COPY go.mod go.sum ./
RUN go mod download
# 复制源码并构建
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp
# ===== 阶段 2:运行 =====
FROM alpine:latest
WORKDIR /root/
# 只复制编译产物
COPY --from=builder /app/myapp .
COPY --from=builder /app/config /config
# 使用非 root 用户
RUN adduser -D -g '' appuser
USER appuser
CMD ["./myapp"]多阶段构建优势
- 减小镜像体积:只打包运行产物,丢弃编译工具
- 提升安全性:减少攻击面,不包含构建依赖
- 清理临时文件:构建阶段的缓存、临时文件不会进入最终镜像
最佳实践
1. 利用构建缓存
将不常变化的指令放在前面,频繁变化的放在后面:
# ❌ 错误:每次都要重新安装依赖
COPY . .
RUN npm install
RUN npm run build
# ✅ 正确:先复制 package.json,利用缓存
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build2. 减少镜像层数
合并多条 RUN 指令:
# ❌ 多层(每个 RUN 创建一层)
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ 一层(用 && 连接,结束时清理)
RUN apt-get update && \
apt-get install -y nginx curl && \
rm -rf /var/lib/apt/lists/*3. 使用轻量级基础镜像
| 镜像 | 大小 | 适用场景 |
|---|---|---|
alpine | ~5MB | 生产环境首选 |
debian-slim | ~80MB | 需要 Debian 兼容性 |
ubuntu | ~80MB | 需要完整工具链 |
centos | ~200MB | 需要 CentOS 兼容性 |
# ❌ 使用完整镜像
FROM node:21
# ✅ 使用 Alpine 镜像(注意兼容性问题)
FROM node:21-alpine4. 使用 .dockerignore
在构建上下文中排除不需要的文件:
# .dockerignore
.git
.gitignore
node_modules
dist
*.log
.env*
<mark>Dockerfile</mark>
docker-compose.yml5. 使用非 root 用户
# 创建用户组和用户
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# 复制文件
COPY --chown=appuser:appgroup ./app /app
# 切换用户
USER appuser
CMD ["node", "/app/index.js"]安全建议
不要在镜像中存储敏感信息(如密码、密钥),使用环境变量、Secret 或配置文件挂载。
构建命令
# 基本构建
docker build -t myapp:1.0 .
# 指定 Dockerfile 名称和路径
docker build -f Dockerfile.dev -t myapp:dev .
# 构建时传递参数
docker build --build-arg VERSION=1.0 -t myapp:1.0 .
# 不使用缓存(强制重新构建)
docker build --no-cache -t myapp:1.0 .
# 添加标签
docker tag myapp:1.0 registry.example.com/myapp:1.0
# 查看构建历史
docker history myapp:1.0完整示例
Node.js 应用
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
# 先复制依赖文件,利用缓存
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 复制源码
COPY src ./src
RUN npm run build
FROM nginx:alpine
# 从 builder 阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Java 应用(Spring Boot)
# 构建阶段
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# 运行阶段
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Python 应用
FROM python:3.11-slim
WORKDIR /app
# 先复制依赖文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制源码
COPY . .
# 非 root 用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 构建失败 | 基础镜像不存在 | 检查镜像名称和版本 |
| 找不到文件 | COPY 路径错误 | 确认构建上下文和相对路径 |
| 端口冲突 | 容器端口已被占用 | 修改端口映射 -p 8080:80 |
| 权限错误 | 文件权限问题 | 使用 chown 或 USER 调整 |
| 构建超时 | 网络问题 | 配置镜像加速器 |
指令速查
# 基础镜像
FROM image:tag
# 元数据
LABEL maintainer="email@example.com"
LABEL version="1.0"
# 环境变量
ENV APP_HOME=/app
ENV DEBUG=false
# 构建参数
ARG VERSION=1.0
# 工作目录
WORKDIR /app
# 复制文件
COPY [--chown=user:group] source dest
# 添加文件
ADD [--chown=user:group] source dest # 支持 URL 和解压
# 运行命令
RUN command
# 暴露端口
EXPOSE 8080
# 数据卷
VOLUME ["/data"]
# 启动命令
CMD ["command", "arg1"]
ENTRYPOINT ["command"]