Configuration Sources

Load configuration from files, environment variables, Consul KV, and Kubernetes ConfigMaps/Secrets.

Confy supports multiple configuration sources out of the box. Each source implements the ConfigSource interface with Load, Watch, and priority management.

Source Overview

SourcePackageWatchSecretsUse Case
Filesources.FileSource✅ fsnotifyYAML, JSON, TOML config files
Environmentsources.EnvSource✅ pollingMYAPP_DATABASE_HOSTdatabase.host
Consulsources.ConsulSource✅ blocking queryHashiCorp Consul KV store
Kubernetessources.K8sSource✅ watch APIConfigMaps and Secrets

File Source

The file source reads YAML, JSON, or TOML configuration files. It automatically detects the format from the file extension.

import "github.com/xraph/confy/sources"

source, err := sources.NewFileSource("config.yaml", sources.FileSourceOptions{
    Priority:      100,
    WatchEnabled:  true,
    ExpandEnvVars: true,  // expands ${VAR} in values
    ExpandSecrets: true,  // resolves ${secret:key} references
    RequireFile:   false, // don't error if file doesn't exist
})
if err != nil {
    log.Fatal(err)
}

cfg.LoadFrom(source)

File Source Options

OptionTypeDefaultDescription
NamestringautoCustom source name
FormatstringautoForce format (yaml, json, toml)
Priorityint100Source priority (higher wins)
WatchEnabledboolfalseEnable fsnotify file watching
WatchIntervaltime.Duration5sDebounce interval for changes
ExpandEnvVarsboolfalseExpand ${VAR} in values
ExpandSecretsboolfalseResolve ${secret:key} references
RequireFileboolfalseError if file doesn't exist
BackupEnabledboolfalseBackup file before changes
BackupDirstring""Directory for backup files

Supported Formats

# config.yaml
database:
  host: localhost
  port: 5432
{
  "database": {
    "host": "localhost",
    "port": 5432
  }
}
[database]
host = "localhost"
port = 5432

Environment Source

The environment source maps environment variables to nested configuration keys using a prefix and separator.

source, err := sources.NewEnvSource("MYAPP", sources.EnvSourceOptions{
    Separator:    "_",
    Priority:     300,
    WatchEnabled: true,
})

cfg.LoadFrom(source)

// MYAPP_DATABASE_HOST → database.host
// MYAPP_SERVER_PORT   → server.port
host := cfg.GetString("database.host")

Key Transformation

Environment variable keys are transformed using these rules:

  1. Strip the prefix: MYAPP_DATABASE_HOSTDATABASE_HOST
  2. Replace separator with dots: DATABASE_HOSTdatabase.host
  3. Lowercase the result: database.host

Env Source Options

OptionTypeDefaultDescription
NamestringautoCustom source name
Prefixstring""Environment variable prefix
Priorityint300Source priority
Separatorstring"_"Key separator
WatchEnabledboolfalsePoll for env changes
CaseSensitiveboolfalseCase-sensitive key matching
AutoConvertbooltrueAuto-convert values to int/bool/etc
RequiredVars[]stringnilRequired environment variables
SecretVars[]stringnilVariables treated as secrets
KeyMappingmap[string]stringnilCustom key mapping overrides
ValueMappingmap[string]funcnilCustom value transformers

Custom Key Mapping

Override the default key transformation for specific variables:

source, err := sources.NewEnvSource("MYAPP", sources.EnvSourceOptions{
    KeyMapping: map[string]string{
        "MYAPP_DB_URL": "database.dsn",
        "MYAPP_REDIS":  "cache.address",
    },
})

Auto-Conversion

String values are automatically converted to appropriate types:

InputResultType
"true", "yes", "1"truebool
"false", "no", "0"falsebool
"42"42int
"3.14"3.14float64
"hello""hello"string

Consul Source

The Consul source reads configuration from HashiCorp Consul's KV store. It supports watching via Consul's blocking query mechanism.

source, err := sources.NewConsulSource("myapp/config", sources.ConsulSourceOptions{
    Address:      "localhost:8500",
    Token:        os.Getenv("CONSUL_TOKEN"),
    Datacenter:   "dc1",
    Priority:     200,
    WatchEnabled: true,
    Timeout:      10 * time.Second,
    RetryCount:   3,
    RetryDelay:   5 * time.Second,
})

cfg.LoadFrom(source)

Consul Source Options

