安全高效、支持通配符白名单、默认值语法、原生就地编辑,专为 Nginx / Docker / Kubernetes 容器场景优化的增强版 envsubst 工具。
- Nginx 安全默认:仅替换
${VAR}格式,不破坏$host/$uri等内置变量 - 双模式切换:
--all启用传统模式,同时替换$VAR和${VAR} - 强大通配符白名单:支持前缀
REST_*、后缀*_PROD、中间匹配APP_*_API - 默认值支持:
${VAR:-default}bash 风格语法,未设置时使用默认值 - 🆕 原生就地编辑:
-i/--in-place选项,类似sed -i,安全原子操作 - 备份功能:
-i.bak自动备份原文件,支持自定义后缀 - 安全兜底:
-k保留未定义变量,不删除、不空白 - 调试与统计:
--debug显示替换过程,--stats/--json-stats提供统计信息 - 配置文件支持:
--whitelist-file从文件读取白名单规则 - 零依赖、体积小:纯 C 编写(~50KB),适合容器镜像、嵌入式、生产环境
- 容器友好:兼容标准输入输出,一键替换配置模板
# 编译(Linux/macOS 通用)
gcc -O2 -Wall -Wextra -std=c99 -pedantic envsubst.c -o envsubst
# 安装到系统
chmod +x envsubst
mv envsubst /usr/local/bin/envsubst v2.0.0 - 安全型环境变量替换工具 (支持通配符白名单)
【选项】
-h, --help 显示帮助信息
-V, --version 显示版本号
-v, --variables 列出允许的匹配规则并退出
-k, --keep-undefined 变量未定义时保留原字符串,不删除
-i, --in-place[=SUFFIX] 就地编辑文件(可选备份后缀)🆕
--all 全替换模式:同时替换 $VAR 和 ${VAR}
--debug 调试模式:显示每个变量的替换过程
--stats 统计模式:显示替换统计信息
--json-stats JSON 格式统计信息(便于机器解析)
--whitelist-file F 从文件 F 读取白名单规则(每行一个)
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.confenvsubst 'REST_* WAF_* CONFIG_*' < nginx.conf.template > nginx.conf# 变量未设置,使用默认值
echo '${HOST:-localhost}' | envsubst
# 输出: localhost
# 变量已设置,使用环境变量
echo '${HOST:-localhost}' | HOST=example.com envsubst
# 输出: example.com
# Nginx 配置中的默认值
echo 'listen ${PORT:-80}; server_name ${HOST:-localhost};' | envsubst
# 输出: listen 80; server_name localhost;
# ⚠️ 注意:白名单会影响默认值的行为
echo '${ABCD:-9999999}' | envsubst # 输出: 9999999 ✅
echo '${ABCD:-9999999}' | envsubst 'REST_*' # 输出: ${ABCD:-9999999} (不在白名单,原样保留)
echo '${ABCD:-9999999}' | envsubst 'ABCD' # 输出: 9999999 ✅ (在白名单)envsubst --all < app.tpl > app.confenvsubst -k 'APP_*' < config.tpl > config.confecho '${HOST} ${PORT}' | HOST=localhost envsubst --debug 'HOST_*' 2>&1
# 输出:
# [DEBUG] Replace: ${HOST} -> localhost
# [DEBUG] Skip (not in whitelist): ${PORT}
# localhost ${PORT}# 人类可读格式
echo '${A} ${B}' | A=1 envsubst --stats 2>&1
# 输出:
# === envsubst Statistics ===
# Variables replaced: 2
# Variables kept: 0
# Variables skipped: 0
# Total processed: 3
# ========================
# JSON 格式(便于机器解析)
echo '${A} ${B}' | A=1 envsubst --json-stats 2>stats.json
jq '.envsubst_stats.variables_replaced' stats.json
# 输出: 2# 基本就地编辑
envsubst -i 'APP_* DB_*' config.conf
# 带备份的就地编辑
envsubst -i.bak 'APP_* DB_*' config.conf
# 结果:
# config.conf ← 新内容
# config.conf.bak ← 原内容备份
# 自定义备份后缀
envsubst -i.20250101 'PROD_*' prod.conf
# 结果: prod.conf.20250101
# 无白名单(全部替换)
envsubst -i config.conf优势:
- ✅ 语法简洁优雅(类似
sed -i) - ✅ 原子操作,安全可靠
- ✅ 自动清理临时文件
- ✅ 可选备份功能
# 创建白名单文件 rules.txt
cat > rules.txt << EOF
# 这是注释
REST_*
WAF_*
APP_*
EOF
# 使用白名单文件
envsubst --whitelist-file rules.txt < template.conf > output.confcat app.tpl | envsubst 'DB_* REDIS_*' > app.cfg传统方式(v1.x):
COPY envsubst /usr/local/bin/
COPY nginx.conf.template /etc/nginx/
# 启动前替换变量
CMD envsubst 'REST_* WAF_*' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf \
&& nginx -g 'daemon off;'🆕 推荐方式(v2.0.0+):
COPY envsubst /usr/local/bin/
COPY nginx.conf.template /etc/nginx/
# 使用原地编辑,更简洁安全
CMD envsubst -i.bak 'REST_* WAF_*' /etc/nginx/nginx.conf.template \
&& mv /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf \
&& nginx -g 'daemon off;'# 第一阶段:编译 envsubst
FROM gcc:alpine AS builder
WORKDIR /build
COPY envsubst.c .
RUN gcc -O2 -Wall -Wextra -std=c99 envsubst.c -o envsubst
# 第二阶段:生产镜像
FROM nginx:alpine
COPY --from=builder /build/envsubst /usr/local/bin/
COPY nginx.conf.template /etc/nginx/
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]docker-entrypoint.sh:
#!/bin/sh
set -e
echo "🔧 Generating nginx configuration..."
# 使用白名单安全替换,保护 Nginx 内置变量
envsubst 'NGINX_* WAF_* APP_*' \
< /etc/nginx/nginx.conf.template \
> /etc/nginx/nginx.conf
echo "✅ Configuration generated successfully"
exec "$@"从 v3.5 开始,以下 Alpine 镜像已内置最新版本的 envsubst v2.0.0+:
| 镜像源 | 镜像名称 | 版本支持 |
|---|---|---|
| GitHub Container Registry | ghcr.io/tekintian/alpine |
3.5 - 3.23 ✅ |
| Docker Hub | tekintian/alpine |
3.5 - 3.23 ✅ |
| 阿里云容器镜像 | registry.cn-hangzhou.aliyuncs.com/alpine-docker/alpine |
3.5 - 3.23 ✅ |
查看所有版本: https://github.com/tekintian/alpine/pkgs/container/alpine
# 使用 GitHub Container Registry (推荐)
FROM ghcr.io/tekintian/alpine:3.21
# envsubst 已经内置,直接使用!
COPY nginx.conf.template /etc/nginx/
CMD envsubst -i.bak 'NGINX_* APP_*' /etc/nginx/nginx.conf.template \
&& mv /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf \
&& nginx -g 'daemon off;'# 使用阿里云镜像(国内访问更快)
FROM registry.cn-hangzhou.aliyuncs.com/alpine-docker/alpine:3.21
COPY nginx.conf.template /etc/nginx/
# 直接使用内置的 envsubst
RUN envsubst -i 'NGINX_*' /etc/nginx/nginx.conf.template
CMD ["nginx", "-g", "daemon off;"]version: '3.8'
services:
nginx:
image: ghcr.io/tekintian/alpine:3.21
volumes:
- ./nginx.conf.template:/etc/nginx/nginx.conf.template
- ./html:/usr/share/nginx/html
environment:
- NGINX_HOST=example.com
- NGINX_PORT=80
- APP_BACKEND=http://api:8080
ports:
- "80:80"
command: >
sh -c "envsubst -i.bak 'NGINX_* APP_*' /etc/nginx/nginx.conf.template
&& mv /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf
&& nginx -g 'daemon off;'"apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
spec:
template:
spec:
# 使用 init container 生成配置
initContainers:
- name: config-generator
image: ghcr.io/tekintian/alpine:3.21
command: ['sh', '-c']
args:
- |
echo "Generating nginx config..."
envsubst -i 'NGINX_* APP_* DB_*' /config/nginx.conf.template
echo "✅ Config generated"
volumeMounts:
- name: config-volume
mountPath: /config
envFrom:
- configMapRef:
name: nginx-env-vars
# 主容器
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume
emptyDir: {}| 方式 | 优点 | 缺点 |
|---|---|---|
| 预构建镜像 | ✅ 零配置 ✅ 即时可用 ✅ 自动更新 |
|
| 多阶段构建 | ✅ 完全可控 ✅ 无外部依赖 |
|
| 手动复制二进制 | ✅ 简单直接 |
推荐场景:
- 🚀 快速原型/开发环境:使用预构建镜像
- 🏭 生产环境:根据团队策略选择(预构建或多阶段构建)
- 🌏 国内用户:优先使用阿里云镜像
#!/bin/sh
envsubst -k 'APP_* API_*' < /app/config.template > /app/config.yaml
exec /app/mainFROM nginx:alpine
COPY envsubst /usr/local/bin/
COPY config.tpl /etc/nginx/
# 不提供环境变量时使用默认值
CMD envsubst < /etc/nginx/config.tpl > /etc/nginx/nginx.conf \
&& nginx -g 'daemon off;'# 开发环境 - 使用默认值
docker run myapp
# 生产环境 - 覆盖关键配置
docker run -e PORT=443 -e HOST=prod.example.com myappdocker run --rm \
-e REST_HOST=api.example.com \
-v $(pwd)/app.tpl:/tmp/app.tpl \
your-image envsubst < /tmp/app.tpl > /tmp/app.out- 优先使用白名单通配符,避免意外替换 Nginx 内置变量
- Nginx 配置不要使用
--all,避免破坏$proxy_host等变量 - 容器场景始终加
-k,防止变量缺失导致配置异常
- 使用默认值:为非关键配置提供合理的默认值(
${VAR:-default}) - 命名规范:使用前缀区分不同模块(
DB_*,API_*,CACHE_*) - 模板注释:在模板中标注哪些变量需要替换
- 版本控制:将
.template文件纳入版本控制,生成的配置文件忽略
- 使用 JSON 统计:在 CI/CD 中使用
--json-stats进行质量检查 - 白名单文件:将白名单规则纳入版本控制,便于审查
- 多环境配置:dev/staging/prod 统一使用模板 + 环境变量注入
- 精简白名单:只列出实际需要的变量前缀
- 预编译二进制:在 Docker 镜像中直接使用编译好的二进制
- 流式处理:对于大文件,使用管道而非临时文件
问题: 模板中的 ${VAR} 没有被替换成实际值
解决方案:
-
确认变量已导出为环境变量:
export MY_VAR=value # ✅ 正确 MY_VAR=value # ❌ 错误(只是 shell 变量)
-
检查变量名格式:
# ✅ 合法的变量名 ${MY_VAR} ${APP_HOST_123} # ❌ 非法的变量名(会原样保留) ${MY VAR} # 包含空格 ${123NUM} # 以数字开头 ${VAR@HOST} # 包含特殊字符
-
使用
-v查看当前允许的规则:envsubst -v 'REST_* WAF_*' # 输出: # REST_* # WAF_*
-
使用
--debug调试:echo '${MY_VAR}' | MY_VAR=value envsubst --debug 2>&1 # 输出: [DEBUG] Replace: ${MY_VAR} -> value
问题: 替换后 Nginx 启动失败,报错未知变量
原因: 使用了 --all 模式,导致 Nginx 内置变量(如 $host, $uri)被误替换
解决方案:
# ❌ 错误做法
envsubst --all < nginx.conf.template > nginx.conf
# ✅ 正确做法 - 使用白名单
envsubst 'REST_* WAF_* APP_*' < nginx.conf.template > nginx.conf问题: 设置了白名单但变量仍未被替换
检查清单:
-
通配符语法是否正确:
# ✅ 支持的格式 'PREFIX_*' # 前缀匹配 '*_SUFFIX' # 后缀匹配 'MID_*_MID' # 中间匹配 # ❌ 不支持的格式 'VAR' # 缺少通配符 * '*VAR*' # 多个通配符(只支持一个 *)
-
多个规则是否用空格或逗号分隔:
# ✅ 正确 envsubst 'A_* B_* C_*' envsubst 'A_*,B_*,C_*' # ❌ 错误 envsubst 'A_*' 'B_*' # 会被当作多个参数
-
验证规则解析:
envsubst -v 'REST_* WAF_*'
检查:
# 确保语法正确
echo '${VAR:-default}' | envsubst --debug 2>&1
# 常见错误:
# ${VAR-default} ❌ 缺少冒号
# ${VAR: -default} ❌ 有空格
# ${VAR:-default} ✅ 正确白名单影响:
# 如果使用了白名单,变量必须在白名单中才能替换
echo '${ABCD:-9999999}' | envsubst # ✅ 输出: 9999999
echo '${ABCD:-9999999}' | envsubst 'REST_*' # ⚠️ 输出: ${ABCD:-9999999} (不在白名单)
echo '${ABCD:-9999999}' | envsubst 'ABCD' # ✅ 输出: 9999999 (在白名单)
# 解决方案:将变量加入白名单或使用通配符
envsubst 'ABCD REST_* WAF_*' < template.conf
envsubst '*' < template.conf # 匹配所有变量检查:
# 确保重定向 stderr
envsubst --json-stats < tpl.conf 2>stats.json
# 验证 JSON 格式
jq '.' stats.json
# 常见错误:
# envsubst --json-stats < tpl.conf > stats.json ❌ 错误,stdout 是配置内容
# envsubst --json-stats < tpl.conf 2>stats.json ✅ 正确,stderr 是 JSON问题: envsubst < file > file 会清空文件
原因: Shell 重定向机制导致,不是 envsubst 的 bug
解决方案:
-
🆕 推荐:使用
-i选项(v2.0.0+)envsubst -i 'APP_*' config.conf envsubst -i.bak 'APP_*' config.conf # 带备份
-
传统方式:使用临时文件
envsubst < config.tpl > config.tmp && mv config.tmp config.tpl
-
使用 sponge 工具
envsubst < config.tpl | sponge config.tpl
问题: 使用 -v 挂载配置文件时,envsubst -i 报错:
mv: can't rename '/path/to/nginx.conf.tmp': Resource busy
原因:
- Docker 挂载卷的文件可能被宿主机文件系统锁定
envsubst -i内部使用rename()系统调用,在挂载卷上可能失败sed -i也有同样的问题
解决方案 1: 使用内容覆盖而非文件重命名(推荐)
#!/bin/sh
# docker-entrypoint.sh
NGINX_CONF="/etc/nginx/nginx.conf"
# 创建临时文件
TMP_CONF=$(mktemp /tmp/nginx.conf.XXXXXX)
trap "rm -f '$TMP_CONF'" EXIT
# 替换变量到临时文件
envsubst < "$NGINX_CONF" > "$TMP_CONF"
# 使用 cat 覆盖原文件内容(而不是 mv)
cat "$TMP_CONF" > "$NGINX_CONF"
# 清理
rm -f "$TMP_CONF"
trap - EXIT
exec nginx -g 'daemon off;'原理:
cat file > target是写入文件内容,不改变 inodemv file target是重命名文件,会改变 inode(在挂载卷上可能失败)
解决方案 2: 使用模板文件
# Dockerfile
COPY nginx.conf.template /etc/nginx/
COPY docker-entrypoint.sh /
CMD ["/docker-entrypoint.sh"]#!/bin/sh
# docker-entrypoint.sh
# 从模板生成配置(不涉及原地编辑)
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
exec nginx -g 'daemon off;'解决方案 3: 使用智能检测脚本
参考项目中的 docker-entrypoint-safe.sh,它会自动检测挂载卷并选择合适的策略。
对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 内容覆盖 (cat) | ✅ 兼容挂载卷 ✅ 简单可靠 |
|
| 模板文件 | ✅ 最安全 ✅ 无冲突 |
|
| 智能检测脚本 | ✅ 自动适配 ✅ 最佳体验 |
|
| envsubst -i | ✅ 简洁 | ❌ 挂载卷可能失败 |
推荐:
- 🚀 开发环境:使用模板文件方案
- 🏭 生产环境:使用智能检测脚本
- ⚡ 快速测试:使用内容覆盖方案
| 特性 | GNU envsubst | 本工具 |
|---|---|---|
${VAR} 替换 |
✅ | ✅ |
$VAR 替换 |
✅ | --all |
| 保护 Nginx 变量 | ❌ | ✅ 默认启用 |
| 通配符白名单 | ❌ | ✅ 支持 |
| 保留未定义变量 | ❌ | ✅ -k 选项 |
| 默认值支持 | ❌ | ✅ ${VAR:-default} |
| 非法变量名处理 | 删除/空白 | ✅ 原样保留 |
| 调试模式 | ❌ | ✅ --debug |
| 统计信息 | ❌ | ✅ --stats/--json-stats |
| 配置文件支持 | ❌ | ✅ --whitelist-file |
| 🆕 就地编辑 | ❌ | ✅ -i / --in-place |
| 二进制大小 | ~17KB | ~50KB |
| 依赖库 | gettext (libintl) | 无(零依赖) |
| 容器友好度 | 一般 | ✅ 优秀 |
| 适用场景 | 通用 | Nginx/Docker/K8s |
选择建议:
- 🟢 Nginx 配置: 必须使用本工具(保护内置变量)
- 🟢 Docker/K8s: 推荐本工具(零依赖、白名单更安全)
- 🟡 通用场景: 两者均可,本工具提供更多安全特性
- 🔴 需要
$VAR频繁替换: 考虑使用--all或其他工具
- Repo: https://github.com/tekintian/envsubst/
- Version: v2.0.0
- License: MIT
- IN_PLACE_EDITING.md - 就地编辑完整指南 🆕
- SPECIAL_SCENARIOS.md - 特殊场景分析
- CSDN_ARTICLE.md - 技术文章
- 微信公众号:技术与认知
- 站点:https://ai.tekin.cn
- 邮箱:tekintian@gmail.com