How to Configure Dynamic Configuration Sources in Kratos: File, Etcd, Consul, and Apollo

Kratos unifies dynamic configuration through the config.Source interface, which continuously monitors external stores via the Watch() method and hot-reloads values into your application without restarts.

The go-kratos/kratos microservice framework provides a pluggable configuration subsystem that supports real-time updates from multiple backends. By leveraging the config.Source abstraction defined in config/source.go, developers can configure dynamic configuration sources in Kratos to load settings from local files, distributed key-value stores, and remote configuration centers while maintaining zero-downtime configuration updates.

Understanding the Kratos Configuration Architecture

The Source Interface

At the heart of dynamic configuration lies the config.Source interface located in [config/source.go](https://github.com/go-kratos/kratos/blob/main/config/source.go). Every backend must implement two critical methods:

  • Load() – Retrieves the current set of key/value pairs from the external store.
  • Watch() – Returns a config.Watcher that yields new *config.KeyValue slices whenever the underlying data changes.

This abstraction allows the core framework to treat file watchers, Etcd listeners, Consul blocking queries, and Apollo notifications identically.

The Config Lifecycle

When you initialize the system using config.New (defined in [config/config.go](https://github.com/go-kratos/kratos/blob/main/config/config.go)), you supply sources via the WithSource functional option from [config/options.go](https://github.com/go-kratos/kratos/blob/main/config/options.go). The lifecycle follows this sequence:

  1. Initial Load – Each source's Load() method executes once, merging key/value pairs into an internal cache.
  2. Watcher Initialization – A dedicated goroutine runs config.watch for every source, blocking on the Watch() stream.
  3. Hot Reload – When a watcher detects changes, the framework merges updates, resolves placeholder variables (e.g., ${ENV_VAR}), and notifies registered observers.

Because watchers run continuously, any modification in the external store—whether a file edit, Etcd key update, Consul KV change, or Apollo namespace refresh—automatically propagates to your running service.

Configuring File-Based Dynamic Configuration

The file source in [contrib/config/file/file.go](https://github.com/go-kratos/kratos/blob/main/contrib/config/file/file.go) monitors directories or individual files for changes. It automatically detects formats (JSON, YAML, XML, TOML) from file extensions and uses the appropriate codec from the encoding package.

import (
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/config"
    "github.com/go-kratos/kratos/v2/config/file"
    "github.com/go-kratos/kratos/v2/log"
)

func main() {
    // Create a file source (can be a directory or a single file)
    src := file.NewSource("./configs")

    // Build the config subsystem
    cfg := config.New(
        config.WithSource(src),                // dynamic source
        config.WithResolveActualTypes(true),   // optional type conversion
    )
    if err := cfg.Load(); err != nil {
        log.Fatalf("load config error: %v", err)
    }

    // Access a value
    port := cfg.Value("http.port").String()
    log.Infof("http port = %s", port)

    // React to future changes of the key
    _ = cfg.Watch("http.port", func(k string, v config.Value) {
        log.Infof("port changed to %s", v.String())
    })

    // Start your Kratos application with the config as a dependency
    app := kratos.New(
        kratos.Config(cfg),
        // other options …
    )
    if err := app.Run(); err != nil {
        log.Fatalf("app run error: %v", err)
    }
}

Configuring Etcd as a Dynamic Source

For distributed systems, the Etcd source in [contrib/config/etcd/config.go](https://github.com/go-kratos/kratos/blob/main/contrib/config/etcd/config.go) watches a key prefix and streams Watch events from the Etcd client.

import (
    "github.com/go-kratos/kratos/v2/config"
    etcdconf "github.com/go-kratos/kratos/contrib/config/etcd/v2"
    clientv3 "go.etcd.io/etcd/client/v3"
)

func etcdConfig() config.Config {
    // Etcd client (assume endpoint is reachable)
    cli, _ := clientv3.New(clientv3.Config{
        Endpoints: []string{"http://127.0.0.1:2379"},
    })
    src, _ := etcdconf.New(cli,
        etcdconf.WithPath("/myapp/config"),
        etcdconf.WithPrefix(true), // watch all keys under the prefix
    )
    return config.New(config.WithSource(src))
}

The WithPrefix(true) option ensures that any key under /myapp/config triggers an update, enabling hierarchical configuration trees.

Configuring Consul KV Store

The Consul integration in [contrib/config/consul/config.go](https://github.com/go-kratos/kratos/blob/main/contrib/config/consul/config.go) uses blocking queries to efficiently watch for changes in the HashiCorp Consul KV store.

import (
    "github.com/go-kratos/kratos/v2/config"
    consulconf "github.com/go-kratos/kratos/contrib/config/consul/v2"
    "github.com/hashicorp/consul/api"
)

func consulConfig() config.Config {
    client, _ := api.NewClient(api.DefaultConfig())
    src, _ := consulconf.New(client, consulconf.WithPath("myapp/config"))
    return config.New(config.WithSource(src))
}

This source polls Consul using the ModifyIndex mechanism, ensuring you receive updates immediately when an operator changes a value in the UI or API.

Configuring Apollo Configuration Center

For Apollo users, the source in [contrib/config/apollo/apollo.go](https://github.com/go-kratos/kratos/blob/main/contrib/config/apollo/apollo.go) supports multiple namespaces, secret-based authentication, and local backup caching.

import (
    "github.com/go-kratos/kratos/v2/config"
    apolloconf "github.com/go-kratos/kratos/contrib/config/apollo/v2"
)

func apolloConfig() config.Config {
    src := apolloconf.NewSource(
        apolloconf.WithAppID("demo-app"),
        apolloconf.WithCluster("default"),
        apolloconf.WithEndpoint("http://apollo.meta.server"),
        apolloconf.WithNamespace("application,extra.yaml"),
        apolloconf.WithSecret("my-secret"),
        apolloconf.WithEnableBackup(),
    )
    return config.New(config.WithSource(src))
}

The WithNamespace option accepts comma-separated values, allowing you to compose configuration from multiple Apollo namespaces simultaneously.

Watching Configuration Changes at Runtime

Beyond automatic reloading, you can register granular observers for specific keys using the Watch method. When the internal config.watch goroutine detects a diff (implemented around line 84 in config/config.go), it invokes your callback with the new config.Value.

_ = cfg.Watch("database.max_connections", func(key string, value config.Value) {
    // Convert to int and apply to connection pool
    maxConn, _ := value.Int()
    pool.SetMaxOpenConns(maxConn)
})

This pattern enables reactive programming—your service adjusts thread pools, feature flags, or logging levels instantly when operators update the remote store.

Summary

  • Unified Interface – All dynamic sources implement config.Source with Load() and Watch() methods defined in config/source.go.
  • Lifecycle Managementconfig.New accepts WithSource options, loads initial data via config.Load, and spawns background watchers via config.watch in config/config.go.
  • Backend Variety – File, Etcd, Consul, and Apollo sources live in contrib/config/ and share identical integration patterns.
  • Hot Reloading – Changes propagate automatically through the watcher channel, merging updates and resolving placeholders without process restarts.
  • Observable Updates – Register key-specific callbacks using cfg.Watch to react to precise configuration mutations at runtime.

Frequently Asked Questions

How does Kratos hot-reload configuration without restarting the service?

Kratos spawns a background goroutine for each source that blocks on the Watch() method. When the underlying backend detects a change (file modification, Etcd event, Consul index change, or Apollo notification), the watcher pushes new key/value pairs to the core config.Config instance. The framework merges these updates into the internal cache and resolves any variable placeholders, making new values available immediately to cfg.Value() calls and registered observers.

Can I combine multiple dynamic sources like Etcd and file in the same application?

Yes. The WithSource option in config/options.go accepts variadic arguments, allowing you to pass multiple sources to config.New. For example, you can load baseline settings from a local file and override specific keys with values from Etcd. The sources are loaded in order and merged, with later sources taking precedence in case of key collisions.

What configuration formats are supported by the Kratos file source?

The file source in contrib/config/file/file.go automatically detects the format from file extensions and uses the corresponding codec from the encoding subsystem. Supported formats include JSON, YAML, XML, TOML, and YML. You can place multiple format files in the same directory; the source loads and merges all valid configuration files it discovers.

How do I watch a specific configuration key for changes?

Use the Watch method on your config.Config instance, passing the key path and a callback function. When the internal diff detection logic in config/config.go determines that the value for your key has changed, it invokes your callback with the key name and new config.Value. This is ideal for adjusting connection limits, toggling feature flags, or reloading TLS certificates without restarting the application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →