CMD指令本身不解析环境变量,但可通过shell形式(如CMD sh -c "cmd $VAR")、ENTRYPOINT+CMD分离、自定义entrypoint.sh脚本或ARG+ENV组合实现动态配置。

Dockerfile 中的 CMD 指令本身不解析环境变量,但可以通过合理组合 ENV、SHELL 模式(即 shell 形式)或封装启动脚本,实现“动态配置启动参数”的效果。关键在于理解 CMD 的两种写法及其变量展开时机。
使用 shell 形式 CMD 直接展开环境变量
当 CMD 以字符串形式(shell 形式)书写时,它会在容器启动时由 /bin/sh -c 执行,此时环境变量会被 shell 解析:
ENV APP_PORT=8080 ENV LOG_LEVEL=info CMD ["sh", "-c", "exec java -Dlog.level=$LOG_LEVEL -jar app.jar --server.port=$APP_PORT"]
注意:必须用双引号包裹整个命令字符串,并使用 sh -c 显式调用 shell,否则 JSON 数组形式(exec 形式)不会展开变量。
- ✅ 正确:CMD sh -c "java ... $VAR"
- ❌ 错误:CMD ["java", "-Dport=$PORT", "app.jar"] —— $PORT 不会替换
通过 ENTRYPOINT + CMD 分离静态与动态部分
更推荐的方式是把固定入口(如启动脚本或 java 命令)设为 ENTRYPOINT,CMD 仅传参。这样既保留可覆盖性,又支持变量注入:
ENV DEFAULT_PORT=8080 ENTRYPOINT ["sh", "-c", "exec java -jar app.jar --server.port=$1 --logging.level.root=$2"] CMD ["$DEFAULT_PORT", "info"]
运行时可通过 docker run -e DEFAULT_PORT=9000 myapp 覆盖默认值,或直接覆盖 CMD:docker run myapp 9000 warn。
用自定义启动脚本增强灵活性
对复杂逻辑(如条件判断、文件存在检查),建议将启动逻辑写入 entrypoint.sh:
COPY entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"]
entrypoint.sh 内容示例:
#!/bin/sh
PORT=${APP_PORT:-8080}
LOG_LEVEL=${LOG_LEVEL:-info}
exec java -Dlogging.level.root="$LOG_LEVEL" -jar app.jar --server.port="$PORT" "$@"
这样既能读取环境变量,又能透传额外参数($@),也便于本地调试。
构建时 vs 运行时变量要分清
ARG 是构建阶段变量,ENV 才在运行时生效。若想让构建时传入的值影响启动行为,需先用 ARG 设 ENV:
ARG BUILD_PORT=8080 ENV APP_PORT=$BUILD_PORT CMD ["sh", "-c", "java -jar app.jar --server.port=$APP_PORT"]
构建时: docker build --build-arg BUILD_PORT=9000 .,则运行时 $APP_PORT 就是 9000。










