Configuring Go application with flags, pflags, environment variables and Viper

There are multiple ways to configure your Go application. I will describe a few most common one in this article with code samples.

Flags

Go supports command line flags with builtin package flag.

package main

import (
	"flag"
	"log"
	"net/http"
)

func main() {
	addrPtr := flag.String("addr", ":8000", "addr of http server")
	flag.Parse()
	log.Printf("Listening on %s", *addrPtr)
	log.Fatal(http.ListenAndServe(*addrPtr, nil))
}
func main() {
	var addr string
	flag.StringVar(&addr, "addr", ":8000", "addr of http server")
	flag.Parse()
	log.Printf("Listening on %s", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}

You can use flag.String flag.Int and so on to return a pointer to variable. If you prefer you can bind the flag to a variable with Var() functions (as for example flag.StringVar flag.IntVar. The full list is here.

go run main.go -addr :8888
2023/11/05 20:52:28 Listening on :8888

POSIX-style –flags

pflag is a drop-in replacement for Go’s flag package, implementing POSIX/GNU-style –flags

package main

import (
	"github.com/spf13/pflag"

	"log"
	"net/http"
)

func main() {
	addr := pflag.String("addr", ":8000", "addr of http server")
	pflag.Parse()
	log.Printf("Listening on %s", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
go run main.go --addr :8888
2023/11/05 23:03:49 Listening on :8888

Environment variables

According to The Twelve-Factor App the application should be configurable with environment variables.

import (
    "http"
    "log"
    "os"
)

func main() {
	addr := os.Getenv("ADDR")
	if addr == "" {
		addr = ":8000"
	}
	log.Printf("Listening on %s", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}
ADDR=8888 go run main.go
2023/11/05 20:54:35 Listening on :8888

Viper

Viper is a full solution for app configuration. It supports:

  • defaults
  • multiple formats including JSON, TOML, YAML, HCL, envfile and Java properties config files
  • live watching and re-reading of config files (optional)
  • reading from environment variables
  • reading from remote config systems (etcd or Consul), and watching changes
  • reading from command line flags
  • reading from buffer

Reading from files and envvars

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/spf13/viper"
)

func main() {
	viper.SetDefault("addr", ":8000")
	viper.AddConfigPath("./config")
	viper.SetConfigName("default")
	viper.AutomaticEnv()
	err := viper.ReadInConfig()
	if err != nil {
		log.Fatal(fmt.Errorf("fatal error config file: %w", err))
	}
	addr := viper.GetString("addr")

	log.Printf("Listening on %s", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}
// config/default.yaml

ADDR: ":8888"

Unmarshaling the config into a struct

Viper has an ability to unmarshal config to a struct or map.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/spf13/viper"
)

type config struct {
	Addr string
}

func main() {
	// ...

	var c config
	err = viper.Unmarshal(&c)
	if err != nil {
		log.Fatal(fmt.Errorf("error unmarshalling: %w", err))
	}

	log.Printf("Listening on %s", c.Addr)
	log.Fatal(http.ListenAndServe(c.Addr, nil))
}

Combining Viper and pflag full example

Viper supports binding to flags with pflag package. This way you can configure the app by:

  • flag
  • environment variable
  • file
package main

import (
	"fmt"
	"log"
	"net/http"
	
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

type config struct {
	Addr string
}

func main() {
	pflag.String("addr", ":8000", "addr of http server")
	pflag.Parse()

	viper.SetDefault("addr", ":8000")
	err := viper.BindPFlags(pflag.CommandLine)
	if err != nil {
		log.Fatal(fmt.Errorf("fatal binding pflags: %w", err))
	}
	viper.AddConfigPath("./config")
	viper.SetConfigName("default")
	viper.AutomaticEnv()
	err = viper.ReadInConfig()
	if err != nil {
		log.Fatal(fmt.Errorf("fatal error config file: %w", err))
	}

	var c config
	err = viper.Unmarshal(&c)
	if err != nil {
		log.Fatal(fmt.Errorf("error unmarshalling: %w", err))
	}

	log.Printf("Listening on %s", c.Addr)
	log.Fatal(http.ListenAndServe(c.Addr, nil))
}