.env.go.local
In professional Go development, you often want a hierarchy: System Env Vars > Local File > Default Values.
Here is a production-ready setup using the godotenv library with overrides:
package mainimport ( "log" "os" "path/filepath"
"github.com/joho/godotenv")
func LoadEnv() // Get the current working directory execPath, err := os.Getwd() if err != nil log.Fatal(err)
// Define the path to your local file envPath := filepath.Join(execPath, ".env.go.local") // Load the file. // Note: If the file doesn't exist, godotenv.Load returns an error. // We usually want to ignore this error in Production environments. if _, err := os.Stat(envPath); err == nil if err := godotenv.Load(envPath); err != nil log.Printf("Error loading .env.go.local file: %v", err) else log.Println("Loaded environment from .env.go.local")func main() LoadEnv()
// Use the variable apiKey := os.Getenv("STRIPE_API_KEY") // ...
You run go run main.go and wonder why your local overrides aren’t working. The solution: alias go run -tags local in your shell.
While .env.go.local is a pattern, not a library, you see echoes of it in major projects:
package configimport ( "log" "os" "github.com/joho/godotenv" )
func Load() // Load default .env first (if it exists) if err := godotenv.Load(".env"); err != nil log.Println("No .env file found, using system envs")
// Override with .env.go.local – fails silently if not present _ = godotenv.Overload(".env.go.local")
func Get(key, defaultValue string) string if val := os.Getenv(key); val != "" return val return defaultValue
At its core, .env.go.local is a naming convention—a hybrid between a configuration file and a Go source file. Unlike standard .env files which are parsed at runtime, .env.go.local is typically a Go package that exports variables, or a structured file that is read into your main.go exclusively during development. .env.go.local
A typical .env.go.local might look like this:
// env/env.local.go //go:build local // +build localpackage env
func init() os.Setenv("DB_HOST", "localhost:5432") os.Setenv("API_KEY", "dev-12345") os.Setenv("LOG_LEVEL", "debug")
var Config = struct Port string Port: "8080",
By using build tags (//go:build local), this file is only compiled when you explicitly tell Go to use the local tag. In production, it is completely ignored.
Instead of init(), use a local config loader:
// config/env.go.local package config
func LocalOverrides() os.Setenv("PORT", "3000")In professional Go development, you often want a
Then in your main.go:
func main()
if os.Getenv("GO_ENV") == "local"
config.LocalOverrides()
// ... rest of app
This is safer because you control when overrides apply.
One of the hidden benefits is test isolation. Create config/env_test.go.local:
//go:build local_testpackage config
func init() os.Setenv("TEST_MODE", "true") os.Setenv("DB_HOST", "localhost:5432")
Run tests with:
go test -tags local_test ./...