Viper and Environment Variables

Viper supports automatic environment variables, aliases, binding, prefixes, and empty environment variables.

Viper and environment variables

Environment variables are an important configuration source and the only one for 12-factor applications, which is a common pattern for building service-based systems.

Viper has excellent support for environment variables. When using Viper with environment variables it’s important to note that environment variables are case-sensitive.

Automatic environment

The simplest way to use environment variables is via the viper.AutomaticEnv() function, which makes all environment variables available as Viper keys.

func main() {
	viper.AutomaticEnv()
	home := viper.Get("home")
	fmt.Println(home)
}

/Users/gigi.sayfan

This is very straightforward, but in many cases, you may want to map environment variables to specific Viper keys. This is especially important using multiple configuration modes. For example, your program may have a configuration key called “output-dir” for the location of your output files, but if it is not defined you want to default to the HOME environment variable. You can do that with Viper’s RegisterAlias() function that allows us to have multiple keys for the same value.

Using aliases

func main() {
	viper.AutomaticEnv()
	viper.RegisterAlias("output-dir", "HOME")
	outputDir := viper.GetString("output-dir")
	fmt.Println(outputDir)
}

/Users/gigi.sayfan

You can use RegisterAlias() for any Viper value to access it using multiple names, but I find it especially useful for environment variables.

Binding environment variables

viper.AutomaticEnv() is very convenient, but since it brings all the environment variables, you might inadvertently pull in some environment variable you didn’t set but has the same name as one of your configuration keys (e.g. in a configuration file). This could be a pretty bad situation that is only discovered in production, and difficult to reproduce (Often, your production environment is not identical to your local or staging environment.). Instead of pulling the entire environment it is safer to bind to specific environment variables:

func main() {
	err := viper.BindEnv("home")
	if err != nil {
		fmt.Println(err.Error())
	}

	home := viper.GetString("home")
	fmt.Println(home)
}

/Users/gigi.sayfan

viper.BindEnv() accepts one or two arguments, and it uses this signature:

func (v *Viper) BindEnv(input ...string) error {

It returns an error only if you provide no arguments. I don’t really like this design choice because it’s not clear from the code what the input should be. If you provide just a single input argument, Viper will treat it as both the key and the environment variable name. When binding to an environment variable using viper.BindEnv() with just a key name, the function will capitalize the key to determine the environment variable name. For example viper.BindEnv("name") will search for an environment variable called “NAME”. If you want to bind to an environment variable whose name is not capitalized you must pass the environment variable name as a second argument:

func main() {
	os.Setenv("NAME", "1")
	os.Setenv("nAmE", "2")

	viper.BindEnv("name")
	fmt.Println(viper.Get("name"))

	viper.BindEnv("name", "nAmE")
	fmt.Println(viper.Get("name"))
}

1
2

Note, that I used bind twice with the same key “name” for two different environment variables. Viper will always use the latest binding, and it doesn’t cache the value.

BindEnv() with an environment variable name is another alternative for RegisterAlias() when binding specific variables.

Using prefixes

One of the main problems with environment variables is that they are a shared resource, which allows name collisions to happen. We mentioned how binding to specific environment variables is safer than using AutomaticEnv(), but if you pick too generic environment variable names like “NAME” or “HOME” or “USER”, you might conflict with existing environment variables. To avoid cluttering the global environment namespace or conflicting with existing variables, I recommend using a prefix for your application. For example, try multi-git using the “MG_” prefix for its environment variables.

Viper supports prefixes via the SetEnvPrefix() function. Then you don’t need to specify the prefix when binding to a variable:

func main() {
	// This is usually set outside the program
	os.Setenv("PREFIX_VAR", "hello")
	viper.SetEnvPrefix("prefix") // will be capitalized
	viper.BindEnv("var")

	fmt.Println(viper.Get("var"))
}

hello

Note that if you provide an environment variable name (the second argument) to BindEnv() will use it as is to look up the environment variable and the prefix will not be used.

When you set a prefix AutomaticEnv() also fetches only environment variables with the prefix

func main() {
	// This is usually set outside the program
	os.Setenv("PREFIX_VAR", "hello")
	os.Setenv("PREFIX_VAR2", "hello2")

	viper.SetEnvPrefix("prefix")
	viper.AutomaticEnv()

	fmt.Println(viper.Get("var"))
	fmt.Println(viper.Get("var2"))

	if !viper.IsSet("home") {
		fmt.Println("The key 'home' is not set")
	}
}

hello
hello2
The key 'home' is not set

Understanding empty environment variable

One sneaky gotcha of Viper is the treatment of empty environment variables. If an environment variable is set to an empty string Viper will treat it as unset by default and will continue looking for other configuration sources. Since empty strings may be a valid configuration value, you may want to change this behavior. You can use the viper.AllowEmptyEnv() to do that.

func main() {
	os.Setenv("EMPTY", "")
	viper.BindEnv("empty")
	if !viper.IsSet("empty") {
		fmt.Println("The key 'empty' is not considered set")
	}

	viper.AllowEmptyEnv(true)
	if viper.IsSet("empty") {
		fmt.Println("The key 'empty' is considered set")
	}
}


The key 'empty' is not considered set
The key 'empty' is considered set

Get hands-on with 1400+ tech skills courses.