bkl is a templating configuration language without the templates. It's designed to be simple to read and write with obvious behavior.
Write your configuration in your favorite format: JSON, YAML, or TOML. Layer configurations on top of each other, even from different file formats. Use filenames to define the inheritance. Have as many layers as you like. bkl merges your layers together with sane default behavior that you can override. Export your results in any supported format for human or machine consumption. Use the CLI directly or in scripts or automate with the library.
$ bkl service.test.toml
addr = '127.0.0.1'
name = 'myService'
port = 8081
bkl knows that service.test.toml inherits from service.yaml by the filename pattern (override with $parent) and uses filename extensions to determine formats.
Specifying multiple input files merges all layers in order.
$ bkl -- -.yaml <<'EOF'
a: 1
EOF
Specifying an input file with the base name - and a valid format extension causes bkl to read that format from standard input. Use -- before the filename to avoid it being treated as a flag.
Inheritance is determined using filenames by default. After stripping the extension, the remaining filename is split on . and treated as an inheritance hierarchy (e.g. a.b.c.yaml inherits from a.b.<ext> inherits from a.<ext>). Parent layers may have any supported file extension.
$parent: a.b # inherits from a.b., from a.
bkl will check for all supported endings of a manually-specified parent file and will still evaluate layers under the parent in the normal order.
$parent:
- a
- b
$parent: a.*
Setting $parent to a list or wildcard allows inheriting from multiple files. All parent files are loaded before the child.
* does not match .. a.* matches a.b.yaml but not a.b.c.yaml.
Layer order can be specified with commandline argument order. The -P flag (or --skip-parent) tells bkl to not load parent layers using filenames or $parent.
$parent: false # no further inheritance
Setting $parent to false or null stops any further inheritance regardless of filename structure.
Streams package multiple documents into a single file. YAML/TOML streams are delimited with --- or sometimes +++. JSON streams are concatenated or delimited with newlines (see JSON Lines and ndjson).
To layer streams, bkl has to match documents between layers. By default, child documents are applied to all parent documents. $match allows applying to specific parent documents.
a: 1
---
b: 2
+
c: 3
=
a: 1
c: 3
---
b: 2
c: 3
a: 1
---
b: 2
+
$match:
b: 2
c: 3
=
a: 1
---
b: 2
c: 3
The supplied pattern can match multiple documents and the updates will be applied to all of them. $match: {} matches any documents with a map as their root element, regardless of parent.
a: 1
---
b: 2
---
a: 1
+
$match:
a: 1
c: 3
=
a: 1
c: 3
---
b: 2
---
a: 1
c: 3
$invert: true inside a $match block inverts the match, causing it to apply to any documents that do not contain the match criteria.
a: 1
---
b: 2
+
$match:
a: 1
$invert: true
c: 3
=
a: 1
---
b: 2
c: 3
$match: null forces the updates to apply to a new document.
Maps are merged by default. To change that, use $replace: true or remove individual entries with $delete.
a: 1
+
b: 2
=
a: 1
b: 2
a: 1
+
b: 2
$replace: true
=
b: 2
a: 1
b: 2
+
c: 3
b: $delete
=
a: 1
c: 3
bkl returns an error if you use $delete, $replace: true, or a key: value pair when they don't override a value from a lower layer. This helps keep upper layers minimal.
$repeat: at the top level of a document causes the document to be duplicated the given number of times. Within the document, $repeat can be used to reference the zero-based repeat index.
a: foo
b: $repeat$repeat: 2
=
a: foo
b: 0
---
a: foo
b: 1
$repeat: can also contain a map of named repeat iterators to repeat count. The result is the cartesian product (all combinations) of repeat values.
bklb is a wrapper for CLI programs that take configuration files as commandline arguments but do not support bkl format. It transparently merges layers, translates formats, writes to temporary files, alters the commandline arguments, then execs the wrapped program.
$ ln -s ~/go/bin/bklb ~/go/bin/catb # catb could be, e.g. kubectlb
$ cat service.yaml
addr: 127.0.0.1
name: myService
port: 8080
$ cat service.test.toml
port = 8081
$ catb service.test.toml
addr = "127.0.0.1"
name = "myService"
port = 8081
$ cat service.test.yaml service.test.json
cat: service.test.yaml: No such file or directory
cat: service.test.json: No such file or directory
$ catb service.test.yaml
addr: 127.0.0.1
name: myService
port: 8081
$ catb service.test.json
{"addr":"127.0.0.1","name":"myService","port":8081}
Note that the files mentioned don't have to exist; bklb will search for files with the same root name but different extensions, then merge layers and translate into the specified format.
bklb takes the name of the program it wraps from its own filename, hence the ln -s symlink creation in the example above. It trims at most one b from the end of its name before searching for the wrapped program so they can coexist in your PATH.
bkld (d for "diff") generates the minimal intermediate layer needed to create the target output from the base layer. Along with bkli, it automates splitting existing configurations into layers.
bkli (i for "intersect") generates the maximal base layer that the specified targets have in common. Along with bkld, it automates splitting existing configurations into layers.
Any fields that exist in all upper layers but have different values will be marked $required.
bklr (r for "required") generates a document containing just the required fields and their ancestors from the lower layer. The output can be edited into a minimal upper layer.
kubectl bkl is a kubectl plugin that wraps all the normal kubectl commands by evaluating any input files as bkl layers and passing the output to kubectl.
# deploy.dev.yaml and deploy.yaml are bkl layer files
$ kubectl bkl apply -f deploy.dev.yaml
deployment.apps/deploy-dev unchanged
Below is an example process for migrating an existing set of configurations to bkl. It contains some Kubernetes-specific items but the use of the bkl* tools applies to any configuration source.
We assume you start with two K8s deployments called deploy-dev and deploy-prod which are similar but not identical.
It's possible to export configuration directly from existing templating systems (e.g. kustomize build, helm install --dry-run --debug). Instead, we do this in a generic way and make sure we get the latest configuration by fetching it directly from the K8s API server.
# "kubectl neat" removes status and default value fields
$ kubectl neat get -- deploy deploy-dev > deploy-dev-orig.yaml
$ kubectl neat get -- deploy deploy-prod > deploy-prod-orig.yaml
# See bkli details here
$ bkli -o deploy.yaml deploy-dev-orig.yaml deploy-prod-orig.yaml
# See bkld details here
$ bkld -o deploy.dev.yaml deploy.yaml deploy-dev-orig.yaml
$ bkld -o deploy.prod.yaml deploy.yaml deploy-prod-orig.yaml
# Should show no diff
$ kubectl bkl diff -f deploy.dev.yaml
$ kubectl bkl diff -f deploy.prod.yaml
$ rm deploy-dev-orig.yaml deploy-prod-orig.yaml
You now have 3 bkl files:
deploy.yaml is the base layer containing values common to both deployments
deploy.dev.yaml is the upper layer containing values specific to deploy-dev
deploy.prod.yaml is the upper layer containing values specific to deploy-prod
The migration process isn't deterministic; there are design and aesthetic considerations. Here are some general tips:
Start with the most similar examples to maximize base layer size
Consider intermediate layers that make sense (e.g. deploy.yaml → deploy.frontend.yaml → deploy.frontend.prod.yaml)
Split large configurations into logical subfiles; bkl can merge across file boundaries
Iterate by diffing your evaluated layers against production (e.g. kubectl bkl diff -f), making small changes, then diffing again.
Migrate as much as possible to the lowest layer to reduce duplication and complexity.
Consider making your base layer match your production configuration then overriding values for dev/test configurations. This makes it very clear where you're drifting away from production.
Remove duplication with $merge: but avoid chained merge paths.
Avoid using hidden $output: false trees as template variables; prefer overriding values in-place.
bkld doesn't know how to match entries within lists, so it may remove and replace large entries (e.g. K8s containers) that could be trivially patched. Use $match: to select the container and override values within it.
bkl has some overlap with other configuration templating tools (e.g. Helm, Kustomize, Hiera). We believe that bkl has a combination of project goals that aren't fully served by any of the alternatives:
Configuration and templating/layering should be generic, not service-specific.
Configuration and templating/layering should be separate from deployment tooling.
Configuration files should be written in standard formats (JSON, YAML, TOML) and parseable by their standard parsers.
Basic functionality should be available without learning custom syntax.
Non-templatized configuration (e.g. StackOverflow answers) should be usable without modification.
File composition should be accomplished without meta configuration (e.g. manifest files).
Templating/layering behavior should be intuitive and produce expected results.