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:
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):
export PORT=3000
export GIN_MODE=release
go run main.go
Temporarily in Windows Command Prompt:
set PORT=3000
set GIN_MODE=release
go run main.go
Temporarily in Windows 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
, ortest
)PORT
: Commonly used (but not built into Gin) for setting the server port
Example of setting Gin's mode:
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:
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:
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:
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:
go run main.go
Production mode:
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
-
Never hardcode sensitive information - Always use environment variables for:
- API keys and secrets
- Database credentials
- Authentication tokens
-
Provide sensible defaults - Make your application work without requiring all environment variables to be set.
-
Document required environment variables - Create a template
.env.example
file showing which variables are needed. -
Validate critical environment variables - Check that essential variables are present and valid at startup.
-
Use different variable sets for different environments - Development, testing, staging, and production should each have their own variable sets.
-
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:
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:
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
- Go os package documentation
- Godotenv library
- Viper configuration library
- Twelve-Factor App methodology (See factor III: Config)
Exercises
-
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. -
Extend your application to use different database connections based on the
ENVIRONMENT
variable (development/production). -
Implement a configuration system using Viper that reads from both a config file and environment variables, with environment variables taking precedence.
-
Create a middleware that requires an API key (from an environment variable) for certain routes.
-
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! :)