- Installation
- Usage
- Configuration Merging (Inheritance)
- Example File Structure
- Code
- Automatic Autoloader (init function)
- Architecture
- Examples
- Logger Support
- Configuration Value Priority
- Commands
- Environment Variables
Go package for loading environment-based configurations (SCOPE) using Viper.
This package is particularly well-suited for Kubernetes environments, where
the SCOPE variable (e.g., dev, staging, prod) can be easily injected
into Pods as an environment variable, allowing the application to automatically
pick the correct configuration file based on the cluster environment.
The environment variable name (SCOPE) is configurable to support different
organizational standards.
go get github.com/arielsrv/go-scope-configThe package automatically looks for the SCOPE environment variable.
If it's not defined, it uses dev by default.
It searches for files with the pattern config.[SCOPE].yaml or
config.[SCOPE].yml in a folder (default is config/).
If a config.common.yaml (or .yml) exists in the configuration directory,
it will be loaded first as a base configuration. Then, the scope-specific file
(config.[SCOPE].yaml) will be merged into it, overriding any shared keys.
graph TD
Start([Start Load]) --> InitViper[Initialize Viper]
InitViper --> LoadEnv[AutomaticEnv enabled]
LoadEnv --> CheckCommon{config.common.yaml?}
CheckCommon -- Exists --> ReadCommon[Read common config]
CheckCommon -- Not Found --> CheckScope
ReadCommon --> CheckScope{config.SCOPE.yaml?}
CheckScope -- Exists --> MergeScope[Merge scope config]
CheckScope -- Not Found --> Error([Error: Scope config not found])
MergeScope --> FinalConfig([Final Configuration])
subgraph Merging Logic
ReadCommon
MergeScope
end
style ReadCommon fill:#f9f,stroke:#333,stroke-width:2px
style MergeScope fill:#bbf,stroke:#333,stroke-width:2px
style Error fill:#fbb,stroke:#333,stroke-width:2px
.
βββ config/
β βββ config.common.yaml (base values)
β βββ config.dev.yaml (overrides for dev)
β βββ config.prod.yml (overrides for prod)
βββ main.go
The quickest way to start with default options:
package main
import (
"fmt"
"log/slog"
"os"
goscopeconfig "github.com/arielsrv/go-scope-config"
)
func main() {
// Local logger to avoid using the global slog
logger := slog.Default()
// Loads from "config/" folder using SCOPE env var (defaults to "dev")
v, err := goscopeconfig.LoadDefault()
if err != nil {
logger.Error("Error loading default config", "error", err)
os.Exit(1)
}
fmt.Printf("App Name: %s\n", v.GetString("app.name"))
}The package provides two ways to automatically load the configuration.
If you import the package with a name, you can access the pre-initialized
DefaultViper instance.
package main
import (
"fmt"
"log/slog"
"os"
goscopeconfig "github.com/arielsrv/go-scope-config"
)
func main() {
// Local logger to avoid using the global slog
logger := slog.Default()
// Check if there was an error during automatic loading
if goscopeconfig.ErrLoad != nil {
logger.Error("Error loading config", "error", goscopeconfig.ErrLoad)
os.Exit(1)
}
// Use the pre-loaded instance
v := goscopeconfig.DefaultViper
fmt.Printf("App Name: %s\n", v.GetString("app.name"))
}If you want to use the standard github.com/spf13/viper package directly,
you can use the autoload sub-package with a blank import. This will load the
configuration into Viper's global instance (the singleton returned by
viper.GetViper()).
Important
This is distinct from the DefaultViper provided by the root package,
which uses its own isolated Viper instance.
package main
import (
"fmt"
_ "github.com/arielsrv/go-scope-config/autoload"
"github.com/spf13/viper"
)
func main() {
// Configuration is already loaded into global viper
fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
}package main
import (
"fmt"
"log/slog"
"os"
goscopeconfig "github.com/arielsrv/go-scope-config"
)
func main() {
// Local logger to avoid using the global slog
logger := slog.Default()
// Initializes the loader.
// By default, it looks in the "config/" folder and reads the
// "SCOPE" environment variable.
loader := goscopeconfig.New(
goscopeconfig.WithConfigDir("custom_configs"),
goscopeconfig.WithScopeEnv("APP_ENV"),
)
if err := loader.Load(); err != nil {
logger.Error("Error loading config", "error", err)
os.Exit(1)
}
// Access values via Viper
v := loader.Viper()
fmt.Printf("App Name: %s\n", v.GetString("app.name"))
fmt.Printf("Current Scope: %s\n", loader.GetScope())
}The ConfigLoader is the core component. It uses options for configuration:
New(...Option): Creates a loader.- NewWithViper(*viper.Viper, ...Option): Creates a loader using an existing Viper instance.
- WithConfigDir(string): Custom configuration directory.
- WithScope(string): Force a specific scope.
- WithScopeEnv(string): Custom environment variable for scope.
- WithLogger(Logger): Provide a logger for loading information.
loader.Load(): Executes the loading and merging.loader.Viper(): Returns the internal*viper.Viperinstance.loader.GetScope(): Returns the current detected scope.loader.GetConfigPath(): Returns the absolute path of the loaded configuration file.
You can find complete examples in the examples/ folder:
- blank-import: Usage with blank import (global Viper).
- autoloader: Quick start using
LoadDefault(). - automatic: Accessing the pre-initialized
DefaultViper. - with-logger: Integrating with
slog(JSON format). - custom-scope-env: Using a custom environment variable for scope.
- custom-dir: Loading configurations from a non-standard directory.
- merge-common: Demonstrating configuration inheritance and overrides.
- uber-fx: Integration with the Uber-FX dependency injection framework.
- docker-compose: Running inside a container with Docker Compose, demonstrating environment variable inheritance.
The examples are in a separate module. To run them, go to the examples directory:
cd examplesThen run the desired example:
go run simple/main.go
go run custom-dir/main.go
go run merge-common/main.go
go run autoloader/main.go
go run automatic/main.go
go run blank-import/main.go
go run with-logger/main.go
go run custom-scope-env/main.go
go run uber-fx/main.goTo run the Docker Compose example:
cd examples/docker-compose
docker compose upYou can provide a logger that satisfies the Logger interface (which has a
Printf(format string, v ...any) method). This allows easy integration with
both the standard log package and modern loggers like slog.
// Example with slog (JSON format)
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)
// Small wrapper to satisfy the Printf interface
type slogWrapper struct { logger *slog.Logger }
func (s *slogWrapper) Printf(f string, v ...any) {
s.logger.Info(fmt.Sprintf(f, v...))
}
loader := goscopeconfig.New(
goscopeconfig.WithLogger(&slogWrapper{logger: logger}),
)
loader.Load()The project uses Taskfile to manage common tasks:
task test: Run unit tests.task lint: Run linters (golangci-lint,gofumpt,betteralign).task audit: Verify modules, rungo vetandgovulncheck.task markdown: Lint markdown files.task coverage: Generate and show coverage report.task clean: Remove coverage and temporary files.task build: Build the project.
The package uses Viper's resolution order. When the same key is defined in multiple sources, the following priority applies (highest wins):
| Priority | Source | Example |
|---|---|---|
| 1 β Highest | Environment variables (AutomaticEnv) |
APP_NAME=myapp |
| 2 | Scope-specific config file | config.prod.yaml |
| 3 β Lowest | Common config file | config.common.yaml |
Key-mapping rule: dots (.) in YAML keys are replaced by underscores (_) in
environment variable names.
app.name β APP_NAME
app.port β APP_PORT
db.host β DB_HOST
Important
Environment variables always override any value defined in the YAML files. This follows the 12-Factor App principle and is especially useful in containerised environments (Docker, Kubernetes) where secrets or runtime values are injected via env vars.
# Overrides app.name regardless of what is in the YAML files
APP_NAME=production-service go run main.go
# Works even if the key does NOT exist in any YAML file
NEW_KEY=value go run main.go # v.GetString("new.key") β "value"| Variable | Default | Description |
|---|---|---|
SCOPE |
dev |
Selects the scope config file. Configurable via WithScopeEnv. |
<ANY_KEY> |
- | Overrides YAML values at runtime (dots replaced by underscores). |