Skip to main content

Command Palette

Search for a command to run...

Day 15 - Env variables in go

Published
5 min read

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 .gitignore file.

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 .env if 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