For more robust applications, instead of reading os.Getenv everywhere, many Go developers use library github.com/kelseyhightower/envconfig to map environment variables directly to a struct.
// Step 4: Start the application server.Run()
What (e.g., Gin, Fiber, Chi, standard library) your Go app uses? How you currently manage production secrets ? Whether you are using a monorepo or polyrepo structure?
import _ "embed"
"github.com/joho/godotenv"
| 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) |
: It is helpful to commit a file named .env.go.local.sample (containing empty or dummy values) so other developers know which variables they need to define. .env.go.local
package main import ( "fmt" "log" "os" "://github.com" ) func init() // Load files from highest priority to lowest priority. // Overload ensures that variables in .env.go.local overwrite those in .env err := godotenv.Overload(".env.go.local", ".env.local", ".env") if err != nil // We do not panic here because production environments // rely on actual system environment variables instead of .env files. log.Println("Note: Some configuration files were not found, relying on system environment.") func main() // Retrieve a variable dbUser := os.Getenv("DB_USER") port := os.Getenv("APP_PORT") if port == "" port = "8080" // Default fallback fmt.Printf("Server starting on port %s as user %s\n", port, dbUser) Use code with caution. Use Cases for .env.go.local 1. Local Database and Credential Overrides
init-dev: cp .env .env.go.local.example @echo "Created .env.go.local.example – copy to .env.go.local and edit"
Using a .env.go.local file is a powerful, simple, and secure way to manage your Go application's configuration. It turns a potential source of deployment chaos into a clean, predictable, and developer-friendly system. By adopting this convention and the libraries that support it, you're writing better, safer, and more collaborative Go code. The tools are easy to learn, the security benefits are immense, and your future self (and your team) will thank you. For more robust applications, instead of reading os
While Go's standard library provides os.Getenv and os.LookupEnv to retrieve environment variables, it does not natively parse .env files out of the box. To use files like .env.go.local , you will need a parser. Learn how to use .env files in Go projects
Continuous Integration (CI) servers like GitHub Actions, GitLab CI, or Jenkins will not have access to a .env.go.local file. Ensure your Go code doesn't throw a fatal error if the file is missing; instead, design it to smoothly log a warning and fallback onto native system environment flags injected by your runner pipeline.
package main import ( "fmt" "log" "://github.com" ) func main() // Set the explicit file name and path viper.SetConfigFile(".env.go.local") viper.SetConfigType("env") // Read the configuration file if err := viper.ReadInConfig(); err != nil log.Println("Could not read .env.go.local, checking system environment variables:", err) // Automatically read system environment variables if missing from the file viper.AutomaticEnv() dbPort := viper.GetString("DB_PORT") fmt.Printf("Database Port: %s\n", dbPort) Use code with caution. Structuring Your Environment File Whether you are using a monorepo or polyrepo structure
The godotenv package is a Go port of the Ruby dotenv project. It is perfect for straightforward projects.