Skip to main content

Echo Environment Variables

Introduction

Environment variables are a crucial aspect of modern application deployment. They allow you to configure your Echo applications without changing code, making your applications more portable, secure, and easier to manage across different environments.

In this guide, we'll explore how to effectively use environment variables in your Echo applications, why they matter for deployment, and best practices for managing configuration.

Why Use Environment Variables?

Before diving into implementation, let's understand why environment variables are important:

  1. Separation of configuration from code: Following the twelve-factor app methodology
  2. Security: Keeping sensitive information out of your codebase
  3. Environment-specific configuration: Easily switching between development, testing, and production settings
  4. Containerization support: Essential for Docker and Kubernetes deployments

Accessing Environment Variables in Echo

Echo applications can access environment variables through Go's standard library. Here's how to do it:

go
package main

import (
"fmt"
"os"

"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()

// Get an environment variable with a default fallback
port := os.Getenv("PORT")
if port == "" {
port = "8080" // Default port if not specified
}

e.GET("/", func(c echo.Context) error {
return c.String(200, "Server running with configuration from environment variables")
})

e.Logger.Fatal(e.Start(":" + port))
}

In this example, the application reads the PORT environment variable to determine which port to use. If the variable isn't set, it defaults to 8080.

Setting Environment Variables

There are multiple ways to set environment variables depending on your development and deployment environment.

Local Development

For local development, you can set variables in your terminal session:

Linux/macOS:

bash
export PORT=3000
export DB_CONNECTION_STRING="postgresql://user:pass@localhost:5432/mydb"

Windows (Command Prompt):

cmd
set PORT=3000
set DB_CONNECTION_STRING=postgresql://user:pass@localhost:5432/mydb

Windows (PowerShell):

powershell
$env:PORT = "3000"
$env:DB_CONNECTION_STRING = "postgresql://user:pass@localhost:5432/mydb"

Using .env Files

A common practice is to use .env files for local development. You'll need the godotenv package:

go get github.com/joho/godotenv

Now you can create a .env file in your project root:

PORT=3000
DB_CONNECTION_STRING=postgresql://user:pass@localhost:5432/mydb
DEBUG=true

And load it in your application:

go
package main

import (
"fmt"
"os"

"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
)

func main() {
// Load .env file if it exists
godotenv.Load()

e := echo.New()

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

debug := os.Getenv("DEBUG") == "true"
if debug {
e.Debug = true
}

e.GET("/", func(c echo.Context) error {
return c.String(200, "Server running with configuration from .env")
})

e.Logger.Fatal(e.Start(":" + port))
}

Important: Never commit .env files to version control if they contain sensitive information. Add .env to your .gitignore file.

Configuration Best Practices

Creating a Configuration Package

For larger applications, it's a good practice to centralize environment variable handling in a dedicated package:

go
// config/config.go
package config

import (
"os"
"strconv"
)

type Config struct {
Port string
DatabaseURL string
LogLevel string
Debug bool
JWTSecret string
}

func Load() *Config {
config := &Config{
Port: getEnv("PORT", "8080"),
DatabaseURL: getEnv("DATABASE_URL", ""),
LogLevel: getEnv("LOG_LEVEL", "info"),
Debug: getEnvAsBool("DEBUG", false),
JWTSecret: getEnv("JWT_SECRET", "default-secret"),
}

return config
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}

func getEnvAsBool(key string, defaultValue bool) bool {
valueStr := getEnv(key, "")
if valueStr == "" {
return defaultValue
}
value, err := strconv.ParseBool(valueStr)
if err != nil {
return defaultValue
}
return value
}

Now you can use this config package in your main application:

go
// main.go
package main

import (
"your-app/config"

"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
)

func main() {
// Load .env file for local development
godotenv.Load()

// Load configuration from environment
cfg := config.Load()

e := echo.New()
e.Debug = cfg.Debug

// Use configuration values throughout your app
e.GET("/", func(c echo.Context) error {
return c.String(200, "Server configured via environment")
})

e.Logger.Fatal(e.Start(":" + cfg.Port))
}

Environment Variables in Production Deployment

When deploying Echo applications to production, you'll set environment variables differently based on your deployment platform.

Docker Deployment

