Go cookbook · Beginner

Load and validate configuration from environment variables

Configuration is read ad hoc with os.Getenv all over the code, and typos are discovered in production.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Load configuration once at startup into a struct, parse types explicitly, collect all problems and fail with a clear message, then pass the struct to the components that need it.

Solution

package config

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"time"
)

type Config struct {
	Addr            string
	DatabaseURL     string
	ShutdownTimeout time.Duration
	MaxUploadMB     int
	Debug           bool
}

func Load() (Config, error) {
	var errs []error
	get := func(key, def string) string {
		if v, ok := os.LookupEnv(key); ok {
			return v
		}
		return def
	}
	required := func(key string) string {
		v, ok := os.LookupEnv(key)
		if !ok || v == "" {
			errs = append(errs, fmt.Errorf("%s is required", key))
		}
		return v
	}

	cfg := Config{Addr: get("ADDR", ":8080"), DatabaseURL: required("DATABASE_URL")}
	var err error
	if cfg.ShutdownTimeout, err = time.ParseDuration(get("SHUTDOWN_TIMEOUT", "25s")); err != nil {
		errs = append(errs, fmt.Errorf("SHUTDOWN_TIMEOUT: %w", err))
	}
	if cfg.MaxUploadMB, err = strconv.Atoi(get("MAX_UPLOAD_MB", "10")); err != nil || cfg.MaxUploadMB < 1 {
		errs = append(errs, errors.New("MAX_UPLOAD_MB must be a positive integer"))
	}
	if cfg.Debug, err = strconv.ParseBool(get("DEBUG", "false")); err != nil {
		errs = append(errs, fmt.Errorf("DEBUG: %w", err))
	}
	return cfg, errors.Join(errs...)
}

Pitfalls

  • Reading environment variables deep inside packages, making them hard to test.
  • Silent defaults for required values like database URLs.
  • Printing the config at startup including secrets.

Variations

  • github.com/caarlos0/env or kelseyhightower/envconfig map variables to struct tags.
  • Combine with flag for command-line overrides in CLIs.

Further reading: os.LookupEnv

Reference: https://pkg.go.dev/os#LookupEnv

More Go recipes

Was this page helpful?