Dockerfile 从入门到精通:构建可复用、高效、安全的容器镜像
在《四、制作/更改镜像》中,我们通过 docker commit 手动创建镜像,但这种方式存在不可重复、难维护、镜像臃肿 等致命缺陷。Dockerfile 的出现彻底改变了这一局面------它用声明式脚本定义镜像构建过程,成为现代 DevOps 的基石。
为什么必须用 Dockerfile?
- ✅ 版本控制:Dockerfile 可纳入 Git 管理
- ✅ 自动化构建:CI/CD 流水线直接调用
- ✅ 分层缓存:加速重复构建
- ✅ 最小化镜像:精准控制每一层内容
一、Dockerfile 核心原理
构建流程深度解析
Docker 构建过程本质是逐层叠加:
- 启动临时容器 :基于
FROM镜像 - 执行指令 :如
RUN apt-get install - 提交新层 :类似
docker commit - 清理临时容器:仅保留镜像层
- 循环执行:直到所有指令完成

💡 关键认知 :
每条指令生成一个只读层,最终镜像是所有层的叠加。删除文件不会减小镜像体积(因历史层仍存在)!
二、核心指令详解(附最佳实践)
| 指令 | 作用 | 最佳实践 |
|---|---|---|
FROM |
基础镜像 | 优先选 alpine(<5MB)或官方 slim 镜像 |
RUN |
安装软件 | 合并命令:RUN apt-get update && apt-get install -y ... && rm -rf /var/lib/apt/lists/* |
COPY |
复制文件 | 优于 ADD(避免自动解压陷阱) |
ENV |
环境变量 | 集中管理配置(如 JAVA_HOME) |
EXPOSE |
声明端口 | 仅文档作用,运行时仍需 -p 映射 |
WORKDIR |
工作目录 | 替代 RUN cd ...(避免路径混乱) |
CMD |
默认命令 | 可被 docker run 覆盖 |
ENTRYPOINT |
入口点 | 与 CMD 组合实现参数传递 |
⚠️
ADDvsCOPY:
ADD会自动解压 tar 文件(可能非预期行为)ADD支持 URL 下载(但建议用RUN wget)
→ 99% 场景用COPY!
三、实战案例:构建 Centos + Java 镜像
需求分析
- 基础系统:CentOS
- 必备工具:vim, ifconfig(net-tools)
- 运行环境:JDK 8
- 痛点:官方 CentOS 镜像无 yum 源(国内网络慢)
优化版 Dockerfile
dockerfile
# 使用精简基础镜像
FROM 192.168.100.50:5000/centos:7
# 维护者信息(已弃用,建议用 LABEL)
LABEL maintainer="xiani<123@qq.com>"
# 设置工作目录
WORKDIR /usr/local
# 复制 JDK(比 ADD 更明确)
COPY jdk-8u121-linux-x64.tar.gz /usr/local/
# 解压并清理(关键!减少镜像体积)
RUN tar -zxf jdk-8u121-linux-x64.tar.gz && \
rm -f jdk-8u121-linux-x64.tar.gz
# 配置环境变量
ENV JAVA_HOME=/usr/local/jdk1.8.0_121 \
JRE_HOME=/usr/local/jdk1.8.0_121/jre \
CLASSPATH=/usr/local/jdk1.8.0_121/lib/dt.jar:/usr/local/jdk1.8.0_121/lib/tools.jar:/usr/local/jdk1.8.0_121/jre/lib \
PATH=/usr/local/jdk1.8.0_121/bin:$PATH
# 安装必要工具(若网络允许)
# RUN yum install -y vim net-tools && yum clean all
# 声明端口(文档作用)
EXPOSE 80
# 默认命令
CMD ["/bin/bash"]
🔧 关键优化点:
- 合并 RUN 指令:避免中间层残留 tar 包
- 显式 COPY:清晰表达意图
- 多行 ENV:提升可读性
构建与验证
bash
# 构建镜像(注意末尾的 .)
docker build -t centosjava:1.0 .
# 验证 JDK
docker run --rm centosjava:1.0 java -version
# 输出:java version "1.8.0_121"
# 验证环境变量
docker run --rm centosjava:1.0 env | grep JAVA_HOME
# 输出:JAVA_HOME=/usr/local/jdk1.8.0_121
📊 镜像体积对比:
- 未清理 tar 包:601MB
- 清理后:580MB(节省 21MB)
四、虚悬镜像(Dangling Images)深度解析
什么是虚悬镜像?
- 定义 :没有仓库名和标签(
<none>:<none>)的镜像 - 产生原因 :
- 重新构建同名镜像(旧镜像失去引用)
- 构建中途失败(残留中间层)
- 手动删除镜像标签
识别与清理
bash
# 查看虚悬镜像
docker images -f "dangling=true"
# 安全清理(仅删除虚悬镜像)
docker image prune
# 强制清理(包括停止的容器、构建缓存)
docker system prune -a
💡 生产建议 :
在 CI/CD 脚本末尾添加
docker image prune -f,避免磁盘占满
五、Dockerfile 高级技巧
1. 多阶段构建(Multi-stage Build)
场景:编译型语言(Go/Java)应用
dockerfile
# 第一阶段:编译
FROM maven:3.8 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests
# 第二阶段:运行
FROM openjdk:8-jre-slim
COPY --from=builder /app/target/app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]
效果:镜像体积从 500MB → 120MB!
2. 构建参数(ARG)
动态传入构建参数:
dockerfile
ARG JDK_VERSION=8u121
COPY jdk-${JDK_VERSION}-linux-x64.tar.gz /usr/local/
构建时指定:
bash
docker build --build-arg JDK_VERSION=8u292 -t myapp .
3. .dockerignore 文件
排除无关文件(类似 .gitignore):
gitignore
.git
*.log
target/
避免将敏感文件或大文件(如日志)复制到镜像
六、安全加固指南
1. 非 root 用户运行
dockerfile
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
2. 最小化攻击面
- 不安装 SSH 服务
- 删除 shell(
RUN rm /bin/sh) - 使用 distroless 镜像(仅含应用和 runtime)
3. 扫描漏洞
bash
# 官方扫描工具
docker scan centosjava:1.0
# 第三方工具(Trivy)
trivy image centosjava:1.0
七、常见陷阱与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 构建缓慢 | 每次都重装软件 | 合并 RUN 指令,利用缓存 |
| 镜像过大 | 未清理缓存文件 | apt-get clean && rm -rf /var/lib/apt/lists/* |
| 中文乱码 | 缺少 locale | RUN localedef -i zh_CN -f UTF-8 zh_CN.UTF-8 |
| 时区错误 | 默认 UTC | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime |
八、总结:Dockerfile 黄金法则
- 基础镜像最小化:Alpine > Slim > Full
- 指令合并:减少镜像层数
- 及时清理:删除临时文件、缓存
- 非 root 运行:提升安全性
- 多阶段构建:分离构建与运行环境
🚀 行动清单:
- 将现有
docker commit镜像重写为 Dockerfile- 为团队制定 Dockerfile 规范
- 在 CI 中集成镜像扫描
掌握 Dockerfile,你就拥有了打造标准化、轻量化、安全化 容器镜像的核心能力。下一步,我们将探索如何用 docker-compose 编排多容器应用!
🔗 权威参考:
- Dockerfile 官方最佳实践
- 《Docker ------ 从入门到实践》第 5 章