For Docker, you can set environment variables in your Dockerfile or when running containers:

dockerfile
# Dockerfile
FROM golang:1.19-alpine AS build
# ... build steps ...

FROM alpine:latest
# ... copy binary from build stage ...
ENV PORT=8080
ENV LOG_LEVEL=info
# For sensitive values, don't hardcode them in the Dockerfile
CMD ["./myapp"]

When running the container:

bash
docker run -e PORT=8080 -e DATABASE_URL="postgresql://user:pass@db:5432/mydb" -e JWT_SECRET="my-secret" myapp

Kubernetes Deployment

In Kubernetes, you can set environment variables in your deployment YAML:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-app
spec:
# ...
template:
spec:
containers:
- name: echo-app
image: myapp:latest
env:
- name: PORT
value: "8080"
- name: LOG_LEVEL
value: "info"
# Reference secrets for sensitive values
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database_url

Cloud Providers

Most cloud platforms provide ways to configure environment variables:

  • Heroku: Through the dashboard or CLI (heroku config:set VAR=value)
  • AWS Elastic Beanstalk: In the configuration settings
  • Google Cloud Run: In the deployment configuration
  • Azure App Service: In the Application Settings section

Environment Variables and Secrets

For sensitive information like API keys, database passwords, and JWT secrets, you should use a secure method to manage these values:

  1. Never commit secrets to version control
  2. Use your platform's secrets management:
    • Kubernetes Secrets
    • AWS Secrets Manager
    • Google Secret Manager
    • Azure Key Vault

Real-World Example: Configurable Echo API Server

Here's a comprehensive example of an Echo API server that uses environment variables for configuration:

go
package main

import (
"context"
"os"
"os/signal"
"time"

"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

type Config struct {
Port string
AllowedOrigins []string
RateLimit int
Timeout time.Duration
LogRequests bool
}

func loadConfig() Config {
// Load .env file if present (for local development)
godotenv.Load()

rateLimit, _ := strconv.Atoi(getEnv("RATE_LIMIT", "100"))
timeout, _ := strconv.Atoi(getEnv("TIMEOUT_SECONDS", "30"))

return Config{
Port: getEnv("PORT", "8080"),
AllowedOrigins: strings.Split(getEnv("ALLOWED_ORIGINS", "*"), ","),
RateLimit: rateLimit,
Timeout: time.Duration(timeout) * time.Second,
LogRequests: getEnv("LOG_REQUESTS", "true") == "true",
}
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}

func main() {
// Load configuration from environment variables
config := loadConfig()

// Create Echo instance
e := echo.New()

// Configure middleware based on environment variables
if config.LogRequests {
e.Use(middleware.Logger())
}

e.Use(middleware.Recover())
e.Use(middleware.CORS(middleware.CORSConfig{
AllowOrigins: config.AllowedOrigins,
}))
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(config.RateLimit)))
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
Timeout: config.Timeout,
}))

// Routes
e.GET("/", func(c echo.Context) error {
return c.JSON(200, map[string]string{
"message": "Echo API configured via environment variables",
})
})

// Start server with graceful shutdown
go func() {
if err := e.Start(":" + config.Port); err != nil {
e.Logger.Info("shutting down the server")
}
}()

// Wait for interrupt signal to gracefully shut down the server
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
}

Summary

Environment variables are an essential tool for configuring Echo applications across different environments. They help maintain the separation between code and configuration, improve security, and make your applications more flexible and portable.

Key takeaways:

  1. Use environment variables to store configuration that varies between environments
  2. Keep sensitive information like API keys and passwords in environment variables, not in code
  3. Provide sensible defaults for non-critical configuration
  4. For local development, use .env files but don't commit them to version control
  5. Centralize environment variable handling in a dedicated configuration package
  6. For production, use the appropriate method to set environment variables based on your deployment platform

Additional Resources

Exercises

  1. Create a simple Echo application that reads different database URLs based on an ENVIRONMENT variable that can be development, testing, or production.

  2. Extend the configuration package to include validation for required environment variables, returning errors when critical variables are missing.

  3. Create a middleware that adds environment-specific headers to responses based on configuration from environment variables.

  4. Build an Echo application that supports different logging levels (debug, info, warning, error) configurable through an environment variable.



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