cfg := &Config Port: os.Getenv("APP_PORT"), DBURL: os.Getenv("DATABASE_URL"), APIKey: os.Getenv("API_KEY"),
package main
| Feature | .env file + godotenv | OS env vars | .env.go.local | | :--- | :--- | :--- | :--- | | | ❌ String only | ❌ String only | ✅ Full Go types | | Compile-time validation | ❌ Runtime error | ❌ Runtime error | ✅ Compiler catches errors | | Production isolation | ⚠️ Must not commit file | ✅ Secure | ✅ Build tags prevent leaks | | Developer experience | Okay | Tedious | Excellent (IDE autocomplete) | | Overhead | Runtime parsing | Zero | Zero (compile-time) | .env.go.local
// Step 2: Initialize configuration cfg := mustLoadConfig()
// config/env.go.local package config
.env files must be UTF-8 encoded without a BOM. Files saved with Windows CRLF line endings can cause issues where \r becomes part of your variable values.
The GOE framework provides a particularly clear implementation of this hierarchy. It loads four layers in order: the .env file, then a .local.env file, then an environment‑specific file like .production.env , and finally the system environment variables. The priority resolution ensures that the correct values are applied without any developer having to manually merge configuration. cfg := &Config Port: os
While these approaches work, they have their drawbacks. Manually setting environment variables can be tedious and prone to errors, while hardcoding values can lead to security risks and make it harder to manage different environments.
type Config struct Port int `env:"PORT" envDefault:"8080"` DBTimeout time.Duration `env:"DB_TIMEOUT" envDefault:"30s"` IsDebugMode bool `env:"DEBUG_MODE" envDefault:"false"` It loads four layers in order: the