第八篇:构建 Docker 镜像的艺术:从 Dockerfile 到多阶段构建
参考文档:
一、镜像是由哪些"层"组成的?
Docker 镜像并非单一文件,而是由多层(Layer)组成的只读文件系统叠加而成。
🧱 每一层来源:
- 镜像层 = Dockerfile 中每条指令(如
RUN
,COPY
,ADD
)创建一层 - 底层基础镜像层 + 应用构建层 + 配置层
🔁 层的优点:
- 缓存重用:只变更发生的层重新构建,提高构建速度
- 共享优化:多个镜像可共享相同层,节省空间
- 版本控制友好:变更可追踪与回滚
二、编写 Dockerfile 的最佳实践
Dockerfile
是构建镜像的"配方脚本",描述从哪开始、执行什么命令、如何构建运行环境。
🛠 Dockerfile 基本结构:
dockerfile
# 指定基础镜像
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 拷贝文件
COPY requirements.txt .
# 安装依赖
RUN pip install -r requirements.txt
# 拷贝源代码
COPY . .
# 定义容器启动命令
CMD ["python", "app.py"]
✅ 常用指令说明:
指令 | 作用 |
---|---|
FROM |
基础镜像 |
RUN |
执行命令并创建新镜像层 |
COPY |
拷贝本地文件至镜像内 |
WORKDIR |
设置默认工作目录 |
CMD |
默认启动命令 |
ENV |
设置环境变量 |
EXPOSE |
声明开放的端口(文档提示) |
🧼 编写建议:
- 合并多个
RUN
命令为一条,减少层数 - 明确
.dockerignore
文件排除无关文件(如.git/
,node_modules/
)
三、构建、标记和推送镜像的流程
🔧 构建镜像:
bash
docker build -t myapp:1.0 .
-t
:指定镜像名称和标签(tag).
:当前目录为构建上下文
🏷 为镜像打标签:
bash
docker tag myapp:1.0 myregistry.example.com/myapp:1.0
☁️ 推送到远程仓库:
bash
docker push myregistry.example.com/myapp:1.0
确保已使用 docker login
登录仓库。
四、构建缓存机制的使用
构建缓存能大幅提升镜像构建效率,但也需注意顺序与变更影响。
🚀 缓存命中规则:
- Docker 会按顺序逐条执行指令
- 当前指令和其上下文(依赖的文件)没有变化时,则命中缓存
🔥 示例:
dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt # ✅ 如果 requirements 没变则可用缓存
COPY . . # ❌ 若这里提前 COPY,会导致前面的 RUN 缓存失效
✅ 优化建议:
- 把稳定的文件(如依赖)提到前面
- 利用
--target
分阶段构建调试 - 修改频繁的代码层尽量靠后
五、多阶段构建:减小镜像体积的利器
"构建时用大镜像,运行时只带最小必需组件"
🎯 使用场景:
- 项目依赖大量构建工具(如 Java, Go, C++ 项目)
- 最终镜像希望精简(无 Node、无 Maven)
🏗 示例:Go 应用的多阶段构建
dockerfile
# 第一阶段:构建阶段
FROM golang:1.22 as builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 第二阶段:运行阶段
FROM alpine:latest
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
🎉 优点:
- 最终镜像干净、体积小
- 减少安全风险面(无编译器、构建工具残留)
- 更适合生产环境部署
六、总结与建议
概念/操作 | 说明 |
---|---|
镜像层 | 多层组成,支持缓存与共享 |
Dockerfile | 镜像构建蓝图,合理组织命令可加速构建 |
镜像标签 | 有助于版本管理与部署识别 |
构建缓存 | 避免重复构建,提高构建效率 |
多阶段构建 | 精简镜像,提升安全性与部署效率 |
第九篇:运行容器的实战技巧:网络、数据卷与多容器协作
参考文档:
一、容器端口映射:让服务暴露给外部访问
容器内部的服务默认无法被主机直接访问,需通过 端口映射 将其公开。
🔌 示例:
bash
docker run -d -p 8080:80 nginx
-p <宿主端口>:<容器端口>
- 访问 http://localhost:8080 即可访问容器内 nginx 的 80 端口
🌐 支持多端口、多容器绑定:
bash
docker run -p 5000:5000 myapp
docker run -p 3306:3306 mysql
二、自定义容器默认启动命令
镜像中通常会设置默认启动命令(CMD
),但可在运行时覆盖。
🛠 使用命令行覆盖:
bash
docker run ubuntu echo "Hello from container"
即使镜像默认执行 bash
,此处也会覆盖为 echo
。
🧩 ENTRYPOINT 与 CMD 区别:
指令类型 | 特点 |
---|---|
CMD |
可被 docker run 参数覆盖 |
ENTRYPOINT |
固定命令,后接参数不影响本体 |
三、数据持久化:容器生命周期外保存数据
容器删除时,其内部数据也会丢失。为实现持久化,可使用:
✅ Docker 卷(Volume):
bash
docker volume create mydata
docker run -v mydata:/data myapp
- 卷位于 Docker 管理目录下,不随容器删除
- 支持多个容器共享
🗂 绑定主机目录:
bash
docker run -v /host/path:/container/path myapp
- 直接映射宿主机文件夹
- 可用于调试、本地开发
四、共享本地文件与挂载策略
将本地代码、配置等文件共享给容器使用,适用于开发测试场景。
📁 示例:
bash
docker run -v $(pwd):/usr/src/app myapp
- 主机当前目录(代码)映射到容器工作目录
- 每次本地代码修改容器可实时读取
📎 挂载模式说明:
模式 | 说明 |
---|---|
读写(默认) | -v src:dest |
只读 | -v src:dest:ro |
SELinux 支持 | :z 或 :Z 标志 |
五、多容器应用:使用 Docker Compose 编排服务
大型应用常由多个服务组成,如:
- Web 应用(Node.js / Flask)
- 数据库(MySQL / PostgreSQL)
- 缓存(Redis)
- 消息队列(RabbitMQ)
使用 Docker Compose 编排管理多个容器。
🧾 示例 docker-compose.yml
:
yaml
version: "3"
services:
web:
image: myapp
ports:
- "8080:80"
db:
image: postgres
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
🚀 启动应用:
bash
docker compose up -d
- 自动创建网络、卷
- 服务间自动 DNS 发现(web 可通过
db
名称访问 PostgreSQL)
六、实践总结
操作点 | 命令或说明 |
---|---|
映射端口 | -p 宿主端口:容器端口 |
覆盖默认启动命令 | docker run 镜像 <新命令> |
数据卷持久化 | -v 卷名:/路径 或 -v 宿主路径:/路径 |
文件共享与挂载 | 本地映射容器目录(开发中常用) |
多容器应用编排 | 使用 docker-compose.yml 配置 |