写给懒猫微服玩家的容器小书 Docker篇(二):《镜像旅馆的秘密》

一直想写一本容器小书,真好懒猫基本都做了容器化,所以把这部分分享出来。不同的是,懒猫微服中使用 pg-docker 来替代 docker 命令,使用 dockge 来执行 docker-compose。以下讲解以标准 docker 为主,这样子既学会了 docker 知识,也能够在懒猫微服上启动 Docker 服务。

《镜像旅馆的秘密》讲的是 Docker 镜像的原理、分层结构、生命周期、Docker Hub 上传与下载、常见镜像命令详解

🏰 开篇:进入镜像旅馆

自从小李用 Docker 成功打包并运行了自己的 Flask 项目,他的开发效率飞快提高。

某天,老周带他来到一座巨大的数字建筑——Docker 镜像旅馆

“这是你所有镜像的家,”老周说,“也是全球程序员共享旅程资源的中转站。”

镜像旅馆里,层层叠叠地存放着成千上万个镜像,就像一栋模块化的高楼大厦。


🧱 镜像的本质:一层一层搭起来的文件系统

老周告诉小李:

“镜像(Image)其实是一个只读的分层文件系统。你写的每一条 Dockerfile 指令,都会构成一层 Layer。”

比如这个简单的 Dockerfile:

1
2
3
4
5
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "main.py"]

对应的镜像层如下:

  1. FROM → 拉了一个基础镜像层(Python 3.11)
  2. WORKDIR → 添加一个设置工作目录的 Layer
  3. COPY → 拷贝代码文件的 Layer
  4. RUN → 安装依赖的新 Layer
  5. CMD → 容器入口(不是 Layer,但存配置)

💡 小知识:Docker 会尽量缓存和复用前面的 Layer,节省时间和存储。


🧪 镜像命令全攻略

小李打开终端,开始探索这些镜像的日常操作。

1. 查看本地镜像:

1
docker images

输出示例:

1
2
3
REPOSITORY     TAG        IMAGE ID       CREATED          SIZE
my-flask-app latest 123abc456def 2 minutes ago 125MB
python 3.11-slim 789xyz654hij 3 days ago 40MB

解释:

  • 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
2
docker tag my-flask-app yourdockerhubname/my-flask-app:1.0
docker push yourdockerhubname/my-flask-app:1.0

📦 镜像 Tag 与版本控制

老周问:“小李,你知道为什么镜像都有个 :latest 吗?”

小李说:“这是默认版本号吧?”

“对,但我们不能依赖它。开发、测试、生产应使用明确版本号,比如 1.0、20240321 等。”

Docker 镜像是通过 tag 来区分版本的:

1
2
docker build -t myapp:1.0 .
docker build -t myapp:latest .

你可以为同一个镜像打多个标签,对应不同场景使用。


🔍 镜像体积优化技巧

小李注意到镜像越来越大了,占了很多硬盘空间。

老周给了他几点建议:

  1. 使用轻量级基础镜像:

    • 比如 python:3.11-slim 代替 python:3.11
  2. 合并

    1
    RUN

    命令,减少层数:

    1
    RUN apt update && apt install -y git && rm -rf /var/lib/apt/lists/*
  3. 删除临时文件:

    • 安装后清理缓存,避免垃圾文件残留
  4. 多阶段构建(进阶):

    • 构建和运行使用不同的镜像阶段

📂 镜像保存与迁移

后来小李想把自己的镜像传给另一位没有 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
2
3
ls /
cd /app
cat requirements.txt

他终于明白,每个镜像就像是一个“静态快照”,而容器才是“它的动态运行副本”。


📊 镜像生命周期总结表

操作 命令
查看本地镜像 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 进行离线传输
  • 管理命令要熟练掌握:buildpullpushrmitaghistory
  • 优化镜像大小要用 slim 基础镜像、合并命令、清理缓存

✨ 增补内容:镜像的高级技能与实战应用


🧪 多阶段构建:精致分工,极限瘦身

有一次,小李需要构建一个使用 npm 打包前端、Python 启动后端的项目。打包工具很多、依赖也重,他担心镜像太大。

老周说:“你要学会多阶段构建(multi-stage build),把构建阶段和运行阶段分开。”

多阶段构建的目标是:编译用谁都行,最终镜像要最小。

示例:Node 构建 + nginx 托管

1
2
3
4
5
6
7
8
9
# 第一阶段:使用 node 构建前端
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

# 第二阶段:用 nginx 托管打包后的静态文件
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
  • 第一阶段装依赖、打包代码
  • 第二阶段只取编译结果,不用带上 node/npm 等工具

小李一测试,镜像体积从 300MB 降到 25MB,部署速度快了 10 倍!


🧩 使用 .dockerignore:镜像防垃圾机制

构建时,小李发现镜像中夹杂了 .gitnode_modules__pycache__……

老周摇头道:“你忘了 .dockerignore 文件。”

就像 .gitignore 一样,.dockerignore 告诉 Docker 哪些文件在构建镜像时要排除。

示例:

1
2
3
4
5
__pycache__/
.git/
node_modules/
.env
*.log

这个文件放在 Dockerfile 同目录下,能显著加快构建速度和减小镜像大小


📦 自建私有镜像仓库(Registry)

当公司禁止使用 Docker Hub 时,小李开始尝试搭建自己的镜像库。

老周带他部署了一个本地私有镜像仓库(基于 Docker 官方镜像):

1
docker run -d -p 5000:5000 --restart=always --name registry registry:2

现在他可以:

  • 推送到私库:

    1
    2
    docker 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

通过 lscatwhichenv 命令,可以检查:

  • 文件有没有 COPY 进去?
  • pip install 是否安装成功?
  • 环境变量是否丢失?

🔐 镜像安全:不要把密码打包进镜像!

小李曾在 Dockerfile 里写了:

1
ENV DB_PASSWORD=123456

老周当场拍桌:“你这是把钥匙写死进容器了!”

最佳做法:

  • 在容器运行时注入环境变量(例如使用 .env 文件 + --env 参数)
  • 使用 docker secret 或 KMS 管理
  • 使用 BuildKit 的 --secret 机制加密构建时参数(高级用法)

🧾 镜像标签管理规范建议

小李准备上线,他开始给镜像打各种 tag:

1
2
docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 myapp:latest

老周说:

“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
2
3
4
COPY requirements.txt .      # OK,变动少,适合先复制
RUN pip install -r requirements.txt

COPY . . # 后复制代码,避免频繁无效重建

✨ 技巧:越是稳定的文件,越早 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篇(二):《镜像旅馆的秘密》/

作者

Xu

发布于

2025-07-02

更新于

2025-07-01

许可协议

评论