OptionTypeDefaultDescription
Addressstring"localhost:8500"Consul address
Tokenstring""ACL token
Datacenterstring""Consul datacenter
Prefixstring""KV prefix path
Priorityint200Source priority
WatchEnabledboolfalseWatch for KV changes
Timeouttime.Duration10sConnection timeout
RetryCountint3Retry attempts
RetryDelaytime.Duration5sDelay between retries
TLS*ConsulTLSConfignilTLS configuration

TLS Configuration

source, err := sources.NewConsulSource("myapp/config", sources.ConsulSourceOptions{
    Address: "consul.example.com:8501",
    TLS: &sources.ConsulTLSConfig{
        Enabled:  true,
        CertFile: "/path/to/cert.pem",
        KeyFile:  "/path/to/key.pem",
        CAFile:   "/path/to/ca.pem",
    },
})

KV Structure

Consul keys are mapped to nested configuration using / as a separator:

myapp/config/database/host  → database.host
myapp/config/database/port  → database.port
myapp/config/server/address → server.address

Values can be plain strings or JSON objects. JSON values are automatically parsed into nested maps.

Kubernetes Source

The Kubernetes source reads configuration from ConfigMaps and Secrets. It supports both in-cluster and out-of-cluster authentication.

source, err := sources.NewK8sSource(sources.K8sSourceOptions{
    Namespace:      "default",
    ConfigMapNames: []string{"myapp-config"},
    SecretNames:    []string{"myapp-secrets"},
    Priority:       250,
    WatchEnabled:   true,
    InCluster:      true,
})

cfg.LoadFrom(source)

K8s Source Options

OptionTypeDefaultDescription
Namespacestring"default"Kubernetes namespace
ConfigMapNames[]stringnilConfigMaps to read
SecretNames[]stringnilSecrets to read
Priorityint250Source priority
WatchEnabledboolfalseWatch for changes
KubeConfigstring""Path to kubeconfig
InClusterboolfalseUse in-cluster config
LabelSelectorstring""Label selector for resources
FieldSelectorstring""Field selector for resources
RetryCountint3Retry attempts
RetryDelaytime.Duration5sDelay between retries

ConfigMap Data

ConfigMap keys containing structured data (YAML/JSON) are automatically parsed:

# ConfigMap: myapp-config
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  config.yaml: |
    database:
      host: postgres.internal
      port: 5432
  log_level: "info"

Secrets

Kubernetes Secrets are base64-decoded and available as configuration values:

// Secret data is decoded and accessible via the config key path
dbPassword := cfg.GetString("database.password")

Source Priority

When multiple sources provide the same key, the value from the highest-priority source wins:

cfg := confy.New()

// Priority 100 — base defaults
baseSource, _ := sources.NewFileSource("config.yaml", sources.FileSourceOptions{
    Priority: 100,
})

// Priority 200 — local overrides
localSource, _ := sources.NewFileSource("config.local.yaml", sources.FileSourceOptions{
    Priority: 200,
})

// Priority 300 — environment variables (highest)
envSource, _ := sources.NewEnvSource("MYAPP", sources.EnvSourceOptions{
    Priority: 300,
})

cfg.LoadFrom(baseSource, localSource, envSource)
// env vars > local file > base file

Use the priority constants confy.PriorityBaseConfig (100), confy.PriorityLocalConfig (200), and confy.PriorityEnvHigh (300) for standard layering.

Custom Sources

Implement the ConfigSource interface to create custom configuration sources:

type ConfigSource interface {
    Name() string
    GetName() string
    GetType() string
    Priority() int
    IsAvailable(ctx context.Context) bool
    Load(ctx context.Context) (map[string]any, error)
    Watch(ctx context.Context, callback func(map[string]any)) error
    StopWatch() error
    Reload(ctx context.Context) error
    IsWatchable() bool
    SupportsSecrets() bool
    GetSecret(ctx context.Context, key string) (string, error)
}

Example: HTTP Config Source

type HTTPSource struct {
    url      string
    priority int
    interval time.Duration
}

func (s *HTTPSource) Name() string     { return "http:" + s.url }
func (s *HTTPSource) GetName() string  { return s.Name() }
func (s *HTTPSource) GetType() string  { return "http" }
func (s *HTTPSource) Priority() int    { return s.priority }

func (s *HTTPSource) Load(ctx context.Context) (map[string]any, error) {
    resp, err := http.Get(s.url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var data map[string]any
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return nil, err
    }
    return data, nil
}

How is this guide?

On this page