Day 15 - Env variables in go
Firstly, what are environment variables and why do we use them?
Environment variables are similar to the variables we use in our code — they store data that we can access later — but the main difference is they live outside your code. They are part of your system or environment, not your Go program itself.
We generally store sensitive or configurable information like passwords, database credentials, API keys, or even runtime configurations inside these environment variables.
Why do we use environment variables?
Let’s take an example:
You’ve created a Go project that integrates with the Gemini API to process some AI queries. To connect with the Gemini service, you need to provide an API key.
Now, if you directly write the key inside your Go file like this:
apiKey := "GEMINI-ABC123XYZ"
and push it to GitHub, there’s a chance someone might misuse your Gemini API key.
To avoid this situation, we use environment variables.
We store all these secret keys in a .env file (usually in the root directory of your project) and load them securely inside our program.
How to create a .env file
The .env file is simply a text file containing key-value pairs, one per line.
Example .env file:
GEMINI_API_KEY=abcd1234xyz
DB_USER=admin
DB_PASSWORD=secretpassword
PORT=8080
👉 Points to remember:
- No spaces around
=. - Keys are case-sensitive.
- The file should not be committed to version control. Add it to your
.gitignorefile.
Example .gitignore:
.env
How to use environment variables in Go
Go has a standard library called os that can read environment variables directly.
But to load .env files, we usually use an external package called godotenv.
You can install it like this:
go get github.com/joho/godotenv
Now, let’s use it in a Go program.
Example 1: Loading and accessing environment variables
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
// Load the .env file
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
// Fetch environment variables
apiKey := os.Getenv("GEMINI_API_KEY")
dbUser := os.Getenv("DB_USER")
port := os.Getenv("PORT")
// Use them in your program
fmt.Println("API Key:", apiKey)
fmt.Println("Database User:", dbUser)
fmt.Println("Server running on port:", port)
}
Output
API Key: abcd1234xyz
Database User: admin
Server running on port: 8080
⚠️ Important Note
If a variable is not found in the environment or .env file,
os.Getenv("KEY") will return an empty string — it doesn’t throw an error.
If you want to handle missing values safely, you can do something like this:
if apiKey == "" {
log.Fatal("GEMINI_API_KEY is missing!")
}
Example 2: Setting environment variables manually (for testing)
You can also set environment variables temporarily from your terminal:
On Linux / macOS:
export GEMINI_API_KEY=abcd1234xyz
go run main.go
On Windows (PowerShell):
$env:GEMINI_API_KEY="abcd1234xyz"
go run main.go
This way, you don’t even need a .env file if you only want to test quickly.
Best Practices for Managing Environment Variables in Go
While using environment variables is secure and convenient, managing them properly becomes important when your application grows or runs across multiple environments (like development, staging, and production).
Here are some best practices you should always follow 👇
1. Use separate .env files for each environment
When working on different environments, it’s a good idea to keep separate files for each one. For example:
.env.development
.env.staging
.env.production
Each file contains the variables specific to that environment.
Example: .env.development
PORT=8080
DEBUG=true
DB_NAME=dev_db
.env.production
PORT=80
DEBUG=false
DB_NAME=prod_db
You can load the correct file dynamically:
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
godotenv.Load(".env." + env)
Run your program with:
APP_ENV=production go run main.go
2. Add .env.example to your repository
Never push your real .env file (since it contains secrets),
but it’s a good idea to push a .env.example file that lists all required keys with sample values.
Example .env.example:
GEMINI_API_KEY=your_api_key_here
DB_USER=your_db_user
DB_PASSWORD=your_db_password
PORT=8080
This helps other developers quickly understand which environment variables are needed.
Don't add this in .gitignore file
3. Validate your environment variables
Before running your main application logic, make sure all critical environment variables exist. You can create a small helper function:
func mustGetEnv() bool {
requiredKeys := []string{"KEY1", "KEY2"}
for _, key := range requiredKeys {
val := os.Getenv(key)
if val == "" {
log.Fatalf("Environment variable %s is missing!", key)
return false
}
fmt.Printf(" %s = %s\n", key, val)
}
return true
}
func main() {
// Load environment variables from .env file
err := godotenv.Load()
if err != nil {
log.Println("No .env file found, using system environment variables.")
}
// Validate required keys
if mustGetEnv() {
fmt.Println("All required environment variables are set.")
}
}
4. Use a .gitignore file to protect secrets
Always include your .env file in .gitignore:
.env
.env.*
!.env.example
This prevents accidentally committing sensitive data to GitHub.
5. Use external secret managers (for production)
For production systems, .env files are not the safest option.
You can use cloud secret managers to store and manage sensitive data securely.
Some popular options:
- AWS Secrets Manager
- Google Cloud Secret Manager
- HashiCorp Vault
- Docker Secrets (for containerized apps)
Example: Handling multiple .env files and fallback
Here’s a more complete example that shows how to:
- Load environment files based on environment
- Fallback to
.envif nothing is found - Validate required variables
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
)
func mustGetEnv(key string) string {
val := os.Getenv(key)
if val == "" {
log.Fatalf("Missing required environment variable: %s", key)
}
return val
}
func main() {
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
err := godotenv.Load(".env." + env)
if err != nil {
godotenv.Load() // fallback to default .env
}
fmt.Println("Loaded environment:", env)
apiKey := mustGetEnv("GEMINI_API_KEY")
dbUser := mustGetEnv("DB_USER")
fmt.Println("Gemini API Key:", apiKey)
fmt.Println("Database User:", dbUser)
}
#go #goenv #developments #softwareDevelopment #microservices