Problem Statement
Implement 12-factor app configuration using Viper to read from files, environment variables, and flags with proper precedence.
Basic Viper Setup
package config
import (
"github.com/spf13/viper"
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
Redis RedisConfig
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Host string `mapstructure:"host"`
Debug bool `mapstructure:"debug"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Name string `mapstructure:"name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
}
func Load() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("./config")
// Environment variable support
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")
// Set defaults
viper.SetDefault("server.port", 8080)
viper.SetDefault("server.host", "0.0.0.0")
if err := viper.ReadInConfig(); err != nil {
// Config file not found is OK
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, err
}
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}Config File (config.yaml)
server:
port: 8080
host: "0.0.0.0"
debug: false
database:
host: "localhost"
port: 5432
name: "myapp"
user: "postgres"
password: "${DB_PASSWORD}" # Reference env var
redis:
host: "localhost"
port: 6379Environment Variable Override
# Override with environment variables
export APP_SERVER_PORT=9090
export APP_DATABASE_HOST=db.prod.example.com
./myapp
# Uses port 9090 and production databaseConfig File Watching
func WatchConfig(onChange func()) {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Println("Config file changed:", e.Name)
onChange()
})
}
// Usage:
WatchConfig(func() {
newCfg, _ := Load()
updateRuntimeConfig(newCfg)
})Command Line Flags with pflag
import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
func init() {
pflag.Int("port", 8080, "Server port")
pflag.Bool("debug", false, "Enable debug mode")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
}
// Precedence: flags > env > config file > defaultsBest Practices
- Always unmarshal into a struct (type safety)
- Use SetDefault for all config values
- Don't read viper.GetString() everywhere - load once at startup
- Validate config immediately after loading