写给懒猫微服玩家的容器小书 Docker篇(二):《镜像旅馆的秘密》
一直想写一本容器小书,真好懒猫基本都做了容器化,所以把这部分分享出来。不同的是,懒猫微服中使用 pg-docker 来替代 docker 命令,使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主,这样子既学会了 docker 知识,也能够在懒猫微服上启动 Docker 服务。
《镜像旅馆的秘密》讲的是 Docker 镜像的原理、分层结构、生命周期、Docker Hub 上传与下载、常见镜像命令详解
🏰 开篇:进入镜像旅馆
自从小李用 Docker 成功打包并运行了自己的 Flask 项目,他的开发效率飞快提高。
某天,老周带他来到一座巨大的数字建筑——Docker 镜像旅馆。
“这是你所有镜像的家,”老周说,“也是全球程序员共享旅程资源的中转站。”
镜像旅馆里,层层叠叠地存放着成千上万个镜像,就像一栋模块化的高楼大厦。
🧱 镜像的本质:一层一层搭起来的文件系统
老周告诉小李:
“镜像(Image)其实是一个只读的分层文件系统。你写的每一条 Dockerfile 指令,都会构成一层 Layer。”
比如这个简单的 Dockerfile:
1 | FROM python:3.11-slim |
对应的镜像层如下:
FROM
→ 拉了一个基础镜像层(Python 3.11)WORKDIR
→ 添加一个设置工作目录的 LayerCOPY
→ 拷贝代码文件的 LayerRUN
→ 安装依赖的新 LayerCMD
→ 容器入口(不是 Layer,但存配置)
💡 小知识:Docker 会尽量缓存和复用前面的 Layer,节省时间和存储。
🧪 镜像命令全攻略
小李打开终端,开始探索这些镜像的日常操作。
1. 查看本地镜像:
1 | docker images |
输出示例:
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
解释:
REPOSITORY
:镜像名TAG
:标签(版本号)IMAGE ID
:镜像唯一标识符SIZE
:镜像大小
2. 查看镜像历史构建过程(看每层):
1 | docker history my-flask-app |
3. 删除镜像:
1 | docker rmi my-flask-app |
(⚠️ 若有容器在运行该镜像,需先停止并删除容器)
🗂 镜像仓库:Docker Hub
老周指了指旅馆大堂里的一个巨大电梯:
“这是 Docker Hub,全球最大的镜像共享仓库。”
在这里,小李能下载成千上万的开源镜像,也能上传自己的。
登录 Docker Hub:
1 | docker login |
(需要先注册账号)
下载镜像:
1 | docker pull nginx |
这会从 Docker Hub 拉取最新版本的 nginx
镜像
指定版本拉取:
1 | docker pull redis:6.2 |
如果 docker run/pull 有问题,那么可以通过
lzc-cli appstore copy-image your-images
来使用懒猫的镜像仓库。
(相当于拉取 redis
仓库中 tag 为 6.2
的镜像)
上传镜像(先打标签):
1 | docker tag my-flask-app yourdockerhubname/my-flask-app:1.0 |
📦 镜像 Tag 与版本控制
老周问:“小李,你知道为什么镜像都有个 :latest
吗?”
小李说:“这是默认版本号吧?”
“对,但我们不能依赖它。开发、测试、生产应使用明确版本号,比如 1.0、20240321 等。”
Docker 镜像是通过 tag
来区分版本的:
1 | docker build -t myapp:1.0 . |
你可以为同一个镜像打多个标签,对应不同场景使用。
🔍 镜像体积优化技巧
小李注意到镜像越来越大了,占了很多硬盘空间。
老周给了他几点建议:
使用轻量级基础镜像:
- 比如
python:3.11-slim
代替python:3.11
- 比如
合并
1
RUN
命令,减少层数:
1
RUN apt update && apt install -y git && rm -rf /var/lib/apt/lists/*
删除临时文件:
- 安装后清理缓存,避免垃圾文件残留
多阶段构建(进阶):
- 构建和运行使用不同的镜像阶段
📂 镜像保存与迁移
后来小李想把自己的镜像传给另一位没有 Docker Hub 的同事。
他用到了镜像导出与导入:
导出镜像为 .tar
文件:
1 | docker save my-flask-app > myapp.tar |
导入镜像:
1 | docker load < myapp.tar |
镜像打包成离线文件,便于携带与备份。
🔍 深入 Layer 实战:查看镜像内容
小李很好奇,镜像到底长什么样?
老周教他运行容器并进到里面:
1 | docker run -it --rm my-flask-app /bin/bash |
这样他就能直接进入容器的 Linux 环境,像在服务器上一样查看文件结构:
1 | ls / |
他终于明白,每个镜像就像是一个“静态快照”,而容器才是“它的动态运行副本”。
📊 镜像生命周期总结表
操作 | 命令 |
---|---|
查看本地镜像 | docker images |
构建新镜像 | docker build -t name . |
删除镜像 | docker rmi 镜像名 |
下载镜像 | docker pull 镜像名[:tag] |
上传镜像 | docker push 镜像名[:tag] |
镜像打包导出 | docker save > xxx.tar |
镜像导入还原 | docker load < xxx.tar |
镜像历史层查看 | docker history 镜像名 |
🎬 尾声:镜像旅馆的门票
小李现在拥有了多个镜像,搭配不同的版本、依赖、语言,像积木一样可以快速组合各种环境。
“这就像 Minecraft 的世界地图,每张都是一个镜像。”小李说。
老周点点头:“没错,镜像只是开始,真正的冒险——是容器运行起来后的世界。”
🧭 第二章小结
- 镜像是构建环境的基础模板,支持版本控制、缓存加速、快速构建
- 可以上传到 Docker Hub 或导出
.tar
进行离线传输 - 管理命令要熟练掌握:
build
、pull
、push
、rmi
、tag
、history
- 优化镜像大小要用 slim 基础镜像、合并命令、清理缓存
✨ 增补内容:镜像的高级技能与实战应用
🧪 多阶段构建:精致分工,极限瘦身
有一次,小李需要构建一个使用 npm
打包前端、Python 启动后端的项目。打包工具很多、依赖也重,他担心镜像太大。
老周说:“你要学会多阶段构建(multi-stage build),把构建阶段和运行阶段分开。”
多阶段构建的目标是:编译用谁都行,最终镜像要最小。
示例:Node 构建 + nginx 托管
1 | # 第一阶段:使用 node 构建前端 |
- 第一阶段装依赖、打包代码
- 第二阶段只取编译结果,不用带上 node/npm 等工具
小李一测试,镜像体积从 300MB 降到 25MB,部署速度快了 10 倍!
🧩 使用 .dockerignore
:镜像防垃圾机制
构建时,小李发现镜像中夹杂了 .git
、node_modules
、__pycache__
……
老周摇头道:“你忘了 .dockerignore
文件。”
就像 .gitignore
一样,.dockerignore
告诉 Docker 哪些文件在构建镜像时要排除。
示例:
1 | __pycache__/ |
这个文件放在 Dockerfile 同目录下,能显著加快构建速度和减小镜像大小。
📦 自建私有镜像仓库(Registry)
当公司禁止使用 Docker Hub 时,小李开始尝试搭建自己的镜像库。
老周带他部署了一个本地私有镜像仓库(基于 Docker 官方镜像):
1 | docker run -d -p 5000:5000 --restart=always --name registry registry:2 |
现在他可以:
推送到私库:
1
2docker tag myapp localhost:5000/myapp
docker push localhost:5000/myapp拉取镜像:
1
docker pull localhost:5000/myapp
适合公司内部使用,搭配 Nexus、Harbor 可实现更完善的权限、审计、镜像管理等功能。(比如懒猫的 copy-image)
🧠 镜像调试技巧:如何从镜像中探查问题?
如果小李的镜像出错了,他可以通过两种方式“探测”镜像内部:
方法 1:运行一个交互式 shell 容器
1 | docker run -it myapp /bin/bash |
(如果 bash 不存在,可以用 /bin/sh
)
方法 2:打开已有容器的终端
1 | docker exec -it container_id /bin/bash |
通过 ls
、cat
、which
、env
命令,可以检查:
- 文件有没有 COPY 进去?
pip install
是否安装成功?- 环境变量是否丢失?
🔐 镜像安全:不要把密码打包进镜像!
小李曾在 Dockerfile 里写了:
1 | ENV DB_PASSWORD=123456 |
老周当场拍桌:“你这是把钥匙写死进容器了!”
最佳做法:
- 在容器运行时注入环境变量(例如使用
.env
文件 +--env
参数) - 使用
docker secret
或 KMS 管理 - 使用 BuildKit 的
--secret
机制加密构建时参数(高级用法)
🧾 镜像标签管理规范建议
小李准备上线,他开始给镜像打各种 tag:
1 | docker build -t myapp:1.0.0 . |
老周说:
“tag 是镜像的版本名,不要用
latest
作为生产环境唯一标识。”
推荐命名规范:
标签 | 含义 |
---|---|
myapp:1.0.0 |
语义化版本控制 |
myapp:20240324 |
构建时间戳 |
myapp:prod |
环境标识 |
myapp:feature-login |
功能分支测试 |
🔁 镜像缓存失效调试技巧
有时候构建镜像时,小李发现修改了某个文件,Docker 却好像没更新。
老周点拨他:“那是缓存搞的鬼。”
方法一:强制跳过缓存
1 | docker build --no-cache -t myapp . |
方法二:注意 COPY 顺序影响缓存命中
Docker 会从上到下按顺序缓存。如果把变化频繁的文件 COPY 太早,就会导致缓存失效:
1 | COPY requirements.txt . # OK,变动少,适合先复制 |
✨ 技巧:越是稳定的文件,越早 COPY,利于缓存复用。
📘 第二章 · 补充总结更新版
技术点 | 命令 / 说明 |
---|---|
多阶段构建 | FROM ... AS builder + COPY --from=builder |
忽略文件 | .dockerignore 文件 |
镜像上传私库 | docker push localhost:5000/myapp |
开启 BuildKit | DOCKER_BUILDKIT=1 docker build ... |
进入镜像内调试 | docker run -it 镜像 /bin/bash |
镜像版本管理建议 | 避免乱用 latest ,使用语义化 tag |
跳过缓存构建 | docker build --no-cache ... |
小李站在镜像旅馆的屋顶,看着一层层高楼像乐高积木一样堆叠而起。
他感到激动——他已经不再为“部署”苦恼,而是拥有了一个随时可打包、可还原的开发宇宙。
老周说:“你的旅程才刚刚开始,容器的世界比镜像更复杂。”
写给懒猫微服玩家的容器小书 Docker篇(二):《镜像旅馆的秘密》
https://xu-hardy.github.io/写给懒猫微服玩家的容器小书-docker篇(二):《镜像旅馆的秘密》/