Auto-Discovery
Automatically discover configuration files in monorepos and multi-application projects.
Confy can automatically find your configuration files by searching the current directory and parent directories. This is especially useful in monorepos where your application may be nested several directories deep.
Quick Start
cfg, err := confy.AutoLoadConfy("myapp", nil)
if err != nil {
log.Fatal(err)
}
port := cfg.GetInt("server.port", 8080)AutoLoadConfy uses default settings to search for config.yaml and config.local.yaml starting from the current working directory, walking up parent directories.
Discovery Configuration
For more control, use DiscoverAndLoadConfigs with an AutoDiscoveryConfig:
cfg, result, err := confy.DiscoverAndLoadConfigs(confy.AutoDiscoveryConfig{
AppName: "myapp",
SearchPaths: []string{".", "./config", "/etc/myapp"},
ConfigNames: []string{"config.yaml", "config.yml", "config.json"},
LocalConfigNames: []string{"config.local.yaml", "config.local.yml"},
MaxDepth: 5,
RequireBase: true,
EnableEnvSource: true,
EnvPrefix: "MYAPP",
EnvSeparator: "_",
EnvOverridesFile: true,
})
if err != nil {
log.Fatal(err)
}
// Discovery result contains useful metadata
fmt.Println("Base configs:", result.BaseConfigPaths)
fmt.Println("Local configs:", result.LocalConfigPaths)
fmt.Println("Working dir:", result.WorkingDirectory)
fmt.Println("Monorepo:", result.IsMonorepo)
// Convenience methods return the first path (backward compat)
fmt.Println("First base:", result.BaseConfigPath())
fmt.Println("First local:", result.LocalConfigPath())Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
AppName | string | "" | Application name (used for app-scoping) |
SearchPaths | []string | ["."] | Directories to search |
ConfigNames | []string | ["config.yaml"] | Base config filenames |
LocalConfigNames | []string | ["config.local.yaml"] | Local override filenames |
MaxDepth | int | 5 | Max parent directories to search |
RequireBase | bool | false | Error if base config not found |
RequireLocal | bool | false | Error if local config not found |
EnableAppScoping | bool | false | Extract app-scoped config |
EnableEnvSource | bool | false | Add environment source |
EnvPrefix | string | "" | Env variable prefix |
EnvSeparator | string | "_" | Env key separator |
EnvOverridesFile | bool | true | Env vars override file values |
Search Algorithm
The discovery process searches all configured search paths and merges every config it finds:
- For each search path, look for any of the
ConfigNamesfiles - If not found, walk up to the parent directory (up to
MaxDepthlevels) - Once found, also look for
LocalConfigNamesin the same directory - Repeat for every remaining search path — all paths are searched, not just the first match
- Duplicate paths are removed automatically
- Optionally add an environment source
project-root/
├── config.yaml ← discovered (base, priority 100)
├── config.local.yaml ← discovered (local, priority 200)
├── services/
│ └── api/
│ └── main.go ← working directory (cwd)Multi-path merging
When multiple search paths each contain config files, they are all loaded with incrementing priorities so later paths layer on top of earlier ones:
SearchPaths: ["./config", "/etc/myapp"]
./config/config.yaml → base priority 100
/etc/myapp/config.yaml → base priority 101 (overrides ./config values)
./config/config.local.yaml → local priority 200
/etc/myapp/config.local.yaml → local priority 201 (overrides ./config local)All local configs override all base configs, and environment variables (priority 300 by default) override everything.
App-Scoped Configuration
In monorepos with multiple applications sharing a single config file, use app-scoping to extract the relevant section:
# config.yaml (shared)
database:
host: shared-db.internal
apps:
api:
port: 8080
database:
host: api-db.internal
worker:
concurrency: 10
database:
host: worker-db.internalcfg, err := confy.LoadConfigWithAppScope("api", logger, nil)
if err != nil {
log.Fatal(err)
}
// Gets "api-db.internal" — app config overrides shared config
host := cfg.GetString("database.host")
// Gets 8080 — from the api app section
port := cfg.GetInt("port")How App-Scoping Works
- Load the full configuration file
- Find the
apps.<appName>section - Merge the app-specific config over the global config
- App keys override global keys at the same path
Priority Resolution
When using app-scoping with multiple sources:
# config.yaml (priority 100)
database:
host: base-host
apps:
api:
database:
host: app-host
# config.local.yaml (priority 200)
database:
host: local-hostFor app api, the resolution is:
base-hostfrom base config (priority 100)app-hostfrom app-scoped base configlocal-hostfrom local config (priority 200) — wins
Local config globals always override app-scoped base config.
Loading from Explicit Paths
When you know exactly where your config files are:
cfg, err := confy.LoadConfigFromPaths(
"/etc/myapp/config.yaml", // base path
"/etc/myapp/config.local.yaml", // local path
"api", // app name (optional)
logger,
)Debugging Discovery
Use GetConfigSearchInfo to understand where confy would look for configs:
info := confy.GetConfigSearchInfo("myapp")
fmt.Println(info)
// Outputs search paths, filenames, and priority informationDefault Discovery Configuration
DefaultAutoDiscoveryConfig() returns sensible defaults:
defaultCfg := confy.DefaultAutoDiscoveryConfig()
// SearchPaths: ["."]
// ConfigNames: ["config.yaml", "config.yml"]
// LocalConfigNames: ["config.local.yaml", "config.local.yml"]
// MaxDepth: 5
// EnvSeparator: "_"
// EnvOverridesFile: trueThe config.local.yaml file is typically added to .gitignore so each developer can maintain their own overrides without affecting the shared configuration.
How is this guide?