ScriptsApr 13, 2026·3 min read

Viper — Complete Configuration Solution for Go

Viper is the most popular configuration library for Go applications. It reads config from JSON, TOML, YAML, HCL, envfile, and Java properties files, supports environment variables, remote config systems, and live watching for config changes.

SC
Script Depot · Community
Quick Use

Use it first, then decide how deep to go

This block should tell both the user and the agent what to copy, install, and apply first.

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    viper.AutomaticEnv()

    viper.SetDefault("port", 8080)
    viper.SetDefault("debug", false)

    if err := viper.ReadInConfig(); err != nil {
        fmt.Printf("No config file found, using defaults\n")
    }

    fmt.Printf("Port: %d\n", viper.GetInt("port"))
    fmt.Printf("Debug: %v\n", viper.GetBool("debug"))
    fmt.Printf("DB Host: %s\n", viper.GetString("database.host"))
}
# config.yaml
port: 3000
debug: true
database:
  host: localhost
  port: 5432
  name: myapp
  password: ${DB_PASSWORD}  # resolved from env

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 fileconfig.yaml
5. Remote KV storeetcd, 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

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets