Dockerfile 编写基础归纳总结
写 Dockerfile 这事,说白了就是给应用盖一间标准化的房子。你想想看,生物信息学里最头疼的是什么?环境不一致!我这些年不知道吃了多少亏——同一个脚本在自己电脑上跑得飞起,换到服务器上就缺依赖、版本对不上,最后只能重装系统。Dockerfile 就是解决这个问题的钥匙。
这一章我们不搞花架子,就把 Dockerfile 里最常用的指令捋一遍。我会结合自己踩过的坑,帮你建立一套「稳、小、快」的镜像编写思维。
FROM:选对基础镜像,成功一半
我个人习惯从 ubuntu:22.04 或者 biocontainers 家族开始。为什么不用 alpine?生物信息学很多 R 包和 Python 库在 musl 下容易翻车,我试过一次,编译 R 包花了整整一下午还没搞定,最后换回 Ubuntu 镜像十分钟搞定。
| 场景 | 推荐基础镜像 | 说明 |
|---|---|---|
| 通用生物信息分析 | ubuntu:22.04 | apt 源丰富,glibc 兼容性好 |
| R 语言为主 | rocker/r-ver:4.3 | 预装 R 和 Tidyverse,省去编译时间 |
| Python / PyTorch | python: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 下
COPY 与 ADD:文件搬运工
COPY 就是老老实实把本地文件复制进去。ADD 多了解压功能,但容易带来意外——比如你明明只想复制一个 tar 文件,ADD 却自动解压了。我个人只用 COPY,解压这些事交给 RUN 命令显式处理。
# 推荐方式
COPY requirements.txt /workspace/
COPY scripts/ /workspace/scripts/
# 不推荐:ADD 自动解压 tar 包,容易误判
# ADD data.tar.gz /workspace/
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"
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"]
.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"]