Configuration

Configure your Forge application with files, environment variables, and code

Forge provides three complementary ways to configure your application: functional options in Go code, YAML configuration files with auto-discovery, and environment variables. These layers compose together with a clear precedence order.

Configuration Precedence

When the same setting is specified in multiple places, the following order applies (highest priority first):

  1. Environment variables (when EnvOverridesFile is true, which is the default)
  2. config.local.yaml (local developer overrides)
  3. config.yaml (base configuration)
  4. Functional options in Go code (e.g., forge.WithHTTPAddress(":9090"))
  5. DefaultAppConfig defaults

Functional Options

The most direct way to configure Forge is through functional options passed to forge.New():

app := forge.New(
    // Application identity
    forge.WithAppName("my-api"),
    forge.WithAppVersion("2.0.0"),
    forge.WithAppDescription("My production API server"),
    forge.WithAppEnvironment("production"),

    // Server settings
    forge.WithHTTPAddress(":9090"),
    forge.WithHTTPTimeout(60 * time.Second),
    forge.WithShutdownTimeout(45 * time.Second),

    // Extensions
    forge.WithExtensions(dbExtension, cacheExtension),

    // Custom components
    forge.WithAppLogger(customLogger),
    forge.WithAppMetrics(customMetrics),
    forge.WithAppConfigManager(customConfigManager),
)

Complete Option Reference

Application Identity

OptionDescriptionDefault
WithAppName(name)Application name"forge-app"
WithAppVersion(version)Semantic version"1.0.0"
WithAppDescription(desc)Human-readable description"Forge Application"
WithAppEnvironment(env)Environment ("development", "staging", "production")"development"

Server Settings

OptionDescriptionDefault
WithHTTPAddress(addr)Listen address (e.g., ":8080", "0.0.0.0:3000")":8080"
WithHTTPTimeout(d)Read/write timeout for HTTP connections30s
WithShutdownTimeout(d)Maximum time to wait for graceful shutdown30s
WithShutdownSignals(signals...)OS signals that trigger shutdownSIGINT, SIGTERM

Components

OptionDescription
WithExtensions(ext...)Register extensions at creation time
WithAppLogger(logger)Replace the default logger
WithAppMetrics(metrics)Replace the default metrics collector
WithAppConfigManager(cm)Replace the default configuration manager
WithAppRouterOptions(opts...)Pass options to the underlying router
WithAppMetricsConfig(cfg)Configure metrics collection
WithAppHealthConfig(cfg)Configure health check behavior
WithAppErrorHandler(handler)Replace the default error handler

Config Auto-Discovery

OptionDescriptionDefault
WithEnableConfigAutoDiscovery(bool)Enable/disable automatic config file searchtrue
WithConfigSearchPaths(paths...)Directories to search for config filesCurrent directory
WithConfigBaseNames(names...)Base config file names["config.yaml", "config.yml"]
WithConfigLocalNames(names...)Local override file names["config.local.yaml", "config.local.yml"]
WithEnableAppScopedConfig(bool)Enable app-scoped config for monorepostrue

Environment Variables

OptionDescriptionDefault
WithEnableEnvConfig(bool)Enable loading config from env varstrue
WithEnvPrefix(prefix)Prefix for env vars (e.g., "MYAPP_")App name in uppercase
WithEnvSeparator(sep)Separator for nested keys"_"
WithEnvOverridesFile(bool)Whether env vars override file configtrue

Using Direct Config

Instead of functional options, you can build an AppConfig struct directly:

config := forge.DefaultAppConfig()
config.Name = "my-api"
config.Version = "2.0.0"
config.HTTPAddress = ":9090"
config.Environment = "production"

app := forge.NewApp(config)

YAML Configuration Files

Forge auto-discovers and loads YAML configuration files on startup. No code changes are needed -- just drop the files in your project directory.

Base Config (config.yaml)

This is your primary configuration file, committed to version control:

# config.yaml
server:
  host: 0.0.0.0
  port: 8080
  timeout: 30s

database:
  driver: postgres
  host: db.example.com
  port: 5432
  name: myapp
  pool:
    max_open: 25
    max_idle: 5
    max_lifetime: 5m

cache:
  driver: redis
  host: redis.example.com
  port: 6379
  ttl: 10m

logging:
  level: info
  format: json

Local Overrides (config.local.yaml)

Developer-specific settings that override the base config. Add this file to .gitignore:

# config.local.yaml
database:
  host: localhost
  password: localdev123

cache:
  host: localhost

logging:
  level: debug
  format: pretty

Always add config.local.yaml to your .gitignore to avoid committing local secrets or developer-specific settings.

Auto-Discovery Behavior

