2. 镜像管理:从拉取到构建的完整闭环

镜像就是Docker的“集装箱规范”。你拉取的镜像、自己构建的镜像,都保存在本地仓库里。我每天跟质谱数据打交道,最离不开的就是这几个命令。这次我把它们串起来讲——拉取、查看、删除、构建,再加上Dockerfile的核心写法。

核心知识点:

  • docker pull / docker push —— 远程与本地之间搬运镜像
  • docker images —— 查看本地镜像仓库
  • docker rmi / docker image prune —— 清理不再需要的镜像
  • docker build + Dockerfile —— 将你的分析环境固化为可复用的镜像
Docker 镜像管理流程 Docker 镜像管理生命周期 远程仓库 (Docker Hub) docker pull 本地镜像仓库 docker rmi / prune 已删除 / 清理 docker build Dockerfile 构建上下文 docker push docker images 查看 查看状态 💡 本地镜像既可以 push 到远程仓库备份 / 分享,也可以从远程仓库 pull 下来使用。 docker build 总是需要一个 Dockerfile,它定义了你如何“组装”镜像。

2.1 拉取镜像:让你的环境“空降”到本地

拉取镜像是使用 Docker 的第一道门槛。说白了,就是把远程仓库里的环境包下载到你电脑上。

最基本的命令长这样:

docker pull ubuntu:22.04

你想想看,ubuntu 是镜像名,22.04 是标签(tag)。如果不写 tag,Docker 默认给你拉 latest 标签。我个人习惯在拉取之前先查一下官方镜像的 tag 列表,免得拉到不兼容的版本。

命令 说明 我踩过的坑
docker pull ubuntu:22.04 拉取指定 tag 的镜像 ——
docker pull ubuntu 等价于 docker pull ubuntu:latest ❌ 坑:latest 可能不是你想要的版本
docker pull registry.cn-hangzhou.aliyuncs.com/xxx/yyy 从国内加速器或私有仓库拉取 ✅ 推荐:配置镜像加速器,避免超时

拉取时 Docker 会按层下载。每一层都有 hash 校验,你不用担心数据损坏。我记得有一次在实验室里,网络特别不稳定,一个 bioconductor 镜像拉了三遍才成功。后来我配了国内镜像加速器,问题就解决了。

加速配置小贴士: 修改 /etc/docker/daemon.json,添加 registry-mirrors,然后重启 Docker 服务。拉取质谱分析常用的 bioconductor 或 python:3.10-slim 镜像时,速度提升非常明显。

2.2 查看镜像:你的本地仓库里有什么

拉完之后,怎么确认它到了本地?用 docker images 查看。

docker images
# 或
docker image ls

输出会告诉你仓库名、标签、镜像 ID、创建时间和大小。嗯,这里要留意大小——一个基础镜像可能就 200MB,但装了分析软件后能膨胀到 2GB 甚至更大。

我个人经常搭配 --filter 参数来快速定位镜像:

docker images --filter "reference=ubuntu*"  # 只看 ubuntu 系列
docker images --filter "dangling=true"      # 查看悬空镜像(无标签的层)

为什么会有悬空镜像?当你用同一个 tag 反复构建新镜像时,旧镜像的 tag 会被移除,但层数据还留在磁盘上。这些镜像在 docker images 里显示为 <none>:<none>,很碍眼,而且也占空间。

2.3 删除镜像:做好磁盘的“断舍离”

长期使用 Docker,磁盘空间肯定是个大问题。删除镜像用 docker rmi 命令:

docker rmi ubuntu:22.04          # 通过仓库名+tag 删除
docker rmi a1b2c3d4              # 通过镜像 ID 删除
docker rmi ubuntu:22.04 python:3.10  # 一次删除多个

如果镜像正在被某个容器使用,Docker 会拒绝删除,并提示你容器还在运行。这时候要么先删容器,要么加 -f 强制删除(不推荐)。

更彻底的清理是用 docker image prune:

docker image prune        # 删除所有悬空镜像
docker image prune -a     # 删除所有未被使用的镜像

我习惯在每个项目结束后跑一次 docker image prune -a。项目里构建的临时镜像一下子全没了,磁盘空间瞬间释放十几 GB。但要注意,如果某个镜像虽然没被容器用,但它被你后续构建引用为基础镜像,-a 会把它也干掉。所以我建议先确认清楚再用。

