Important
This repository has moved. The family lives in the
dynamic-config-rs organisation
now, split into four repositories that release on their own schedules:
| What | Where |
|---|---|
the engine, the macro, the CLI, the no_std cell |
dynamic-config-rs/dynamic-config |
| the eight stores and the config server | dynamic-config-rs/dynamic-config-remote |
| the two PyPI wheels | dynamic-config-rs/dynamic-config-python |
| the two npm packages | dynamic-config-rs/dynamic-config-node |
The books are at https://dynamic-config-rs.github.io/.
Nothing you installed changed: the crate names, the wheel names and the package names are the same, and 0.6.1 — the last release cut from here — is on crates.io, PyPI and npm exactly as it was. Issues, pull requests and releases now happen in the repository that owns the code; this one is archived and read-only.
This site stays up, and so does its history: every release up to 0.6.1 was made here, and the links in those changelogs still point at it.
Hot-reloadable, layered configuration for Rust — one attribute, lock-free reads.
Configuration that stays live after startup: files, environment, remote stores and command-line flags merged into one typed struct, re-read when they change, served to every thread as one atomic load.
[dependencies]
dynamic-config = { version = "0.6.1", features = ["toml", "watch"] }use dynamic_config::dynamic_config;
use serde::Deserialize;
use std::time::Duration;
#[dynamic_config]
#[derive(Debug, Deserialize)]
pub struct DatabaseConfig {
pub host: String,
pub port: u16,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let builder = DatabaseConfig::builder("db")
.file("config.toml")
.file("secrets.json")
.env("APP_");
builder.init()?; // load once, fail fast on a bad config
builder.watch(Duration::from_millis(250))?.detach(); // reload in the background from now on
let config = DatabaseConfig::current(); // one atomic load, on any thread
println!("{}:{}", config.host, config.port);
Ok(())
}The attribute declares — this type is a configuration — and generates its storage and accessors. The builder configures: where the sources are is runtime data, and it lives in runtime code.
- Reads are lock-free and allocation-free.
current()acquires anarc-swapguard — 85 instructions, ~20 ns, and zero allocations per 100 000 reads, all three measured rather than asserted — so configuration can be read per request without a second thought. - A bad edit cannot take the process down. A file that no longer parses or validates degrades to "no change"; the previous snapshot keeps serving, and the error is reported.
- Layers with provenance.
defaults < discovered < files < remote < secrets_dir < .env < environment < bindings < flags < overrides— andsource_of("key")names the file, variable or store a value actually came from. - Secrets stay out of diagnostics. Errors, diffs, reports and
{:?}print paths and types, never values — enforced by its own test suite. - Any runtime, or none. The async surface is a
Futureand a thread; tokio, smol and Embassy all drive it. Blocking work never lands on your executor. - Remote stores are explicit.
refresh_remote()does the network round trip;load()never does. Eight store crates ship, each watching the way its protocol allows — seven over a network, and git.
The full story — precedence, profiles, discovery, hot reload, encryption, schema export, units, the last-known-good cache, testing patterns — lives in the book.
| Crate | What | Stability |
|---|---|---|
dynamic-config |
the engine: loading, layers, storage, watching | Beta |
dynamic-config-macros |
#[dynamic_config] |
Beta |
dynamic-config-etcd |
etcd, push watch over gRPC | Beta |
dynamic-config-consul |
Consul KV, blocking queries | Beta |
dynamic-config-nats |
NATS JetStream KV, push watch | Beta |
dynamic-config-redis |
Redis, keyspace notifications | Beta |
dynamic-config-vault |
Vault KV v2, version polling | Beta |
dynamic-config-s3 |
S3 & compatibles, ETag polling — needs tokio | Beta |
dynamic-config-firestore |
Firestore REST, updateTime polling |
Beta |
dynamic-config-git |
a git repository, shallow single-ref fetch — GitHub, GitLab, Azure DevOps | Beta |
dynamic-config-embedded |
the same shape for no_std targets |
Beta |
dynamic-config-server |
serves configuration over HTTP, per-caller authorisation | Beta |
dynamic-config-cli |
explain and diff on the command line — cargo install dynamic-config-cli |
Beta |
dynamic-config-python |
Python bindings — pip install dynamic-config-py; a dataclass, Pydantic or msgspec validates |
Beta |
dynamic-config-python-remote |
the stores for Python — pip install "dynamic-config-py[remote]" |
Beta |
dynamic-config-node |
Node.js bindings — npm install dynamic-config-node; Zod, Ajv or a function of your own validates |
Beta |
dynamic-config-node-remote |
the stores for Node — npm install dynamic-config-node-remote |
Beta |
dynamic-config-store-core is also published, and is not in the table: it
is machinery the store crates share rather than something to depend on.
Every crate is Beta: breaking changes bump the minor pre-1.0 and are
announced in the changelog; a patch never breaks. The store crates were
Experimental until 0.6.1, and what promoted them is evidence — each is
tested against a real server in a container, each watch loop's failure
branches are enumerated in its own documentation, and three of them are
unplugged mid-watch by just chaos.
Between here and 1.0, only security fixes and hotfixes land. The surface is what it is going to be for 0.x: no new sources, no new stores, no new methods on the settled types. Pin the minor version and take patches automatically. Details in Stability Tiers.
Every store follows the same contract — the current value is not announced at startup, a deleted key is not a change, transport failures retry, a panicking callback ends the watch with an error — and each documents its stop latency and change-detection rule side by side in Store Crates at a Glance.
| floor | |
|---|---|
dynamic-config core |
1.71 |
schema feature |
1.74 (schemars) |
watch / age / full features |
1.85 (measured, not declared) |
| store crates | 1.85 — nats/redis/s3: 1.88 (their clients) |
dynamic-config-server |
1.80 (axum) |
dynamic-config-cli |
1.85 |
dynamic-config-python |
1.85 — and CPython 3.9+, one abi3 wheel per platform, plus a cp314t manylinux wheel for free-threaded builds |
dynamic-config-python-remote |
1.88 (the AWS, NATS and Redis clients) |
dynamic-config-embedded |
1.83 |
MSRV changes are breaking. Every floor has a CI row against a real toolchain; the full table with reasons is in MSRV & Features.
CONTRIBUTING.md is the short version; the onboarding tour walks every module. What will not be built, and why, is in Limitations & Not Planned; what might be is in ROADMAP.md.
What this engine is built on and whose ideas it took — CREDITS.md.
MIT.