4、Dockerfile基础:FROM指令、RUN指令、CMD与ENTRYPOINT的区别、LABEL与MAINTAINER
聊到 Docker 镜像构建,Dockerfile 就是那个绕不开的起点。说白了,它就是一份「菜谱」,告诉 Docker 引擎怎么一层层把镜像给「煮」出来。今天咱们就掰开揉碎,把最核心的几个指令讲清楚。
4.1 FROM 指令:一切的基础
每个 Dockerfile 的第一行,几乎都是 FROM。它指定了你的镜像基于哪个基础镜像。没有它,后面的一切都无从谈起。
核心要点:FROM 必须是 Dockerfile 中非注释的第一条指令。
我个人习惯,选基础镜像时尽量挑官方、轻量的版本。比如 alpine 就比 ubuntu 小很多,能省下不少磁盘空间和网络传输时间。
# 指定基于 Ubuntu 22.04
FROM ubuntu:22.04
# 或者用更轻量的 Alpine
FROM alpine:3.18
我在项目中遇到过一个问题:有人直接用了 FROM ubuntu:latest,结果过了一段时间,latest 标签指向了新版本,构建出来的镜像行为变了。嗯,这里要注意——生产环境一定要指定具体版本号,别用 latest。
4.2 RUN 指令:执行命令,构建层
RUN 用来在镜像构建过程中执行命令。每执行一次 RUN,就会在镜像上增加一个新层。你想想看,层越多,镜像越大,构建也越慢。
小技巧:尽量把多个命令用 && 连接起来,写在一个 RUN 里,减少层数。
# 不推荐:多个 RUN 产生多层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# 推荐:合并成一个 RUN
RUN apt-get update && \
apt-get install -y curl && \
apt-get clean
我曾经见过一个 Dockerfile,写了十几个 RUN,每个只做一件事。镜像体积直接飙到 2GB。后来合并优化后,降到了 400MB。说白了,减少层数就是减少浪费。
4.3 CMD 与 ENTRYPOINT:启动时的「开关」
这两个指令经常让人搞混。我刚开始学的时候也绕了一阵子。咱们直接看区别:
| 指令 | 作用 | 能否被覆盖 |
|---|---|---|
CMD |
提供默认命令或参数 | 可以,运行容器时指定命令会覆盖 |
ENTRYPOINT |
定义容器启动时的主程序 | 不易覆盖,除非用 --entrypoint |
为什么会这样?因为设计意图不同。CMD 更像是「默认值」,而 ENTRYPOINT 是「固定入口」。
最佳实践:用 ENTRYPOINT 固定主程序,用 CMD 提供默认参数。这样既灵活又可控。
# 示例:一个简单的 Web 服务
FROM node:18-alpine
WORKDIR /app
COPY . .
# ENTRYPOINT 固定为 node 命令
ENTRYPOINT ["node"]
# CMD 提供默认参数
CMD ["server.js"]
运行容器时,如果你传了参数,CMD 会被覆盖:
# 使用默认参数,启动 server.js
docker run my-image
# 覆盖 CMD,启动 app.js
docker run my-image app.js
我记得有一次,团队里有人把 CMD 和 ENTRYPOINT 写反了,结果容器启动时总是报错。排查了半天才发现是顺序问题。嗯,这里要记住:ENTRYPOINT 在前,CMD 在后,组合使用时 CMD 作为 ENTRYPOINT 的默认参数。
4.4 LABEL 与 MAINTAINER:给镜像打标签
MAINTAINER 是 Dockerfile 早期的指令,用来标注作者信息。不过现在已经被官方标记为「已弃用」了。我个人建议直接用 LABEL 替代,更灵活也更规范。
注意:MAINTAINER 虽然还能用,但 Docker 官方推荐使用 LABEL 替代。别再写 MAINTAINER 了。
LABEL 可以添加任意键值对,用来描述镜像的元数据。比如版本号、维护者、描述信息等。
# 推荐:使用 LABEL
LABEL maintainer="zhangsan@example.com"
LABEL version="1.0.0"
LABEL description="这是一个用于演示的 Node.js 应用"
# 或者合并成一行
LABEL maintainer="zhangsan@example.com" \
version="1.0.0" \
description="这是一个用于演示的 Node.js 应用"
我在项目中遇到过,镜像多了以后,根本分不清哪个是哪个。后来强制团队在 Dockerfile 里加上 LABEL,标注了版本和用途。再配合 docker inspect 查看,管理起来就清晰多了。
实用技巧:可以用 docker inspect <镜像名> | grep Labels 快速查看镜像的标签信息。
4.5 避坑指南:我踩过的几个坑
- FROM 用了 latest:构建结果不可控,生产环境一定要指定版本号。
- RUN 写太多:层数多、体积大、构建慢。合并命令,减少层数。
- CMD 和 ENTRYPOINT 搞混:记住 ENTRYPOINT 是主程序,CMD 是默认参数。
- 还在用 MAINTAINER:换成 LABEL,更规范也更灵活。
好了,这一章的内容就到这里。FROM 是地基,RUN 是施工,CMD 和 ENTRYPOINT 是开关,LABEL 是铭牌。把这几个指令吃透了,写 Dockerfile 就顺手多了。下一章咱们聊聊更高级的构建技巧,比如多阶段构建和缓存优化。