When EnableConfigAutoDiscovery is true (the default), Forge:

  1. Searches from the current working directory upward (up to 5 levels).
  2. Looks for files matching ConfigBaseNames (default: config.yaml, config.yml).
  3. Looks for files matching ConfigLocalNames (default: config.local.yaml, config.local.yml).
  4. Loads the base config first, then merges local overrides on top.
  5. In monorepo layouts, can scope config to the current app name when EnableAppScopedConfig is true.

Binding Config to Structs

Access configuration values by binding them to Go structs:

type DatabaseConfig struct {
    Driver string `yaml:"driver"`
    Host   string `yaml:"host"`
    Port   int    `yaml:"port"`
    Name   string `yaml:"name"`
    Pool   struct {
        MaxOpen     int           `yaml:"max_open"`
        MaxIdle     int           `yaml:"max_idle"`
        MaxLifetime time.Duration `yaml:"max_lifetime"`
    } `yaml:"pool"`
}

// In your service or extension:
var dbConfig DatabaseConfig
if err := app.Config().Bind("database", &dbConfig); err != nil {
    return fmt.Errorf("failed to load database config: %w", err)
}

fmt.Println(dbConfig.Host) // "localhost" (from local override)

Type-Safe Accessors

For individual values, use the typed getter methods:

cm := app.Config()

// Get a string value
host, err := cm.GetString("database.host")

// Get an integer value
port, err := cm.GetInt("database.port")

// Get a boolean value
debug, err := cm.GetBool("logging.debug")

// Get any value
val, err := cm.Get("database")

Environment Variables

Environment variables provide the highest-priority configuration source, ideal for containers, CI/CD pipelines, and twelve-factor apps.

How It Works

When EnableEnvConfig is true (the default), Forge reads environment variables with the configured prefix and maps them to config keys using the separator:

MYAPP_DATABASE_HOST=db.prod.example.com
  │      │        │
  │      │        └── Value
  │      └── Config key: "database.host" (separator: "_")
  └── Prefix: "MYAPP_"

Configuration

app := forge.New(
    forge.WithAppName("myapp"),
    forge.WithEnableEnvConfig(true),
    forge.WithEnvPrefix("MYAPP_"),     // Prefix for all env vars
    forge.WithEnvSeparator("_"),        // Nested key separator
    forge.WithEnvOverridesFile(true),   // Env takes precedence over YAML
)

Example

Given this config.yaml:

database:
  host: localhost
  port: 5432

And these environment variables:

export MYAPP_DATABASE_HOST=db.production.example.com
export MYAPP_DATABASE_PORT=5433

The resolved values will be:

KeyYAML ValueEnv ValueResolved
database.hostlocalhostdb.production.example.comdb.production.example.com
database.port543254335433

If EnvPrefix is not set explicitly, it defaults to the app name converted to uppercase with a trailing underscore. For example, WithAppName("my-api") results in a prefix of MY-API_.

The .forge.yaml File

Forge also supports a .forge.yaml file for CLI and project-level configuration. This is separate from the runtime config.yaml and is used primarily by the forge CLI tool for project structure, development, build, deployment, and code generation settings:

# .forge.yaml
project:
  name: my-api
  module: github.com/me/my-api
  layout: single-module

dev:
  auto_discover: true
  hot_reload:
    enabled: true
    delay: 500ms
  docker:
    image: golang:1.25-alpine

build:
  output_dir: ./bin

deploy:
  registry: ghcr.io/myorg

When running your app directly (not through forge dev), the dev.port setting from an app-level .forge.yaml is used if the HTTP address has not been explicitly configured. Priority order:

  1. PORT environment variable
  2. Explicit WithHTTPAddress() in code
  3. dev.port from app-level .forge.yaml
  4. Default :8080

For the complete .forge.yaml field reference, JSON Schema, and annotated examples, see the .forge.yaml Reference page.

DefaultAppConfig Values

For reference, here are all the defaults applied by forge.DefaultAppConfig():

AppConfig{
    Name:                      "forge-app",
    Version:                   "1.0.0",
    Description:               "Forge Application",
    Environment:               "development",
    HTTPAddress:               ":8080",
    HTTPTimeout:               30 * time.Second,
    ShutdownTimeout:           30 * time.Second,
    ShutdownSignals:           []os.Signal{os.Interrupt, syscall.SIGTERM},
    EnableConfigAutoDiscovery: true,
    EnableAppScopedConfig:     true,
    ConfigBaseNames:           []string{"config.yaml", "config.yml"},
    ConfigLocalNames:          []string{"config.local.yaml", "config.local.yml"},
    EnableEnvConfig:           true,
    EnvSeparator:              "_",
    EnvOverridesFile:          true,
}

Next Steps

How is this guide?

On this page