Installation
konfig.ts is a workspace dependency, not a global tool. You add the CLI as a dev dependency, add the packages your manifests import, and point a konfig.json at your environment entry files.
Install the packages
Install @konfig.ts/cli as a dev dependency and the library packages as regular dependencies. Which libraries you need depends on what you render:
| Package | What it gives you | When you need it |
|---|---|---|
@konfig.ts/core | Manifests, module wrappers, Helm releases, dependency declarations, YAML output and diffing | Always |
@konfig.ts/k8s | Builders for Kubernetes resources: workloads, containers, Secrets, ConfigMaps, and binding an env contract to a pod | When you build Kubernetes resources directly |
@konfig.ts/env | The env contract: declare secrets, literals, and downward-API values, plus the runtime decoder for the running process. @konfig.ts/k8s re-exports the decoder as Environment.runtime | When you declare env contracts |
@konfig.ts/argocd | Argo CD Applications, the app-of-apps composition, and sync options | When you compose modules into an Argo CD environment |
@konfig.ts/sops, @konfig.ts/sealed-secrets, or @konfig.ts/external-secrets | One secret backend | When you render Secrets |
The binary is exposed as konfig. Run bunx konfig --help (or npx konfig --help) to confirm the install.
The Effect peer range
Every @konfig.ts/* library declares effect as a peer dependency with a caret range over the release-candidate line, currently ^4.0.0-rc.111: any rc build from rc.111 on satisfies it. @konfig.ts/core and the CLI additionally depend on @effect/platform-node at the same range, as a regular dependency rather than a peer, because they use the Node filesystem and subprocess services to write files and call helm. All packages declare engines.node >= 22.
The range is deliberately narrow: it floats across rc builds of 4.0.0 but never crosses into another prerelease line or a stable major. When Effect ships a stable 4.x, the range widens to ^4.x.
konfig.json
konfig.json tells the CLI where your sources live, which environments exist, and where rendered output goes. The commands that render (konfig build, validate, diff, and set) walk up from the current directory until they find one, and fail with ConfigNotFound otherwise. The tooling commands (konfig helm, crd, docker, and graph) do not read it. This is the file from examples/full-stack:
{
"root": ".",
"cluster": "infra/cluster.ts",
"modules": "infra/modules",
"envs": {
"prod": { "entry": "infra/envs/prod.ts" },
"staging": { "entry": "infra/envs/staging.ts" }
},
"outDir": { "manifests": ".generated/manifests" }
}Only three fields are required: root, envs, and outDir.manifests. Decoding is strict: unknown keys, missing required keys, and invalid JSON are all reported as ConfigParseError with the file path.
The full schema accepts the fields below. Only the first five are read by a command today; the rest are accepted, defaulted, and exported as types, but no command consumes them yet.
| Field | Required | Meaning |
|---|---|---|
root | yes | Directory holding your infra sources, relative to konfig.json. Env entries, images.json, outDir.manifests, and diff.baseline resolve relative to it. |
envs | yes | Map of env name to { "entry": "<path>" }, relative to root. An env not listed here falls back to <root>/env/<name>.ts (EnvEntryNotFound if that file is missing). |
outDir.manifests | yes | Where konfig build writes rendered manifests, relative to root. The env name (and --cluster name) is appended. |
diff.baseline | no | Baseline manifest tree for konfig diff, relative to root; the env name is appended, so konfig diff staging reads <root>/<baseline>/staging. Without it konfig diff fails with DiffBaselineMissing. |
cacheInclude | no | Extra files, directories, or glob patterns hashed into the build cache on top of everything under root, relative to konfig.json (absolute paths allowed). Use it for inputs outside root that affect a render. Default []. |
cluster | no | String, default "cluster.ts". Not read by any command. |
modules | no | String, default "modules". Not read by any command. |
charts | no | String, default "infra/k8s-konfig/charts", relative to konfig.json’s own directory. Read by konfig helm fetch and konfig crd, beneath KONFIG_CHARTS_DIR. |
crd.outDir | no | String, default ".generated/crd", relative to konfig.json’s own directory. Read by konfig crd extract, beneath KONFIG_CRD_OUT_DIR. |
helm.cacheDir | no | String, default ".konfig/helm-cache", relative to konfig.json’s own directory. Read by konfig helm fetch, konfig crd, and Helm.release during build/validate/diff, beneath KONFIG_HELM_CACHE. |
helm.minVersion | no | String, default "3.16.0". Read by the konfig helm fetch/konfig crd preflight check, beneath KONFIG_HELM_MIN_VERSION; Helm.release still takes its own opt-in minVersion option and does not read this field. |
services.outFile, services.globalPaths | no | Optional string and string array (default []). Not read by any command. |
clusters | no | Map of cluster name to { registry?, ingressClass?, storageClass?, repositoryUrl? }. Not read by any command; --cluster <name> only sets the cluster name on the render context and the output subdirectory. |
Environment variable overrides
Four environment variables configure the Helm and CRD tooling. Precedence is: environment variable, then the matching konfig.json field, then the built-in default. The default applies when no konfig.json is found or the field is left unset; in that fallback case the path resolves against the current working directory.
| Variable | Overrides field | Default | Read by |
|---|---|---|---|
KONFIG_HELM_CACHE | helm.cacheDir | .konfig/helm-cache | Helm.release during build/validate/diff, konfig helm fetch, konfig crd |
KONFIG_CRD_OUT_DIR | crd.outDir | .generated/crd | konfig crd extract |
KONFIG_CHARTS_DIR | charts | infra/k8s-konfig/charts | konfig helm fetch, konfig crd (chart registry directory) |
KONFIG_HELM_MIN_VERSION | helm.minVersion | 3.16.0 | konfig helm fetch, konfig crd. Helm.release checks the binary version only when you pass minVersion in its options. |
Project layout
konfig.ts does not prescribe a tree, but examples/full-stack and the defaults assume this shape (the example itself has no images.json):
konfig.json # "root": "."images.json # { "envs": { "<env>": { "<app>": "<image ref>" } } }, edited by `konfig set`, lives at <root>/images.jsoninfra/ cluster.ts # cluster-wide constants (domain, repositoryUrl, per-cluster overlays) envs/ prod.ts # default export: AppOfApps.fromModules({ ... }) staging.ts modules/ postgres.ts # one Module.fixedNs / Module.dynamicNs per Application api.ts secrets/ SopsSecret-db-creds.yaml # encrypted secret sources read by the sops backend.generated/manifests/ # outDir.manifests, one directory per env.konfig/ # cache/ (build cache, next to konfig.json) and helm-cache/, ignore in gitTwo conventions matter. Each env file’s default export must be an AppOfApps.fromModules({ ... }) program, because that is what the CLI loads and runs. Each module file exports a module definition (Module.fixedNs or Module.dynamicNs) that the env file instantiates with a name, an Argo CD source, and its options.
Runtime requirements
konfig build, validate, and most other commands import() your .ts sources, so the CLI needs a runtime that executes TypeScript directly.
| Requirement | Needed for | Notes |
|---|---|---|
| Bun | all commands | Recommended. Runs .ts without flags. |
| Node >= 22.18 or >= 23.6 | all commands | Native type stripping, no flags. The packages declare engines.node >= 22. |
| Node 22.6 to 22.17 | all commands | Run with --experimental-strip-types. |
tsx | all commands | tsx node_modules/.bin/konfig build prod. |
helm (3.16.0 or newer for konfig helm fetch and konfig crd) | modules that use Helm.release, konfig helm fetch, konfig crd | Called as helm pull and helm template. Helm.release enforces a minimum only when given minVersion. |
kubeconform | konfig validate --strict | Must be on PATH. |
sops / kubeseal | Sops.backend (sops --decrypt), SealedSecrets backend (kubeseal) | Sops.passthrough reads a pre-encrypted file and needs no binary. |
Next
Your first environment walks through one module, one env file, and the first konfig build.