Dockerfile 编写基础归纳总结

写 Dockerfile 这事,说白了就是给应用盖一间标准化的房子。你想想看,生物信息学里最头疼的是什么?环境不一致!我这些年不知道吃了多少亏——同一个脚本在自己电脑上跑得飞起,换到服务器上就缺依赖、版本对不上,最后只能重装系统。Dockerfile 就是解决这个问题的钥匙。

这一章我们不搞花架子,就把 Dockerfile 里最常用的指令捋一遍。我会结合自己踩过的坑,帮你建立一套「稳、小、快」的镜像编写思维。

Dockerfile 编写的核心知识体系 FROM 选择基础镜像 WORKDIR 工作目录 COPY/ADD 复制文件 RUN 构建命令 ENV 环境变量 EXPOSE 端口声明 CMD / ENTRYPOINT 启动 最佳实践 多阶段构建 · 最小化层 · .dockerignore
核心原则:每一行 Dockerfile 指令都会生成一层镜像层,层数越多,镜像越大,构建越慢。所以,精简层数、合并 RUN 命令是基本功。

FROM:选对基础镜像,成功一半

我个人习惯从 ubuntu:22.04 或者 biocontainers 家族开始。为什么不用 alpine?生物信息学很多 R 包和 Python 库在 musl 下容易翻车,我试过一次,编译 R 包花了整整一下午还没搞定,最后换回 Ubuntu 镜像十分钟搞定。

场景推荐基础镜像说明
通用生物信息分析ubuntu:22.04apt 源丰富,glibc 兼容性好
R 语言为主rocker/r-ver:4.3预装 R 和 Tidyverse,省去编译时间
Python / PyTorchpython:3.10-slim轻量,保留常用工具
最小化部署alpine:3.18仅 5 MB,但需注意 musl 兼容性问题
# 我常用的 Dockerfile 开头
FROM ubuntu:22.04 AS base
LABEL maintainer="yourname@example.com"

WORKDIR:给容器安家

每次 RUN cd /somewhere && ... 我都觉得别扭——为什么不直接设工作目录?WORKDIR 会自动创建目录,后续 COPY、RUN 都基于这个路径。记得当年有个同事把 COPY 写成了 COPY . . 结果文件散落在根目录,镜像直接炸了 3GB。

WORKDIR /workspace
# 后面的所有操作都在 /workspace 下
小技巧:WORKDIR 可以多次使用,切换目录时记得写全路径。我习惯写在 FROM 后面紧接着设置。

COPY 与 ADD:文件搬运工

COPY 就是老老实实把本地文件复制进去。ADD 多了解压功能,但容易带来意外——比如你明明只想复制一个 tar 文件,ADD 却自动解压了。我个人只用 COPY,解压这些事交给 RUN 命令显式处理。

# 推荐方式
COPY requirements.txt /workspace/
COPY scripts/ /workspace/scripts/

# 不推荐:ADD 自动解压 tar 包,容易误判
# ADD data.tar.gz /workspace/
注意缓存失效:COPY 会检查文件 checksum。如果你先 COPY 了代码,然后 RUN 安装依赖,每次代码改动都会重新安装,很慢。正确顺序:先 COPY 稳定不变的文件(如 requirements.txt),再 RUN 安装,最后 COPY 其余代码。

RUN:构建时的核心

RUN 就是执行 shell 命令。生物信息学特别依赖 apt-get install、pip install、Rscript -e 这些。注意:每个 RUN 指令会生成一个镜像层,尽量把同类命令合并到一个 RUN 里,并且清理临时文件。

RUN apt-get update && apt-get install -y \
    build-essential \
    wget \
    curl \
    git \
    libssl-dev \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir numpy scipy pandas matplotlib

我一直遵循「一个 RUN 做一类事」——系统工具一个 RUN,Python 包一个 RUN,R 包一个 RUN。既保留可读性,又不会产生太多层。曾经我为了省事把所有命令写在一个 RUN 里,结果出了问题定位很痛苦。

ENV:环境变量管理

ENV 可以在构建时和运行时都生效。我喜欢把版本号定义成 ENV,这样升级版本只需改一处。

ENV PYTHON_VERSION=3.10
ENV R_VERSION=4.3.1
ENV PATH="/custom/bin:$PATH"
避坑:ENV 设置的变量会持久化到容器里。如果你不希望运行时暴露敏感信息,用 ARG 代替,ARG 只在构建时存在。

EXPOSE:文档声明

EXPOSE 实际上只是元数据,告诉使用者这个容器会监听哪个端口。它不会真的打开端口——真正映射端口是在 docker run -p 时做的。对于 Visium 聚类分析,我们可能运行 Jupyter Notebook 或 RStudio Server,所以 EXPOSE 8888 或 8787 是有意义的。

EXPOSE 8888

CMD 与 ENTRYPOINT:启动指令

这两个容易混淆。简单说:ENTRYPOINT 是容器的主程序,CMD 是默认参数。我常用的模式是 ENTRYPOINT 固定脚本,CMD 提供默认参数。

# 推荐写法
ENTRYPOINT ["python", "run_visium_pipeline.py"]
CMD ["--config", "default.yaml"]

这样用户运行 docker run myimage --config custom.yaml 就能覆盖默认配置。如果只用 CMD,用户必须把整个命令写全。嗯,这算是我从实战中总结出的经验。

多阶段构建:瘦身利器

生物信息学镜像经常需要编译 C/C++ 库,比如 SpaGCN、leidenalg。编译工具链往往占用几百 MB,运行时完全不需要。多阶段构建就是用来解决这个问题的:第一阶段装编译工具编译出产物,第二阶段只拿产物。

# 第一阶段:编译
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y build-essential cmake
WORKDIR /build
COPY source_code/ .
RUN make

# 第二阶段:运行
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 python3-pip && \
    apt-get clean
COPY --from=builder /build/output /app/
WORKDIR /app
CMD ["python3", "main.py"]
效果:最终镜像只包含运行时依赖,大小从 1.2 GB 降到 280 MB。我在生产环境里几乎每个镜像都用多阶段构建。

.dockerignore:不要什么都往里塞

跟 .gitignore 一个道理。忘了写的话,构建上下文会包含 .git 目录、pycache、大文件,导致构建极慢甚至失败。

# .dockerignore
.git
__pycache__
*.pyc
.DS_Store
*.tar.gz
data/

有一次我构建一个包含 5GB 测试数据的镜像,花了半小时没结束,一查发现 data/ 没被忽略,整个数据集被打包进了上下文。加上 .dockerignore 后构建时间降到 30 秒。

编写规范与最佳实践清单

  • 每个 RUN 合并多个 apt-get 命令,并清理 apt 缓存
  • 使用 --no-cache-dir 避免 pip 缓存
  • 固定基础镜像版本标签(不要用 latest)
  • 将不变的依赖(requirements.txt)放在前面 COPY,利用构建缓存
  • 使用 ARG 传递构建时可变的参数,如版本号
  • 优先使用官方镜像,减少安全漏洞
  • 为镜像添加 LABEL 维护信息
# 一个简洁的 Dockerfile 示例(Visium 聚类分析用)
FROM ubuntu:22.04 AS base

WORKDIR /workspace
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    python3.10 python3-pip \
    libhdf5-dev libssl-dev \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt /workspace/
RUN pip install --no-cache-dir -r requirements.txt

COPY . /workspace/

ENTRYPOINT ["python3", "cluster_visium.py"]
CMD ["--help"]
重要提醒:不要在生产镜像中安装编译工具(如 gcc、cmake),它们会膨胀镜像并提供攻击面。使用多阶段构建将编译与运行分离。