Configuration files
Config files let you supply persistent configuration without command-line arguments. Go supports JSON natively; YAML, INI, and environment variables require small third-party libraries or the standard os package.
JSON
Go’s encoding/json package reads JSON config files into structs. JSON doesn’t support comments.
Here is conf.json:
{
"username": "rjs",
"password": "secret",
"port": 4001,
"storage": "path/to/local/store",
"enableFlag": true
}
This code sample parses conf.json and stores its values in memory:
Capitalize all
configfields to export them.os.Openreturns a*os.Fileand an error.*os.Fileimplementsio.Reader.Always
deferthe file close immediately after opening it.json.NewDecodertakes anio.Reader.Decodematches keys inconf.jsonto fields in theconfigstruct. Matching is case-insensitive. Unmatched keys are ignored. Only exported fields are matched.Use
Decoderather thanjson.Unmarshalwhen reading from a stream, file, or connection. Usejson.Unmarshalwhen the JSON data is already in memory as a[]byte.
This example writes errors to stderr with fmt.Fprintf(os.Stderr, ...), which makes the destination explicit. In production code, prefer log.Printf (adds timestamps automatically) or slog.Error (Go 1.21+, structured key-value output). All three write to stderr by default.
type config struct { // 1
Username string
Password string
Port int
Storage string
EnableFlag bool
}
func jsonConfig() {
file, err := os.Open("conf.json") // 2
if err != nil {
fmt.Fprintf(os.Stderr, "cannot open conf.json: %v\n", err)
return
}
defer file.Close() // 3
decoder := json.NewDecoder(file) // 4
cfg := config{}
err = decoder.Decode(&cfg) // 5
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing config file: %v\n", err)
return
}
// output with field names
fmt.Printf("%+v\n", cfg)
}
YAML
YAML supports comments, which JSON does not. Go doesn’t include a native YAML parser. The Gypsy library provides a simple key-value API for reading YAML files:
For new projects, consider gopkg.in/yaml.v3. It’s actively maintained and uses struct tags like encoding/json.
Here is conf.yaml:
# Test conf file
username: "abc"
password: "secret"
port: 4001
enableFlag: true
This code sample parses conf.yaml and stores its values in memory:
- The
yaml.ReadFilefunction takes a string and returns a*File. - The
*Filetype has methods to retrieve values of typestring,bool, andint. GetIntreturns anint64, so you need to convert it to anintbefore passing it tostrconv.Itoa.
func yamlConfig() {
cfg, err := yaml.ReadFile("conf/conf.yaml") // 1
if err != nil {
fmt.Println(err)
return
}
var username, password, port string
var intPort int64
var enableFlag bool
username, err = cfg.Get("username") // 2
if err != nil {
fmt.Println("`username` flag not set", err)
return
}
password, err = cfg.Get("password")
if err != nil {
fmt.Println("`password` flag not set", err)
return
}
intPort, err = cfg.GetInt("port") // 3
if err != nil {
fmt.Println("`port` flag not set", err)
return
}
port = strconv.Itoa(int(intPort))
enableFlag, err = cfg.GetBool("enableFlag")
if err != nil {
fmt.Println("`enableFlag` flag not set", err)
return
}
}
INI
Go doesn’t include a native INI parser. The gopkg.in/ini.v1 library is widely used:
Here is conf.ini:
; Top level comment
[user]
username = rjs
password = secret
[server]
port = 4001
[flags]
enable_flag = true
This code sample parses conf.ini and stores its values in memory:
ini.Loadaccepts one or more file paths and returns a*ini.Fileand anerror.- Chain
SectionandKeycalls to navigate the file hierarchy.Stringreturns the value as a string.Boolreturns a boolean and an error; it acceptstrue,false,on,off,1, and0.
func iniConfig() {
cfg, err := ini.Load("conf/conf.ini")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
username := cfg.Section("user").Key("username").String()
password := cfg.Section("user").Key("password").String()
port := cfg.Section("server").Key("port").String()
enableFlag, err := cfg.Section("flags").Key("enable_flag").Bool()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Environment variables
Environment variables configure applications per deployment without touching the filesystem. They’re the standard approach for containers and cloud environments, where filesystem access may be restricted or unavailable.
12-factor apps
Configuring applications with environment variables is one of the factors in a 12-factor app.
You can set environment variables in .bashrc or export them directly to a shell session. Namespace your variables to avoid conflicts:
export MYAPP_PORT="4005" # set env var in shell session
unset MYAPP_PORT # unset env var
os.Getenv
Use os.Getenv when a missing variable and an empty value are both invalid. It returns an empty string in both cases, so a single check handles either condition:
func main() {
port := os.Getenv("MYAPP_PORT")
if port == "" {
fmt.Fprintf(os.Stderr, "MYAPP_PORT is not set\n")
os.Exit(1)
}
// use port
}
os.LookupEnv
Use os.LookupEnv when you need to distinguish between a missing variable and one explicitly set to an empty string. It returns the value and a boolean that is false only when the variable is absent:
func main() {
port, ok := os.LookupEnv("MYAPP_PORT")
if !ok {
fmt.Fprintf(os.Stderr, "MYAPP_PORT is not set\n")
os.Exit(1)
}
if port == "" {
fmt.Fprintf(os.Stderr, "MYAPP_PORT is set but empty\n")
os.Exit(1)
}
// use port
}