Write your config in a real programming language.
An inventory is a program. You run it, and it builds one big JSON-compatible data structure — your whole config, assembled dynamically. From there hexol renders it to whatever you need: Kubernetes manifests, Terraform JSON, Ansible playbooks, SQL, plain YAML.
Hexol is both:
- a library — the Scheme you write inventories with, plus target libraries (Kubernetes, Terraform, …) layered on top, and
- a CLI — to act on an inventory:
renderit,applyit,inspectwhat it produced.
An inventory is a program that builds your config. Here's a Kubernetes one
(examples/kubernetes.scm):
(use-modules (hexol k8s))
(hx-ops
(with-namespace "loulou"
;; A Deployment (+ its Service). Its ConfigMap and Secret are pulled in by
;; name — one becomes `envFrom`, the other a mounted volume. Positional
;; name, then `(key value)` entries; values are evaluated Scheme.
(app "loulou"
(image "secure.io/loulou:2.1") (port 9000) (replicas 2)
(env-from (cm "loulou-config"))
(volumes (mount (sec "loulou-secret") "/etc/loulou/secret"))
(resources "200m-*/256Mi"))
;; The config the Deployment above consumes — ordinary Scheme data.
(configmap "loulou-config"
(data (LOG_LEVEL "debug") (CACHE_SIZE "256")))
(secret "loulou-secret"
(data (DB_PASSWORD "bG91bG91LXNlY3JldA=="))))
;; Cross-cutting pass: hash every Deployment's referenced ConfigMaps and
;; Secrets, and stamp the result onto the pod template as an annotation —
;; so a config change forces a rollout. No templating, just code.
(checksum-config))A few things this buys you over plain manifests:
- The Secret is attached to the Deployment without hand-writing the
volumesandvolumeMountssections —(mount …)wires both ends. "200m-*/256Mi"expands to the fullresourcesblock (CPU request200m, memory request + limit256Mi).checksum-configruns after every resource is built, finds each Deployment's ConfigMaps and Secrets, hashes them, and stamps aconfig/checksumannotation onto the pod template — so a config change forces a rollout, for every workload, for free. In Helm you'd hand-write asha256sumper Deployment.
Status: 0.1.0 (hexol --version); see CHANGELOG.md for
what's in it and what's coming.
Hexol runs on Guile 3.x and needs two
Guile libraries: guile-json (the (json) module) and guile-libyaml
(the (yaml) module). It also uses jq. All of these are declared in
manifest.scm, which is the source of truth for dependencies.
Three ways to get them:
From source — install Guile 3.x, guile-json, guile-libyaml, and jq however your distro provides them (or let Guix read the manifest), then run from the checkout:
git clone https://github.com/Polyedre/hexol && cd hexol
./bin/hexol render -i examples/kubernetes.scm # auto-compiles on first use
guix shell -m manifest.scm -- ./bin/hexol render -i examples/kubernetes.scm # with Guix(The repo's .envrc runs the guix shell for you under
direnv.)
Container — an OCI image built from Containerfile
(make image builds it locally); mount your inventories in:
podman run --rm -v "$PWD:/w" -w /w ghcr.io/polyedre/hexol render -i examples/kubernetes.scmNix — flake.nix packages the CLI and provides a dev shell:
nix run github:Polyedre/hexol -- render -i examples/kubernetes.scm
nix develop github:Polyedre/hexol # guile + deps in a shellThe CLI itself shells out to nothing. Individual features do, and only when you
use them: helm/yq to expand charts, sops for inline secrets, and
tofu/kubectl/talosctl for apply. Install whichever you need.
./bin/hexol render -o json -i examples/kubernetes.scm
# act on it (appliers an inventory registers via `applies-with`)
./bin/hexol apply --list -i examples/kubernetes.scm # show the applier pipeline
./bin/hexol apply --dry-run -i examples/kubernetes.scm # delegate to each tool's dry-run
./bin/hexol diff -i examples/kubernetes.scm # drift vs the world: exit 0 clean, 1 drift, 2 error
./bin/hexol diff --explain -i examples/kubernetes.scm # each changed field + the op that set it (kubectl)
./bin/hexol render -o yaml --validate -i examples/kubernetes.scm # pipe the stream through kubeconform (if on PATH)
# introspect: rendering is not opaque
./bin/hexol tree -i examples/kubernetes.scm # op tree (with hashes)
./bin/hexol show OP_HASH -i examples/kubernetes.scm # one op: source + delta
./bin/hexol explain regions.alpha5.network.cni -i examples/inventory.scm # what touched a path
# discover the library: which (key value) entries a construct accepts
./bin/hexol doc # constructs of the preloaded libs (k8s, sql)
./bin/hexol doc -i examples/terraform.scm # what THAT inventory registers
./bin/hexol doc app -i examples/kubernetes.scm # one construct: fields + exampleMigrating. You don't rewrite what you already have — you wrap it.
hexol import -f manifests.yaml > app.scm turns a k8s manifest stream (or a
kubectl get -o yaml dump; server-populated fields are stripped) into an
inventory with one (resource …) per document that renders back to the same
YAML; --sugar lifts objects to the typed constructs (deployment,
service, configmap, …) wherever they fit exactly. hexol import -f main.tf.json --from terraform does the same for a Terraform JSON config
(HCL text is out of scope). From there you refactor at your own pace. See
docs/authoring.md.
While the kernel is target-agnostic, the library provide a few syntaxic sugar helpers:
- Kubernetes — Deployments, Services, ConfigMaps, Secrets, and
cross-cutting passes like
checksum-config. Render to a manifest stream with-o yaml. - Terraform — providers, resources, and data sources as Scheme, rendered to
terraform init-ready JSON with-o terraform. Seeexamples/terraform.scm. - Ansible — plays and tasks accumulated into a playbook, rendered with
-o ansible. Seeexamples/ansible.scm. - Secrets (SOPS) — secrets live inline in the inventory, encrypted at rest
with sops: no separate
*.sops.yamlfiles to keep in sync.(secret-ref 'key)is a cheap marker, so onlyrendershells out to sops; manage the store with thehexol secretsubcommands.examples/secrets.scmis self-contained — it ships a throwaway age key, sohexol render -o yaml -i examples/secrets.scmdecrypts and substitutes real plaintext on a fresh clone. See alsoexamples/homelab.scmanddocs/authoring.md.
Inventories can also register their own renderers via renders-with — the
database-schema.scm and
ledger.scm examples add -o sql and -o ledger that
way.
How is this different from Pulumi, CDK, jsonnet, or Dhall?
Those build your config in memory and hand it off. Hexol keeps the build
itself around: resolving an inventory is a chain of small labeled steps over a
growing data structure, and every value remembers the step that set it — its
source, its file and line, the exact change it made. So you can point at any
value in the output and ask "what set this, and to what?" — tree, show, and
explain answer it. No other config tool does that.
Is this a serious Kubernetes/Terraform tool, or a Scheme demo?
The Kubernetes and Terraform libraries are the real ones —
examples/homelab.scm deploys a 3-node Talos cluster to
OVH for real, end to end. The SQL and Ledger renderers are tiny example-file
extensions, there to show the kernel doesn't care what it's building. Take them
as proof the engine is general, not as products.
Is it production-ready?
It's young: one author, no outside production users. It has a test suite
(make test, run on every push and PR via GitHub Actions) and it does run a
live homelab. Kick the tires before you bet a cluster on it.
docs/model.md— the engine model and its rationale.docs/authoring.md— writing inventories: surface forms, helpers, file splitting, ordering, and the repository layout.docs/extending.md— building target libraries, the kernel/library/example boundary, worked Terraform and Helm conversions, and introspection.- The event-sourced CMDB prototype lives on the
cmdbbranch.
Copyright (C) 2026 Polyedre. GNU General Public License v3.0 or later; see
LICENSE for the full text.