4、Dockerfile 基础指令:FROM、RUN、CMD、ENTRYPOINT 详解
聊 Dockerfile,其实就是在聊怎么把“应用”打包成“镜像”的行动指南。
我个人习惯把 Dockerfile 看作是一份食谱。FROM 是选锅,RUN 是下料翻炒,CMD 和 ENTRYPOINT 就是上桌前的最后一道工序——决定这盘菜端上去是怎么个吃法。
我这里总结了四个最核心的指令。搞懂了它们,你基本就能写出像样的 Dockerfile 了。
4.1 FROM:镜像的“地基”
FROM 是 Dockerfile 的第一行。它指定了你的新镜像是从哪个基础镜像构建出来的。
你可以理解为,所有的楼房都是从地基上建起来的。FROM 就是这个地基。
语法非常简单:
FROM [--platform=<平台>] <镜像名>[:<标签>] [AS <阶段名>]
常用的几种写法:
# 指定版本,最推荐的做法
FROM ubuntu:22.04
# 基于 Alpine,镜像极小
FROM alpine:3.19
# 多阶段构建,给阶段起个名
FROM golang:1.21 AS builder
FROM scratch
4.2 RUN:在构建时执行命令
RUN 是在镜像构建过程中执行的命令。每一条 RUN 都会在当前镜像层之上创建一个新层,并提交结果。
你可以用它安装软件包、编译代码、创建目录等等。
RUN apt-get update && apt-get install -y \
build-essential \
libssl-dev
RUN mkdir /app
RUN ["executable", "param1", "param2"] # exec 形式
这里有一个极其常见的坑。
再举一个反面例子。我之前给一个 Python 项目写 Dockerfile,发现有人这么写:
# 这样写会让"安装依赖"和"复制代码"耦合,改一下代码就要重新装一遍依赖
RUN pip install -r requirements.txt
COPY . /app
你应该把“不变的部分”放在前面:
# 先把 requirements.txt 复制进来,利用缓存安装依赖
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
# 最后复制代码,改动代码时不会触发 pip install
COPY . /app
说白了就是一句话:**把变化慢的放前面,变化快的放后面**。
4.3 CMD vs ENTRYPOINT:容器启动时跑什么?
这两个指令最容易让人困惑。我直接说结论:
| 指令 | 是否可被覆盖 | 典型用法 |
|---|---|---|
| CMD | 是。docker run 后面加的参数会覆盖 CMD | 提供默认命令或参数 |
| ENTRYPOINT | 否(除非用 --entrypoint 强制覆盖) | 固定容器启动时要执行的程序 |
| 两者配合使用 | ENTRYPOINT 固定,CMD 提供默认参数 | 容器作为可执行工具 |
4.3.1 CMD:提供默认的启动命令
CMD 有三种写法:
# shell 形式
CMD command param1 param2
# exec 形式(推荐)
CMD ["executable", "param1", "param2"]
# 作为 ENTRYPOINT 的默认参数
CMD ["param1", "param2"]
举个例子:
FROM ubuntu:22.04
CMD ["echo", "Hello Docker"]
这个镜像启动后会默认打印 "Hello Docker"。但如果你运行 docker run myimage ls,CMD 会被覆盖,执行的是 ls。
/bin/sh -c "你的命令",子进程信号处理有问题,关闭容器时可能不会正确退出。
4.3.2 ENTRYPOINT:把容器“固化”为一个程序
ENTRYPOINT 一旦设置,容器启动时一定会运行它。docker run 后面跟的参数会被当作这个程序的参数。
FROM ubuntu:22.04
ENTRYPOINT ["echo"]
运行 docker run myimage Hello World,实际执行的是 echo Hello World,输出 "Hello World"。
我印象很深的一个场景是做计算工具镜像。我们希望用户跑 docker run mytool --input data.txt,工具名是固定的,参数才是用户提供的。
那 Dockerfile 就该这么写:
FROM ubuntu:22.04
COPY mytool /usr/local/bin/mytool
ENTRYPOINT ["mytool"]
CMD ["--help"]
用户什么都不加,默认显示帮助。用户加参数,就执行对应的功能。
4.4 一张图理清指令关系
4.5 实战时的核心要点
- FROM 不要用 latest,固定一个具体版本,比如 ubuntu:22.04 或 python:3.11-slim
- RUN 尽量合并,apt-get update 和 apt-get install 放一起,减少镜像层数
- 把“不变”放前面,充分利用构建缓存,加快迭代速度
- CMD 和 ENTRYPOINT 二选一或配合,不要同时用两种语义完全不同的写法
嗯,这四个指令就是 Dockerfile 的灵魂。把 FROM 选对地基,RUN 组织好工序,最后用 CMD 和 ENTRYPOINT 控制启动行为,你的镜像构建之路就基本稳了。