| Feature | Benefit |
|--------|---------|
| No accidental commits | Git ignores .env.go.local |
| Team defaults | .env provides sane fallbacks |
| Local freedom | Override any variable without touching shared files |
| Simple debugging | Set LOG_LEVEL=debug only on your machine |
| Easy CI/CD | CI can rely on .env or system environment variables |
Go developers value clarity and minimal magic. The .env.go.local pattern is:
To run your application with .env.go.local active:
go run -tags local main.go
To run without it (simulating production): .env.go.local
go run main.go
Add this to your Makefile for convenience:
.PHONY: dev dev: go run -tags local ./cmd/server
.PHONY: build-prod build-prod: go build -o bin/server ./cmd/server
While .env.go.local is a pattern, not a library, you see echoes of it in major projects:
Every seasoned Go developer knows the pain. You have your config.go file, your os.Getenv calls scattered everywhere, and a bulky .env file that works perfectly on your laptop but breaks catastrophically on your colleague’s machine because their API key has different permissions.
Enter the unsung hero of localized Go configuration: .env.go.local . | Feature | Benefit | |--------|---------| | No
While not an official Go standard library feature, the pattern of using a .env.go.local file has emerged as a best practice among elite Go teams. It bridges the gap between the inflexibility of hardcoded constants and the chaos of global environment variables.
In this article, we will explore why .env.go.local is the most elegant solution for local development, how to implement it, and why it outshines traditional .env files in compiled Go applications.
| Feature | .env file + godotenv | OS env vars | .env.go.local |
| :--- | :--- | :--- | :--- |
| Type safety | ❌ 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) | To run without it (simulating production):
go run main
Organize your project to separate shared configuration from local overrides:
/myapp
├── go.mod
├── main.go
├── config/
│ ├── config.go // Shared logic and defaults
│ └── env.go.local // Local overrides (ignored by git)
└── .gitignore