Dockerfile 编写入门:FROM、RUN、COPY、CMD 等指令
Dockerfile 说白了就是一张菜谱。你写清楚步骤,Docker 按顺序一步步把镜像“炒”出来。我记得刚开始学 Docker 时,总觉得手动 commit 容器也能用,后来团队要自动化构建才发现——没有 Dockerfile,根本没法做 CI/CD。
一句话定义:Dockerfile 是一个纯文本文件,包含构建镜像所需的全部指令。每条指令生成一个只读层,层叠起来就是最终镜像。
核心指令速览
我们先看一张指令对比表,后面逐个细讲。
| 指令 | 作用 | 执行时机 | 典型示例 |
|---|---|---|---|
FROM | 指定基础镜像 | 构建开始第一条 | FROM ubuntu:22.04 |
RUN | 执行命令,生成新层 | 构建过程中 | RUN apt-get update && apt-get install -y samtools |
COPY | 将本地文件复制到镜像 | 构建过程中 | COPY pipeline.py /app/ |
CMD | 容器启动时的默认命令 | 容器启动时 | CMD ["python", "/app/pipeline.py"] |
WORKDIR | 设置工作目录 | 构建过程中影响后续指令 | WORKDIR /app |
ENV | 设置环境变量 | 构建 & 运行 | ENV PATH="/opt/conda/bin:$PATH" |
FROM — 一切从基础开始
没有 FROM,Docker 不知道从哪儿搭起。我建议:能选官方镜像就别用第三方。“为什么?”因为官方镜像经过安全审计,层数精简,出问题也好查。
FROM ubuntu:22.04
# 或
FROM python:3.10-slim
# 或
FROM biocontainers/biocontainers:latest
小技巧:尽量用 slim 或 alpine 变体。比如 python:3.10-slim 只有 100MB 出头,全量版快 1GB。我们做质控管线,用完即弃,镜像越小越好。
RUN — 干活的指令
RUN 在构建时执行 shell 命令。每一条 RUN 都会产生一个新层,层数多了镜像就很肥。我曾在一个项目里看到有人写了 10 条 RUN,每一条装一个包,最后镜像 2.3GB。怎么优化?把相关命令用 && 串起来。
RUN apt-get update && apt-get install -y \
fastqc \
multiqc \
samtools \
&& rm -rf /var/lib/apt/lists/*
看到最后一行了吗?安装完立刻清理缓存。这一招叫“单层清理”,能显著缩小镜像。
避坑指南:我曾经在镜像里装 wget 下载了一个大文件,用完忘记删。结果镜像多了 500MB,生产拉取慢得离谱。如果你需要临时工具,用完就在同一条 RUN 里删掉。
COPY — 把代码送进镜像
你本地写的 Python 脚本、配置文件、参考基因组索引,都要用 COPY 放进去。注意:源路径是构建上下文(context)的路径,不是宿主机任意路径。
# 将当前目录下的 pipeline.py 复制到镜像 /app 下
COPY pipeline.py /app/pipeline.py
# 也可以复制整个目录
COPY configs/ /app/configs/
我记得刚带新人时,他写 COPY /home/user/data /app/data,结果 Docker 报错说路径不在上下文里。说白了,COPY 只能访问 Dockerfile 所在目录及其子目录。
CMD — 容器跑起来做什么
CMD 和 RUN 经常被搞混。区别在于:RUN 是构建时执行,CMD 是容器启动时执行。一个 Dockerfile 只能有一条 CMD,如果写多条只有最后一条生效。
# 推荐 exec 格式(JSON 数组)
CMD ["python", "/app/run_qc.py"]
# 也可以写 shell 格式,但会被 /bin/sh -c 解释
# CMD python /app/run_qc.py
为什么推荐 exec 格式?因为 shell 格式会在进程中产生一个 sh -c 子进程,信号传递容易出问题。exec 格式直接将你的程序作为 PID 1,信号处理更干净。
WORKDIR 与 ENV — 让容器更顺手
WORKDIR 设置工作目录。之后的所有 RUN、CMD、COPY 都会基于这个目录。我个人习惯在 Dockerfile 开头就设好 WORKDIR,这样后面的路径都简洁很多。
WORKDIR /opt/pipeline
COPY . . # 将上下文所有文件复制到 /opt/pipeline
ENV 设环境变量。比如设置软件路径或 Python 环境变量:
ENV PATH="/opt/conda/bin:${PATH}"
ENV PYTHONUNBUFFERED=1
一个完整的质控管线 Dockerfile
以下是我为 RNA-seq 质控写的真实 Dockerfile(精简版)。你可以直接当模板改。
FROM continuumio/miniconda3:22.11.1
LABEL maintainer="yourname@example.com"
LABEL description="RNA-seq QC pipeline"
WORKDIR /opt/qc
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
curl \
&& rm -rf /var/lib/apt/lists/*
# 通过 conda 安装生信工具
RUN conda install -c bioconda -c conda-forge \
fastqc=0.11.9 \
multiqc=1.14 \
cutadapt=4.4 \
&& conda clean -afy
# 复制管线脚本
COPY scripts/run_qc.py /opt/qc/
COPY configs/samples.csv /opt/qc/
# 暴露端口(仅当需要 Web 服务时)
EXPOSE 8080
# 默认启动命令
CMD ["python", "/opt/qc/run_qc.py"]
关于 .dockerignore
如果你的项目目录里有大文件(比如原始 FASTQ)、.git 文件夹、本地 venv,一定要写 .dockerignore。否则 COPY . . 会把整个目录塞进上下文,构建卡死。
*.fastq
*.bam
.git/
__pycache__/
.env
data/
血的教训:我曾经没写 .dockerignore,把 20GB 的参考基因组文件也 COPY 进去了。构建花了 40 分钟,最后镜像 19GB。同事笑我“你是不是把整个实验室的数据装进去了?”
构建与运行命令
学完指令,动手试一下:
# 构建镜像,-t 给名字和标签
docker build -t my-qc:v1 .
# 运行容器,覆盖 CMD
docker run --rm my-qc:v1 python /opt/qc/run_qc.py --input /data/sample.fastq
嗯,Dockerfile 不难。关键是把指令搞明白,再养成好习惯:能用 slim 就别用 full,能用一条 RUN 就别拆三条,记得清理缓存。这样你构建的镜像既小又快,团队里别人拉取时都会感谢你。