Introduction
Viper handles all configuration needs for Go applications. Created by Steve Francia (also creator of Cobra and Hugo), Viper provides a unified interface for reading configuration from files, environment variables, command-line flags, and remote systems like etcd and Consul.
With over 30,000 GitHub stars, Viper is the companion to Cobra for CLI applications and the standard configuration library across the Go ecosystem. The Cobra+Viper combination powers the configuration systems of Kubernetes, Docker, Hugo, and many other tools.
What Viper Does
Viper reads and merges configuration from multiple sources with a clear precedence order: explicit Set calls > flags > environment variables > config file > key/value store > defaults. It supports nested keys (database.host), type coercion, unmarshaling to structs, and live config watching.
Architecture Overview
[Configuration Sources (precedence order)]
1. viper.Set() — explicit overrides
2. Command-line flags — via pflag/Cobra
3. Environment variables — APP_DATABASE_HOST
4. Config file — config.yaml
5. Remote KV store — etcd, Consul
6. Defaults — viper.SetDefault()
|
[Viper Core]
Merge all sources
Type coercion
Nested key access
|
[Access Methods]
viper.GetString("key")
viper.GetInt("key")
viper.Unmarshal(&config)
viper.WatchConfig()Self-Hosting & Configuration
import (
"github.com/spf13/viper"
"github.com/spf13/cobra"
"log"
)
type Config struct {
Port int `mapstructure:"port"`
Debug bool `mapstructure:"debug"`
Database DBConfig `mapstructure:"database"`
}
type DBConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Name string `mapstructure:"name"`
Password string `mapstructure:"password"`
}
func LoadConfig() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.myapp")
viper.AddConfigPath("/etc/myapp")
// Environment variable binding
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// MYAPP_DATABASE_HOST -> database.host
viper.SetDefault("port", 8080)
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.port", 5432)
if err := viper.ReadInConfig(); err != nil {
log.Printf("No config file: %v", err)
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, err
}
// Watch for config changes
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Printf("Config changed: %s", e.Name)
viper.Unmarshal(&config)
})
return &config, nil
}Key Features
- Multi-Format — JSON, TOML, YAML, HCL, envfile, Java properties
- Env Variables — automatic binding with prefix and key replacement
- Cobra Integration — seamless flag binding with Cobra CLI
- Nested Keys — access deep config with dot notation (db.host)
- Struct Unmarshaling — decode config into Go structs
- Live Watching — detect and reload config file changes
- Remote Config — read from etcd, Consul, and Firestore
- Defaults — set sensible defaults with clear precedence
Comparison with Similar Tools
| Feature | Viper | envconfig | koanf | cleanenv |
|---|---|---|---|---|
| File Formats | 6+ | No (env only) | 10+ | YAML, env |
| Env Variables | Yes | Yes (primary) | Yes | Yes |
| Cobra Integration | Native | No | No | No |
| Watch/Reload | Yes | No | Yes | No |
| Remote Config | etcd, Consul | No | Various | No |
| Complexity | Moderate | Very Low | Moderate | Low |
| Best For | Full-featured apps | Simple env config | Flexible config | Struct-based |
FAQ
Q: How does Viper work with Cobra? A: Bind Cobra flags to Viper keys with viper.BindPFlag("port", cmd.Flags().Lookup("port")). This lets config file, env var, and CLI flag all set the same value with proper precedence.
Q: Can I use Viper without config files? A: Yes. Use viper.SetDefault() for defaults and viper.AutomaticEnv() for environment variables. Config files are optional.
Q: How do I handle secrets? A: Use environment variables for secrets (never put them in config files). Viper.AutomaticEnv() reads them automatically. For production, use a secrets manager and inject as env vars.
Q: Is Viper thread-safe? A: Viper is safe for concurrent reads. For concurrent writes (rare), use viper.Set() which is also safe. Config watching runs in a separate goroutine safely.
Sources
- GitHub: https://github.com/spf13/viper
- Documentation: https://github.com/spf13/viper#readme
- Created by Steve Francia (spf13)
- License: MIT