Gin 本身不提供多环境配置抽象,必须用 Viper + ENV 环境变量动态加载 config.dev.yaml 等文件,并配合 AutomaticEnv() 实现环境变量覆盖,gin.Mode 仅影响日志和错误输出,与业务配置无关。

直接用 Viper + 环境变量控制,别手写 if/else 切配置。Gin 本身不提供多环境抽象,硬塞 gin.Mode(如 gin.ReleaseMode)只影响日志和错误输出,和数据库地址、API密钥这些业务配置完全无关。
为什么不能靠 gin.Mode 控制配置
gin.Mode 只改变框架内部行为:比如 gin.DebugMode 会打印详细错误堆栈、自动重载模板;gin.ReleaseMode 关闭这些。它不会读取 config.yaml 或切换 DB_HOST。很多新手误以为设了 gin.SetMode(gin.ReleaseMode) 就等于“切到生产环境”,结果部署后连不上生产数据库——因为配置还是开发时写的 localhost。
真正需要隔离的是:数据库连接串、Redis 地址、第三方 API Key、日志级别、是否启用 Sentry。这些必须由外部配置驱动,而不是 Gin 自身模式。
Viper 加载不同配置文件的实操要点
用 Viper 按环境名加载对应文件最稳妥,比如:config.dev.yaml、config.prod.yaml。关键不是“怎么写 YAML”,而是“怎么确保加载对的文件”:
- 不要在代码里硬编码
viper.SetConfigName("config.prod")—— 这会让构建产物绑定死环境 - 统一用环境变量
ENV=prod控制加载逻辑:viper.SetConfigName("config." + os.Getenv("ENV")) - 必须显式调用
viper.AddConfigPath("./config"),否则viper.ReadInConfig()会报Config File "config.xxx" Not Found - YAML 文件里避免用 tab 缩进,只用空格,否则解析失败且错误信息不提示具体行号
- 如果同时支持环境变量覆盖(例如
DB_PORT=5433覆盖 YAML 里的值),要在viper.AutomaticEnv()后加viper.SetEnvPrefix("APP"),这样APP_DB_PORT才能映射到db.port
本地开发时 ENV=dev 不生效的常见原因
执行 ENV=dev go run main.go 在 Linux/macOS 有效,但在 Windows CMD 下完全无效(PowerShell 也不认这种写法)。这是最常卡住新人的地方:
- Windows 用户必须用
set ENV=dev && go run main.go(CMD)或$env:ENV="dev"; go run main.go(PowerShell) - 更可靠的做法是把环境变量写进
.env文件,用viper.ReadEnvFile(".env")加载,再配合viper.AutomaticEnv()—— 这样跨平台一致 - 检查是否漏掉
viper.BindEnv("log.level", "LOG_LEVEL"):如果代码里写viper.GetString("log.level")却没绑定,环境变量就永远不会生效 - 运行前加一行
fmt.Println("ENV:", os.Getenv("ENV")),确认变量真被读到了,而不是 IDE 终端没继承系统环境
Docker 部署时如何安全传入生产配置
绝不能把 config.prod.yaml 打进镜像。一旦密钥写死在镜像层,所有能拉取该镜像的人都能提取出来:
- 构建阶段只放
config.dev.yaml和空的占位文件,用COPY config/ /app/config/是错的 - 启动容器时通过
-v /host/config.prod.yaml:/app/config.yaml:ro挂载,代码里viper.SetConfigFile("/app/config.yaml") - 敏感字段(如
db.password)全部用环境变量注入:docker run -e DB_PASSWORD=xxx my-gin-app,然后在 YAML 中写password: "{{ .Env.DB_PASSWORD }}"并启用viper.SetConfigType("yaml")+viper.ReadConfig(bytes.NewReader([]byte(template.Must(configTmpl.ExecuteToString(...)).Bytes())))—— 但更推荐直接用viper.Get("db.password"),它会自动从DB_PASSWORD读 - K8s 场景下,用
Secret挂载环境变量,比挂载文件更可控,也避免 YAML 解析失败导致 Pod 启动失败
配置加载顺序容易被忽略:Viper 默认按 “flag → env → config file → default” 覆盖。如果你在 config.yaml 里写了 timeout: 30,又通过 APP_TIMEOUT=60 设置环境变量,最终值是 60 —— 但没人会去翻文档确认这个优先级,出问题时只会怀疑 YAML 写错了。


















