Go by Example

Environment Variables

os.Getenv reads env vars; os.LookupEnv distinguishes "not set" from "set to empty string" - critical for required config validation at startup.

os.Getenv returns the value of an environment variable or an empty string if the variable is not set. os.LookupEnv returns a second boolean that tells you which case you have.

os.Getenv is the quick read. os.LookupEnv returns (value, ok) - use it when an empty string is a valid value and you need to distinguish it from an absent variable.

package main
 
import (
    "fmt"
    "os"
)
 
func main() {
    // Simple read - empty string if not set
    home := os.Getenv("HOME")
    fmt.Println("HOME:", home)
 
    // Distinguish "not set" from "set to empty"
    val, ok := os.LookupEnv("MY_API_KEY")
    if !ok {
        fmt.Println("MY_API_KEY is not set")
    } else {
        fmt.Printf("MY_API_KEY = %q\n", val)
    }
}

os.Environ returns all environment variables as a []string in KEY=VALUE form. os.Setenv and os.Unsetenv mutate the process environment - changes are visible to child processes spawned after the call.

package main
 
import (
    "fmt"
    "os"
    "strings"
)
 
func main() {
    os.Setenv("APP_ENV", "production")
    defer os.Unsetenv("APP_ENV")
 
    // os.Environ returns all KEY=VALUE pairs
    for _, entry := range os.Environ() {
        if strings.HasPrefix(entry, "APP_") {
            fmt.Println(entry)
        }
    }
 
    // os.Expand substitutes ${VAR} references
    template := "running in ${APP_ENV} mode"
    fmt.Println(os.ExpandEnv(template))
}

In production

Use os.LookupEnv instead of os.Getenv when you need to distinguish "not set" from "set to empty string" - a blank DATABASE_URL and a missing DATABASE_URL are different failure modes. Parse and validate all required environment variables at startup and fail fast with a descriptive error: discovering a missing variable on the first request is worse than crashing on boot. A common pattern is a config.Load() function called in main that reads every required var with LookupEnv, collects all missing names into a slice, and returns a single error listing them all. Libraries like kelseyhightower/envconfig or caarlos0/env automate this: they populate a struct from env vars and validate required fields in one call. For secrets (database passwords, API keys), prefer a secrets manager (AWS Secrets Manager, HashiCorp Vault) that injects values as env vars at container start rather than baking them into images or config files.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free