⚠️ 曾经我踩过的坑: 有一次在团队服务器上,我执行了 docker image prune -a,没仔细看它列出的待删镜像。结果把团队成员正在用的基础镜像删了,害得他们重新拉取了一整遍。建议在删除之前,先用 docker images --filter "dangling=true" 确认哪些是真正要清理的。

2.4 构建镜像:从无到有定制你的环境

现成的镜像不一定能满足你的需求。比如你要在容器里运行一个质谱数据处理流程,需要装特定的 R 包和命令行工具。这时候就得自己构建镜像了。

构建命令是 docker build:

docker build -t my-ms-processor:v1 .

这里的 -t 指定镜像名和标签,最后的 . 是构建上下文路径(通常是 Dockerfile 所在的目录)。构建过程中,Docker 会逐行执行 Dockerfile 里的指令,生成一层层的缓存。

我自己遇到最多的问题是构建上下文里放了太多无关文件(比如原始质谱数据文件)。这些文件不会被复制进镜像,但 Docker 必须把它们打包发给构建守护进程,导致构建速度特别慢。我建议用 .dockerignore 文件排除它们:

# .dockerignore 文件内容
*.raw
*.mzML
data/
temp/
.git/

2.5 Dockerfile 基础:写在刀尖上的配方

Dockerfile 就是构建镜像的“配方”。每一行指令对应镜像的一层。把常用的几个指令列出来:

指令 作用 示例
FROM 指定基础镜像 FROM python:3.10-slim
RUN 在构建时执行命令(安装软件等) RUN apt-get update && apt-get install -y procps
COPY 把本地文件/目录复制到镜像 COPY ./scripts /opt/scripts
WORKDIR 设置工作目录 WORKDIR /app
CMD 容器启动时的默认命令 CMD ["python", "/app/run.py"]
EXPOSE 声明容器监听的端口(仅文档用途) EXPOSE 8000
ENV 设置环境变量 ENV DEBIAN_FRONTEND=noninteractive

举个实际的例子——构建一个质谱数据预处理镜像:

FROM python:3.10-slim

# 设置工作目录
WORKDIR /work

# 复制需求文件和脚本
COPY requirements.txt .
COPY scripts/ ./scripts/

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 设置环境变量(避免 apt 交互)
ENV DEBIAN_FRONTEND=noninteractive

# 安装系统工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    wget \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 容器启动时自动运行预处理脚本
CMD ["python", "/work/scripts/preprocess.py"]

你想想看,为什么要把 COPY requirements.txt 写在 COPY scripts/ 前面?因为 Docker 在构建时对每一层都有缓存。如果 requirements.txt 没变,Docker 会直接复用缓存,跳过重新安装依赖。但如果你把代码文件放在前面,哪怕只改了一行代码,整个镜像的依赖层也得重装。

优化建议:

  • 合并 RUN 指令: 用 && 连接多个命令,减少层数。
  • 清理包缓存: 在同一个 RUN 里执行 apt-get clean 或 rm -rf /var/lib/apt/lists/*,让镜像更小。
  • 利用 .dockerignore: 别把数据文件、源码仓库的 .git 文件夹打包进去。

2.6 构建与缓存:别忽视层的威力

镜像是由层堆叠起来的。每一层都是一个只读的文件系统快照。构建时,如果 Docker 发现当前指令没有变化,就直接复用之前的层。但一旦某层变了,它后面的所有层都必须重新构建。

所以理想的 Dockerfile 是把最稳定的部分(比如系统依赖、Python 基础包)写在前面,把最容易变的部分(比如你的脚本代码)写在最后。这样大部分情况你只需要重建最后几层,构建速度能快 10 倍不止。

我个人有个习惯:在 RUN pip install 后面加上 --no-cache-dir,否则 pip 会把下载的 wheel 包缓存也塞进镜像,白白增加几十 MB。对于质谱数据处理的容器来说,几十 MB 看起来不多,但如果做批量构建和分发,累计起来就很可观了。

docker build --no-cache -t my-ms-processor:v1 .

加上 --no-cache 会强制重新构建所有层,不依赖任何缓存。这在调试 Dockerfile 时很有用,但平时别乱用——效率太低了。我只有在基础镜像更新了,或者安装源发生变化时,才会这么干。

快捷清理与构建: 如果发现构建出的镜像有问题,可以用 docker system df 查看磁盘使用情况,然后 docker image prune -f 清理悬空镜像,最后重新构建。