Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的"USB-C",模型上下文协议原理、实践与未来
Python系列文章目录
PyTorch系列文章目录
机器学习系列文章目录
深度学习系列文章目录
Java系列文章目录
JavaScript系列文章目录
Python系列文章目录
Go语言系列文章目录
Docker系列文章目录
01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石------镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- Python系列文章目录
- Go语言系列文章目录
- Docker系列文章目录
- 摘要
- [一、`CMD` 与 `ENTRYPOINT`:定义容器的最终使命](#一、
CMD
与ENTRYPOINT
:定义容器的最终使命) -
- [1.1 `CMD` 指令:容器的默认命令](#1.1
CMD
指令:容器的默认命令) -
- [1.1.1 `CMD` 的三种形式](#1.1.1
CMD
的三种形式) -
- [(1) Exec 形式 (推荐)](#(1) Exec 形式 (推荐))
- [(2) Shell 形式](#(2) Shell 形式)
- [(3) `ENTRYPOINT` 的默认参数形式](#(3)
ENTRYPOINT
的默认参数形式)
- [1.1.2 `CMD` 的核心特性:易于覆盖](#1.1.2
CMD
的核心特性:易于覆盖)
- [1.1.1 `CMD` 的三种形式](#1.1.1
- [1.2 `ENTRYPOINT` 指令:容器的"可执行程序"](#1.2
ENTRYPOINT
指令:容器的“可执行程序”) -
- [1.2.1 `ENTRYPOINT` 的两种形式](#1.2.1
ENTRYPOINT
的两种形式) -
- [(1) Exec 形式 (推荐)](#(1) Exec 形式 (推荐))
- [(2) Shell 形式](#(2) Shell 形式)
- [1.2.2 `ENTRYPOINT` 的核心特性:参数追加](#1.2.2
ENTRYPOINT
的核心特性:参数追加)
- [1.2.1 `ENTRYPOINT` 的两种形式](#1.2.1
- [1.3 `CMD` 与 `ENTRYPOINT` 的巅峰对决与珠联璧合](#1.3
CMD
与ENTRYPOINT
的巅峰对决与珠联璧合) -
- [1.3.1 对比总结](#1.3.1 对比总结)
- [1.3.2 最佳实践:`ENTRYPOINT` + `CMD` 联合使用](#1.3.2 最佳实践:
ENTRYPOINT
+CMD
联合使用)
- [1.1 `CMD` 指令:容器的默认命令](#1.1
- [二、`ENV` 与 `ARG`:构建时与运行时的变量注入](#二、
ENV
与ARG
:构建时与运行时的变量注入) -
- [2.1 `ARG`:构建时的"临时演员"](#2.1
ARG
:构建时的“临时演员”) -
- [2.1.1 `ARG` 的定义与使用](#2.1.1
ARG
的定义与使用) - [2.1.2 `ARG` 的作用域与局限性](#2.1.2
ARG
的作用域与局限性)
- [2.1.1 `ARG` 的定义与使用](#2.1.1
- [2.2 `ENV`:容器内的"永久居民"](#2.2
ENV
:容器内的“永久居民”) -
- [2.2.1 `ENV` 的定义与使用](#2.2.1
ENV
的定义与使用) - [2.2.2 `ENV` 的灵活性:运行时覆盖](#2.2.2
ENV
的灵活性:运行时覆盖)
- [2.2.1 `ENV` 的定义与使用](#2.2.1
- [2.3 `ARG` vs. `ENV`:如何选择?](#2.3
ARG
vs.ENV
:如何选择?)
- [2.1 `ARG`:构建时的"临时演员"](#2.1
- [三、`EXPOSE` 与 `.dockerignore`:端口声明与构建优化](#三、
EXPOSE
与.dockerignore
:端口声明与构建优化) -
- [3.1 `EXPOSE`:声明容器的"服务窗口"](#3.1
EXPOSE
:声明容器的“服务窗口”) -
- [3.1.1 `EXPOSE` 的真正作用](#3.1.1
EXPOSE
的真正作用) - [3.1.2 `EXPOSE` vs `-p` 参数](#3.1.2
EXPOSE
vs-p
参数)
- [3.1.1 `EXPOSE` 的真正作用](#3.1.1
- [3.2 `.dockerignore`:构建上下文的"瘦身器"](#3.2
.dockerignore
:构建上下文的“瘦身器”) -
- [3.2.1 `.dockerignore` 的作用与语法](#3.2.1
.dockerignore
的作用与语法)
- [3.2.1 `.dockerignore` 的作用与语法](#3.2.1
- [3.1 `EXPOSE`:声明容器的"服务窗口"](#3.1
- 四、总结
摘要
在上一篇文章【Docker-Day 6】中,我们初步掌握了 Dockerfile 的基础指令 FROM
、WORKDIR
、COPY
和 RUN
,成功为应用构建了一个基本的镜像"安装包"。然而,一个真正生产级别的镜像,不仅需要能被构建,更需要能灵活、正确地"跑起来"。本文将深入探讨 Dockerfile 的另一半核心内容------启动与配置指令。我们将详细剖析 CMD
与 ENTRYPOINT
的爱恨情仇,揭示它们如何共同决定容器的启动行为;辨析 ENV
与 ARG
在构建时与运行时传递变量的异同;并阐明 EXPOSE
的声明作用以及 .dockerignore
文件在优化构建过程中的重要性。学完本章,你将能够构建出配置更灵活、行为更可控、镜像更精简的专业级 Docker 镜像。
一、CMD
与 ENTRYPOINT
:定义容器的最终使命
当我们通过 docker run
启动一个容器时,我们期望它执行一个特定的任务,比如启动一个 Web 服务、运行一个批处理脚本,或者进入一个交互式 Shell。CMD
和 ENTRYPOINT
这两个指令正是用来定义这个"默认任务"的,但它们之间存在着微妙而关键的区别。
1.1 CMD
指令:容器的默认命令
CMD
指令用于为执行中的容器提供默认命令。如果 docker run
命令后面指定了其他命令,则 CMD
的设置会被覆盖。
1.1.1 CMD
的三种形式
CMD
有三种语法形式,理解它们的差异至关重要。
(1) Exec 形式 (推荐)
这是 CMD
的首选形式,它以 JSON 数组的格式定义了要执行的命令及其参数。
- 语法 :
CMD ["executable", "param1", "param2"]
- 特点 : 命令和参数被直接解析,不会通过 shell 执行。这意味着像
$HOME
这样的环境变量不会被 shell 替换。
示例:
dockerfile
# Dockerfile
FROM ubuntu
CMD ["/bin/echo", "Hello, Docker"]
构建并运行时,它会直接执行 /bin/echo
命令,输出 "Hello, Docker"。
(2) Shell 形式
这种形式将命令作为字符串,它会被包裹在 sh -c
中执行。
- 语法 :
CMD command param1 param2
- 特点: 由于通过 shell 执行,可以使用 shell 的特性,如环境变量替换、管道等。
示例:
dockerfile
# Dockerfile
FROM ubuntu
ENV NAME=World
CMD echo "Hello, $NAME"
运行时,shell 会将 $NAME
替换为 "World",最终输出 "Hello, World"。
(3) ENTRYPOINT
的默认参数形式
当 Dockerfile
中同时定义了 ENTRYPOINT
时,CMD
的内容会作为 ENTRYPOINT
的默认参数。我们将在后面详细讨论这种用法。
1.1.2 CMD
的核心特性:易于覆盖
CMD
的最大特点就是它的值可以被 docker run
命令后面的参数轻松覆盖。
示例 :
假设我们有以下 Dockerfile:
dockerfile
# Dockerfile
FROM ubuntu
CMD ["/bin/echo", "Default command"]
构建镜像 my-ubuntu
: docker build -t my-ubuntu .
-
运行默认命令 :
bashdocker run my-ubuntu # 输出: Default command
-
覆盖默认命令 :
bashdocker run my-ubuntu /bin/ls -l / # 输出: (类似 ls -l / 的结果,CMD 被完全覆盖) # total 64 # dr-xr-xr-x 1 root root 4096 Jul 10 05:28 bin # ...
1.2 ENTRYPOINT
指令:容器的"可执行程序"
ENTRYPOINT
指令将容器配置为像一个可执行文件一样运行。它的命令不容易在 docker run
时被覆盖,而是将 docker run
后的参数作为其自身的参数来接收。
1.2.1 ENTRYPOINT
的两种形式
ENTRYPOINT
同样有 Exec 和 Shell 两种形式。
(1) Exec 形式 (推荐)
这是 ENTRYPOINT
的主要使用形式,它定义了容器启动时必须执行的固定命令。
- 语法 :
ENTRYPOINT ["executable", "param1", "param2"]
示例:
dockerfile
# Dockerfile
FROM alpine
ENTRYPOINT ["ping", "localhost"]
构建镜像 my-pinger
: docker build -t my-pinger .
无论你 docker run my-pinger
后面跟什么,它都会尝试执行 ping localhost
,并将后面的参数追加给它。
(2) Shell 形式
- 语法 :
ENTRYPOINT command param1 param2
- 问题 : Shell 形式的
ENTRYPOINT
会导致docker run
后面的参数无法传递,并且CMD
的值也无法作为其参数。因此,强烈不推荐使用此形式。
1.2.2 ENTRYPOINT
的核心特性:参数追加
与 CMD
不同,docker run
提供的参数会被追加到 ENTRYPOINT
(Exec 形式) 的后面,而不是覆盖它。
示例 :
基于上面的 my-pinger
镜像:
dockerfile
# Dockerfile
FROM alpine
ENTRYPOINT ["ping"] # 将 ping 作为固定入口
CMD ["localhost"] # 提供默认的 ping 目标
构建镜像 my-pinger
: docker build -t my-pinger .
-
运行默认命令 :
bashdocker run my-pinger # 实际执行: ping localhost
-
传递新参数 :
bashdocker run my-pinger google.com # 实际执行: ping google.com (google.com 覆盖了 CMD 的 localhost)
-
覆盖
ENTRYPOINT
:
虽然不推荐,但你依然可以通过--entrypoint
标志来强制覆盖它。bashdocker run --entrypoint /bin/echo my-pinger "Hello Override" # 输出: Hello Override
1.3 CMD
与 ENTRYPOINT
的巅峰对决与珠联璧合
理解了两者的区别后,我们才能在合适的场景下做出最佳选择。
1.3.1 对比总结
特性 | CMD |
ENTRYPOINT (Exec 形式) |
---|---|---|
目的 | 提供容器启动的 默认 命令和参数。 | 将容器配置为一个 可执行程序,定义固定的入口点。 |
覆盖行为 | docker run 后的参数会 完全覆盖 CMD 。 |
docker run 后的参数会 追加 到 ENTRYPOINT 之后,作为其参数。 |
主要场景 | 1. 为 ENTRYPOINT 提供默认参数。<br>2. 独立的、易于覆盖的容器命令。 |
1. 构建行为类似命令行的镜像(如 ping )。<br>2. 结合 CMD 使用,固定主命令,让 CMD 提供默认参数。 |
1.3.2 最佳实践:ENTRYPOINT
+ CMD
联合使用
将 ENTRYPOINT
和 CMD
结合使用是构建灵活且强大的镜像的黄金法则。
ENTRYPOINT
: 设置固定的、不会改变的主命令。CMD
: 提供该主命令的默认参数,这些参数可以被docker run
轻松覆盖。
场景:创建一个通用的 curl
工具容器
dockerfile
# Dockerfile
FROM alpine
# 安装 curl
RUN apk add --no-cache curl
# 将 curl 设置为容器的固定入口程序
ENTRYPOINT ["curl"]
# 提供一个默认的参数,比如请求帮助文档
CMD ["--help"]
构建镜像 my-curl
: docker build -t my-curl .
使用演示:
-
运行默认命令(查看帮助):
bashdocker run --rm my-curl # 输出 curl 的帮助信息 # curl: try 'curl --help' or 'curl --manual' for more information
-
向
curl
传递新的参数(访问网站):bashdocker run --rm my-curl -sL https://www.google.com # 输出 Google 首页的 HTML
-
向
curl
传递多个参数(带请求头):bashdocker run --rm my-curl -I httpbin.org/get # 输出 httpbin.org/get 的响应头信息
通过这种模式,我们创建了一个行为非常清晰的工具镜像:它就是 curl
,而用户需要做的只是像在普通终端里一样提供 curl
所需的参数。
二、ENV
与 ARG
:构建时与运行时的变量注入
在构建和运行镜像时,我们常常需要传递一些配置信息,例如版本号、数据库密码、API 地址等。ARG
和 ENV
就是为此而生,但它们的作用域和生命周期截然不同。
2.1 ARG
:构建时的"临时演员"
ARG
(Argument) 指令定义了一个变量,用户可以在构建时通过 docker build
命令使用 --build-arg <varname>=<value>
标志来传递它。
2.1.1 ARG
的定义与使用
ARG
定义的变量仅在 docker build
过程中有效,一旦镜像构建完成,ARG
变量就不再存在。
- 语法 :
ARG <name>[=<default value>]
示例:构建时指定应用版本
dockerfile
# Dockerfile
FROM alpine
# 定义一个构建参数,并设置默认值为 1.0
ARG APP_VERSION=1.0
# 在构建过程中使用这个参数
RUN echo "Building application version: ${APP_VERSION}" > /app_version.txt
# 你也可以在 ENV 中使用 ARG
ENV APP_VERSION_ENV=${APP_VERSION}
构建过程:
-
使用默认值构建:
bashdocker build -t my-app:default .
构建日志会显示
Building application version: 1.0
。 -
传递新值构建:
bashdocker build --build-arg APP_VERSION=2.5-beta -t my-app:latest .
构建日志会显示
Building application version: 2.5-beta
。
2.1.2 ARG
的作用域与局限性
ARG
的作用域从它在 Dockerfile 中被定义的那一行开始。如果 ARG
在 FROM
之前定义,它只能被 FROM
指令使用。
核心局限 :ARG
是构建时变量。容器运行时无法访问 ARG
变量的值 ,除非像上例一样,你将它的值赋给了 ENV
变量。这使得 ARG
非常适合传递那些不希望残留在最终镜像中的敏感信息(如私有仓库的 token)或纯粹的构建时配置。
2.2 ENV
:容器内的"永久居民"
ENV
(Environment) 指令用于为容器设置环境变量 。这个变量在后续的 RUN
指令中可用,并且在容器启动后也会持久存在。
2.2.1 ENV
的定义与使用
ENV
设置的环境变量是镜像元数据的一部分,并且会传递给所有从该镜像创建的容器。
- 语法 :
ENV <key> <value>
(单条)ENV <key1>=<value1> <key2>=<value2> ...
(多条,推荐,可减少镜像层数)
示例:配置应用环境
dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
# 设置环境变量
ENV NODE_ENV=production \
API_URL=http://api.example.com/v1 \
PORT=8080
# 声明端口,我们将在下一节讨论
EXPOSE 8080
# 启动命令会读取这些环境变量
CMD ["node", "server.js"]
2.2.2 ENV
的灵活性:运行时覆盖
与 CMD
类似,ENV
设置的环境变量也可以在容器启动时被覆盖。
运行演示:
bash
# 使用默认 API_URL
docker run my-node-app
# 覆盖 API_URL 用于测试环境
docker run -e "API_URL=http://test-api.example.com/v1" my-node-app
# 覆盖端口并映射到宿主机
docker run -e "PORT=3000" -p 8000:3000 my-node-app
2.3 ARG
vs. ENV
:如何选择?
特性 | ARG |
ENV |
---|---|---|
生命周期 | 仅在 构建时 有效,镜像构建完成后失效。 | 在 构建时 和 运行时 都有效。 |
持久性 | 不会持久化到镜像或容器中。 | 会作为镜像元数据持久化,并成为容器的环境变量。 |
设置方式 | ARG name=value in Dockerfile;<br>--build-arg in docker build |
ENV key=value in Dockerfile;<br>-e in docker run |
主要用途 | 1. 定义构建时配置,如版本号、依赖源。<br>2. 传递构建时所需的秘密信息(如 token),避免泄露。 | 1. 定义应用运行所需的配置,如数据库连接、端口、环境模式 (prod/dev)。<br>2. 设置路径等,方便 Dockerfile 后续指令使用。 |
简单总结:
- 需要在构建时传入、但不希望 在最终容器里存在的变量,用
ARG
。 - 需要在构建时设置、且希望 在最终容器里继续使用的变量,用
ENV
。
三、EXPOSE
与 .dockerignore
:端口声明与构建优化
最后,我们来看两个虽然不直接影响容器启动命令,但对于镜像的使用和构建效率至关重要的指令。
3.1 EXPOSE
:声明容器的"服务窗口"
EXPOSE
指令声明了容器在运行时监听的网络端口。
3.1.1 EXPOSE
的真正作用
- 语法 :
EXPOSE <port> [<port>/<protocol>...]
(protocol 默认为tcp
)
一个常见的误区 是认为 EXPOSE
会自动将容器端口发布到宿主机上。这是错误的!
EXPOSE
的真正作用是:
- 文档化: 它告诉镜像使用者,这个容器内的应用程序期望在哪个端口上提供服务。这是一种元数据,方便人和工具理解镜像。
- 自动化辅助 : 当使用
docker run -P
(大写 P) 运行时,Docker 会自动读取EXPOSE
的端口,并将其随机映射到宿主机的一个高位端口上。
示例:
dockerfile
# Dockerfile
FROM nginx
# Nginx 默认监听 80 端口
EXPOSE 80
-
使用
-P
自动映射 :bashdocker run -d -P nginx docker ps # 输出类似: # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 9e8a7b6c5d4f nginx "nginx -g 'daemon of..." 5 seconds ago Up 4 seconds 0.0.0.0:32768->80/tcp goofy_nobel
Docker 自动将容器的
80
端口映射到了宿主机的32768
端口。
3.1.2 EXPOSE
vs -p
参数
无论 Dockerfile 中是否有 EXPOSE
指令,真正控制端口映射的始终是 docker run
命令的 -p
(小写 p) 或 -P
参数。
-p <host_port>:<container_port>
: 精确指定将容器的哪个端口映射到宿主机的哪个端口。-P
: 自动映射所有EXPOSE
声明的端口。
结论 :EXPOSE
是一个非常有用的声明性 指令,建议始终为你的网络服务添加 EXPOSE
,但切记它本身不具备发布端口的功能。
3.2 .dockerignore
:构建上下文的"瘦身器"
当执行 docker build .
时,命令末尾的 .
表示当前的目录是"构建上下文"(build context)。Docker 客户端会将这个上下文中的所有文件和目录打包发送给 Docker 守护进程。如果目录中包含大量无关文件(如 .git
目录、本地依赖 node_modules
、日志文件、IDE 配置文件等),将会导致:
- 构建缓慢:发送巨大的上下文会消耗大量时间。
- 缓存失效:不必要的文件变动可能导致 Docker 缓存失效,从而重新执行耗时操作。
- 镜像臃肿 :如果误将这些文件
COPY
进镜像,会导致镜像体积不必要地增大。 - 安全风险 :可能泄露敏感信息,如
.env
文件、密钥等。
3.2.1 .dockerignore
的作用与语法
.dockerignore
文件就是用来解决这个问题的。它的工作方式类似 .gitignore
,你可以在其中列出需要从构建上下文中排除的文件和目录。
示例 .dockerignore
文件:
.dockerignore
# 忽略版本控制目录
.git
.gitignore
# 忽略 Node.js 的本地依赖目录
node_modules
# 忽略日志文件和 npm 调试日志
*.log
npm-debug.log
# 忽略 IDE 和操作系统生成的文件
.idea
.vscode
*.DS_Store
# 忽略 Docker 相关文件
Dockerfile
.dockerignore
# 忽略本地配置文件
.env.local
将此文件放在构建上下文的根目录(通常是项目根目录),docker build
时就会自动忽略这些文件,从而实现构建过程的"瘦身"。
四、总结
通过对 Dockerfile 启动与配置相关指令的深入学习,我们现在可以构建出更加专业和实用的 Docker 镜像。以下是本文的核心要点:
-
CMD
vsENTRYPOINT
:CMD
提供可被轻松覆盖的默认命令 ;ENTRYPOINT
定义容器的固定入口 ,将docker run
参数作为自己的参数。最佳实践是将两者结合,用ENTRYPOINT
指定主程序,用CMD
提供默认参数。 -
ARG
vsENV
:ARG
是构建时 的临时变量,用于配置构建过程,不会保留在最终镜像中;ENV
是运行时的环境变量,用于配置应用程序,会永久存在于容器中。 -
EXPOSE
的作用 :EXPOSE
是一种元数据声明 ,用于指明容器内服务监听的端口,它本身不发布端口 。真正的端口映射由docker run
的-p
或-P
参数完成。 -
.dockerignore
的重要性 : 通过创建.dockerignore
文件,可以从构建上下文中排除无关文件,从而加快构建速度、减小镜像体积、增强安全性,是 Dockerfile 最佳实践中不可或缺的一环。
熟练掌握这些指令,你将能自如地控制容器的启动行为、灵活地注入配置,并优化整个镜像的构建流程,为迈向更复杂的 Docker 应用打下坚实的基础。