Skip to main content

Gin Environment Variables

Environment variables are a crucial aspect of deploying web applications securely and flexibly. In this guide, we'll explore how to effectively use environment variables in Gin applications to manage configuration across different deployment environments.

Introduction to Environment Variables in Gin

Environment variables are dynamic values that can affect the way running processes behave on a computer. In web development, they're commonly used to:

  • Store sensitive information (database credentials, API keys)
  • Configure application behavior based on environment (development, testing, production)
  • Adjust settings without changing code

Gin, being a Go web framework, doesn't have built-in environment variable management, but Go's standard library and third-party packages make it easy to implement.

Basic Environment Variable Usage in Gin

Reading Environment Variables

Go provides the os package to interact with environment variables:

go
package main

import (
"fmt"
"os"
"github.com/gin-gonic/gin"
)

func main() {
// Read an environment variable
port := os.Getenv("PORT")
if port == "" {
port = "8080" // Default port if not specified
}

r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Server is running",
})
})

fmt.Printf("Server starting on port %s\n", port)
r.Run(":" + port) // Listen and serve
}

In this example, the server will use the PORT environment variable if it's set, otherwise, it will default to 8080.

Setting Environment Variables

You can set environment variables in different ways depending on your operating system and deployment method.

Temporarily in terminal (Linux/macOS):

bash
export PORT=3000
export GIN_MODE=release
go run main.go

Temporarily in Windows Command Prompt:

cmd
set PORT=3000
set GIN_MODE=release
go run main.go

Temporarily in Windows PowerShell:

powershell
$env:PORT = "3000"
$env:GIN_MODE = "release"
go run main.go

Common Gin-Specific Environment Variables

Gin recognizes certain environment variables by default:

  • GIN_MODE: Sets the mode Gin runs in (debug, release, or test)
  • PORT: Commonly used (but not built into Gin) for setting the server port

Example of setting Gin's mode:

go
package main

import (
"os"
"github.com/gin-gonic/gin"
)

func main() {
// Check the GIN_MODE environment variable
ginMode := os.Getenv("GIN_MODE")
if ginMode == "release" {
gin.SetMode(gin.ReleaseMode)
}

r := gin.Default()
// ... route definitions
r.Run()
}

Using Environment Variable Libraries

For more advanced environment variable management, consider using third-party libraries like godotenv or viper.

Using godotenv

The godotenv package loads environment variables from a .env file:

go
package main

import (
"log"
"os"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)

func main() {
// Load environment variables from .env file
err := godotenv.Load()
if err != nil {
log.Println("Warning: .env file not found")
}

// Now you can access the environment variables
dbURL := os.Getenv("DATABASE_URL")
port := os.Getenv("PORT")
if port == "" {
port = "8080" // Default port
}

r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Connected to database: " + dbURL,
})
})

r.Run(":" + port)
}

Create a .env file in your project root:

PORT=3000
DATABASE_URL=postgres://user:password@localhost:5432/mydatabase
API_KEY=your_secret_key_here
GIN_MODE=release

Important: Always add .env to your .gitignore file to avoid committing sensitive information to version control.

Using Viper for Configuration

Viper is a more comprehensive configuration solution that can handle environment variables, configuration files, and more:

go
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

func main() {
// Set up Viper
viper.SetConfigName("config") // Name of config file without extension
viper.SetConfigType("yaml") // File type
viper.AddConfigPath(".") // Look for config in current directory

// Read environment variables
viper.AutomaticEnv()

// Read config file
err := viper.ReadInConfig()
if err != nil {
fmt.Printf("Config file not found: %v\n", err)
}

// Set defaults
viper.SetDefault("PORT", "8080")
viper.SetDefault("GIN_MODE", "debug")

// Use the configuration
port := viper.GetString("PORT")
ginMode := viper.GetString("GIN_MODE")

if ginMode == "release" {
gin.SetMode(gin.ReleaseMode)
}

r := gin.Default()
r.GET("/config", func(c *gin.Context) {
c.JSON(200, gin.H{
"port": port,
"gin_mode": ginMode,
"db_url": viper.GetString("DATABASE_URL"),
})
})

r.Run(":" + port)
}

Real-World Application: Environment-Based Configuration

Let's build a more comprehensive example that adjusts the application behavior based on the environment:

go
package main

