Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 39

    Configuration Management

    Production Go services load configuration from environment variables, .env files (development only), YAML/JSON files, and secret managers (AWS Secrets Manager, Vault).

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Production Go services load configuration from environment variables, .env files (development only), YAML/JSON files, and secret managers (AWS Secrets Manager, Vault). The os.Getenv function is the foundation; libraries like viper and envconfig add structure.

    12-factor app principles: config in environment, not code. Validate config at startup — fail fast if required values missing. Never commit secrets; use different configs per environment (dev/staging/prod).

    Interviewers ask how you manage secrets, validate config, and hot-reload settings — this lesson implements typed config loaded from env with startup validation.

    The story

    A Docker-deployed API reads configuration from environment variables (DATABASE_URL, LOG_LEVEL) with defaults for local development and secrets injected by Kubernetes External Secrets Operator in production. Twelve-factor app principles map directly to Go: parse config at startup, fail fast on missing required values, never reload secrets from disk in hot paths.

    Tools like viper or envconfig bind env vars to structs — but even a simple os.Getenv with validation covers most microservices.

    Understanding the topic

    Key concepts

    • os.Getenv("KEY") with default fallback pattern.
    • Struct tags: envconfig, mapstructure for auto-binding.
    • Load .env in dev only; production uses K8s ConfigMap/Secret.
    • Validate required fields at startup before ListenAndServe.
    • time.ParseDuration for config strings like "30s".
    • Separate Config struct from global vars.

    Step-by-step explanation

    1. Define Config struct with all settings.
    2. LoadFromEnv populates struct from environment.
    3. Validate checks required fields non-empty, ports in range.
    4. main calls LoadConfig — panic or log.Fatal on invalid.
    5. Pass *Config to constructors — don't reread env everywhere.
    6. K8s mounts secrets as env vars or files at /etc/secrets/.

    Practical code example

    Typed config from environment with validation at startup:

    go
    package main
    import (
    "fmt"
    "os"
    "strconv"
    "time"
    )
    type Config struct {
    Port int
    DatabaseURL string
    ShutdownTimeout time.Duration
    LogLevel string
    }
    func LoadConfig() (Config, error) {
    var cfg Config
    portStr := os.Getenv("PORT")
    if portStr == "" {
    portStr = "8080"
    }
    port, err := strconv.Atoi(portStr)
    if err != nil {
    return cfg, fmt.Errorf("invalid PORT: %w", err)
    }
    cfg.Port = port
    cfg.DatabaseURL = os.Getenv("DATABASE_URL")
    if cfg.DatabaseURL == "" {
    return cfg, fmt.Errorf("DATABASE_URL required")
    }
    timeoutStr := os.Getenv("SHUTDOWN_TIMEOUT")
    if timeoutStr == "" {
    timeoutStr = "15s"
    }
    cfg.ShutdownTimeout, err = time.ParseDuration(timeoutStr)
    if err != nil {
    return cfg, fmt.Errorf("invalid SHUTDOWN_TIMEOUT: %w", err)
    }
    cfg.LogLevel = os.Getenv("LOG_LEVEL")
    if cfg.LogLevel == "" {
    cfg.LogLevel = "info"
    }
    return cfg, nil
    }
    func main() {
    cfg, err := LoadConfig()
    if err != nil {
    fmt.Fprintln(os.Stderr, "config error:", err)
    os.Exit(1)
    }
    fmt.Printf("starting on :%d timeout=%v level=%s\n", cfg.Port, cfg.ShutdownTimeout, cfg.LogLevel)
    }

    Line-by-line code explanation

    • type Config struct { Port string `env:"PORT" default:"8080"` } maps env vars to fields.
    • port := os.Getenv("PORT") reads an environment variable — empty string if unset.
    • if port == "" { port = "8080" } applies a safe default for local development.
    • required := os.Getenv("DATABASE_URL") — validate non-empty and fail startup if missing.
    • log.Fatal("DATABASE_URL required") stops the process before serving broken requests.
    • flag.String("port", "8080", "HTTP port") adds CLI flags alongside env vars for flexibility.
    • yaml.Unmarshal loads file-based config for local dev while prod uses env injection.
    • never log config at INFO — secrets in DSN strings leak to log aggregators.

    Key takeaway: Fail fast on missing DATABASE_URL. Defaults for non-critical settings. Pass cfg struct to server constructor.

    Real-world use

    Where you'll use this in production

    • K8s ConfigMap for app settings, Secret for DB password.
    • Feature flags toggling experimental endpoints.
    • Multi-region deployment with REGION env var.
    • Local dev with .env and docker-compose environment block.

    Best practices

    • Validate all config at startup — no silent defaults for secrets.
    • Typed Config struct passed to dependencies.
    • Document required env vars in README.
    • Use secret manager in production, not plain env in git.
    • Support config file path via flag for local override.
    • Log effective config at startup minus secret values.

    Common mistakes

    • Reading os.Getenv scattered everywhere — inconsistent defaults.
    • Committing .env with real credentials.
    • No validation — nil pointer when secret missing at runtime.
    • Hardcoding production URLs in source code.
    • Logging full config including API keys at Info level.

    Advanced interview questions

    Q1BeginnerConfig source priority?
    Typically: flags > env vars > config file > defaults.
    Q2Beginner12-factor config rule?
    Store config in environment; strict separation from code.
    Q3IntermediateK8s secrets as files vs env?
    Env simpler; files better for rotation and large certs.
    Q4Intermediateviper vs manual env?
    viper supports files+env+watch; manual fine for small services.
    Q5AdvancedDesign config for multi-tenant SaaS.
    Base config + tenant overrides; validate per-tenant limits; secrets per tenant in vault.

    Summary

    Load typed Config struct from environment at startup. Validate required values; fail fast before serving traffic. Never commit secrets; use K8s Secrets or vault. Pass Config to constructors — don't scatter getenv calls. Next lesson: Dockerizing Go applications.

    Ready to mark this lesson complete?Track your journey across the entire course.