import (
"fmt"
"log"
"os"

"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)

// Config holds all configuration for our application
type Config struct {
Environment string
Port string
DatabaseURL string
LogLevel string
APIKeys map[string]bool
}

// LoadConfig loads the configuration from environment variables
func LoadConfig() Config {
// Try to load .env file, but don't fail if it doesn't exist
godotenv.Load()

environment := os.Getenv("ENVIRONMENT")
if environment == "" {
environment = "development"
}

port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" && environment == "development" {
dbURL = "postgres://user:pass@localhost:5432/devdb"
}

logLevel := os.Getenv("LOG_LEVEL")
if logLevel == "" {
// Set default log level based on environment
if environment == "production" {
logLevel = "warn"
} else {
logLevel = "debug"
}
}

// Parse API keys from comma-separated list
apiKeysStr := os.Getenv("API_KEYS")
apiKeys := make(map[string]bool)
if apiKeysStr != "" {
// In real app, you'd split by comma and process each key
// This is a simplified example
apiKeys[apiKeysStr] = true
}

return Config{
Environment: environment,
Port: port,
DatabaseURL: dbURL,
LogLevel: logLevel,
APIKeys: apiKeys,
}
}

func main() {
config := LoadConfig()

// Set Gin mode based on environment
if config.Environment == "production" {
gin.SetMode(gin.ReleaseMode)
}

r := gin.Default()

// API key middleware for protected routes
authMiddleware := func(c *gin.Context) {
key := c.GetHeader("X-API-Key")
if _, valid := config.APIKeys[key]; !valid {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return
}
c.Next()
}

r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "running",
"environment": config.Environment,
})
})

// Protected route
protected := r.Group("/api")
protected.Use(authMiddleware)
{
protected.GET("/data", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "This is protected data",
"db_url": config.DatabaseURL,
})
})
}

// Debug info - only available in development
if config.Environment != "production" {
r.GET("/debug/config", func(c *gin.Context) {
// Mask sensitive values for display
displayConfig := config
if displayConfig.DatabaseURL != "" {
displayConfig.DatabaseURL = "[MASKED]"
}

c.JSON(200, displayConfig)
})
}

fmt.Printf("Starting server in %s mode on port %s\n", config.Environment, config.Port)
r.Run(":" + config.Port)
}

To run this application with different configurations:

Development mode:

bash
go run main.go

Production mode:

bash
ENVIRONMENT=production PORT=3000 DATABASE_URL="postgres://user:pass@prod-db:5432/proddb" API_KEYS="key1,key2,key3" go run main.go

Best Practices for Environment Variables in Gin

  1. Never hardcode sensitive information - Always use environment variables for:

    • API keys and secrets
    • Database credentials
    • Authentication tokens
  2. Provide sensible defaults - Make your application work without requiring all environment variables to be set.

  3. Document required environment variables - Create a template .env.example file showing which variables are needed.

  4. Validate critical environment variables - Check that essential variables are present and valid at startup.

  5. Use different variable sets for different environments - Development, testing, staging, and production should each have their own variable sets.

  6. Consider using a configuration package - As your app grows, consider structured configuration management with libraries like Viper.

Environment Variables in Containerized Gin Applications

When deploying Gin in Docker containers, you'll typically set environment variables in your Dockerfile or docker-compose.yml:

Dockerfile example:

dockerfile
FROM golang:1.18-alpine as builder
WORKDIR /app
COPY . .
RUN go build -o main .

FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
ENV GIN_MODE=release
ENV PORT=8080
EXPOSE 8080
CMD ["./main"]

Docker Compose example:

yaml
version: '3'
services:
web:
build: .
ports:
- "8080:8080"
environment:
- GIN_MODE=release
- PORT=8080
- DATABASE_URL=postgres://user:pass@db:5432/myapp
- API_KEY=${API_KEY}
depends_on:
- db
db:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp

Summary

Environment variables provide a flexible way to configure your Gin applications across different environments without changing code. They're essential for:

  • Keeping sensitive information out of your codebase
  • Adapting application behavior based on the deployment environment
  • Making your application more portable and easier to deploy

By following the patterns shown in this guide, you can create Gin applications that are secure, configurable, and easy to deploy in various environments.

Additional Resources

Exercises

  1. Create a simple Gin application that reads a GREETING environment variable and displays it on the homepage. Set a default greeting if the variable isn't defined.

  2. Extend your application to use different database connections based on the ENVIRONMENT variable (development/production).

  3. Implement a configuration system using Viper that reads from both a config file and environment variables, with environment variables taking precedence.

  4. Create a middleware that requires an API key (from an environment variable) for certain routes.

  5. Build a Docker container for your Gin application that uses environment variables for configuration.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)