Distributions¶
The high priest declared the engine too pure for mortal use, for it carried no doctrine, no creed, no opinion. And so he dressed it in vestments — converters for the faithful, rewriters for the heretics, indexers for the bureaucrats — and called the result a "distribution." The engine did not object. It had no opinions. That was the point.
— Book of Eibon, On the Dressing of Engines (verbatim, more or less)
What¶
A distribution is dekube-engine (the bare engine) + a set of bundled extensions, concatenated into a single Python script. The engine provides the pipeline, CLI, and extension loader; the distribution decides what's built in.
If you're reading this and thinking "this is the Kubernetes distribution model" — yes. A bare apiserver that does nothing without controllers, wrapped by a distribution (k3s, EKS, etc.) that bundles the defaults. I arrived here by solving engineering problems one at a time, each decision locally reasonable, and looked up to find I had recreated the architecture of the very thing I was trying to bring to the uninitiated. The concepts page has a longer meditation on this for those who enjoy watching someone realize what they've done.
dekube-engine + extensions = distribution
dekube.py helmfile2compose.py
stacking
helmfile2compose + more extensions = kubernetes2simple.py
Distributions can be stacked: built on top of another distribution instead of bare dekube-engine. kubernetes2simple stacks on helmfile2compose, adding all official extensions. The build script strips the tail (registries, sys.modules hack, __main__ guard) from the base, appends new extensions, and re-appends the tail.
Why¶
Because apparently one temple wasn't enough. Different organizations want different defaults. The helmfile2compose distribution bundles Caddy as the reverse proxy, HAProxy as the default rewriter, and SimpleWorkloadProvider for standard K8s kinds. A corporate distribution might bundle Traefik instead, add custom CRD handlers, or remove features that don't apply.
The distribution model avoids forking the core — everyone shares the same engine, just with different extensions wired in.
helmfile2compose — the reference distribution¶
helmfile2compose is the default distribution. It bundles 8 extensions:
| Repo | Type | File | Purpose |
|---|---|---|---|
| dekube-indexer-configmap | IndexerConverter | configmap_indexer.py |
Populates ctx.configmaps |
| dekube-indexer-secret | IndexerConverter | secret_indexer.py |
Populates ctx.secrets |
| dekube-indexer-pvc | IndexerConverter | pvc_indexer.py |
Populates ctx.pvc_names |
| dekube-indexer-service | IndexerConverter | service_indexer.py |
Populates ctx.services_by_selector |
| dekube-provider-simple-workload | Provider | workloads.py |
DaemonSet, Deployment, Job, StatefulSet → compose services |
| dekube-rewriter-haproxy | IngressRewriter | haproxy.py |
HAProxy annotations + default fallback |
| dekube-provider-caddy | IngressProvider | caddy.py |
Caddy service + Caddyfile generation |
| dekube-transform-fix-permissions | Transform | fix_permissions.py |
Fix bind mount permissions for non-root containers |
External extensions (loaded via --extensions-dir at runtime) work on top of whatever a distribution bundles.
Distribution repo structure¶
my-distribution/
├── distribution.json # manifest: base + extension names
├── .github/workflows/
│ └── release.yml # CI: dekube-manager fetch + build-distribution.py
└── README.md
distribution.json declares the base distribution and the list of extensions to bundle:
{
"base": "core",
"extensions": [
"configmap-indexer", "secret-indexer", "pvc-indexer", "service-indexer",
"workload", "haproxy", "caddy", "fix-permissions"
]
}
CI fetches dekube-manager and build-distribution.py, downloads extensions from their individual repos, and assembles the single-file script.
_auto_register() — how it works¶
After all code (core + extensions) is concatenated, the build script appends a call to _auto_register(). This function:
- Scans the caller's globals for classes
- Skips base classes (
Converter,Provider,IndexerConverter,IngressProvider,IngressRewriter) and_-prefixed names - Identifies converters (have
kinds+convert()), rewriters (havename+match()+rewrite()), and transforms (havetransform(), nokinds) - Checks for duplicate kind claims — fatal if two classes claim the same kind (
sys.exit(1)) - Sorts by priority and populates
_CONVERTERS,_REWRITERS,_TRANSFORMS,CONVERTED_KINDS
This means you just define classes in your extension files — no manual registration, no registry boilerplate.
Stacking distributions¶
A distribution can be built on top of another distribution instead of bare dekube-engine. The --base-distribution flag tells build-distribution.py to fetch the parent from GitHub releases (resolved via dekube-manager's distributions.json), strip its tail blocks, and use it as the base instead of dekube.py.
# Build kubernetes2simple on top of helmfile2compose
python build-distribution.py kubernetes2simple \
--extensions-dir .dekube/extensions \
--base-distribution helmfile2compose
# Or use a local .py file as base
python build-distribution.py kubernetes2simple \
--extensions-dir .dekube/extensions \
--base ../helmfile2compose/helmfile2compose.py
The --base-distribution flag defaults to core (bare dekube-engine). When set to another distribution name, build-distribution.py looks it up in dekube-manager's distributions.json, downloads the .py release artifact, and uses it as the base. --base-version pins a specific version (default: latest).
--base takes a local .py file path directly — useful for local dev when you have the parent distribution already built.
CI workflow example¶
Standard distribution (against dekube-engine)¶
name: Release
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fetch build-distribution.py
run: |
curl -fsSL https://raw.githubusercontent.com/dekubeio/dekube-engine/main/build-distribution.py \
-o build-distribution.py
- name: Build distribution
run: python3 build-distribution.py helmfile2compose --extensions-dir extensions
- name: Upload release assets
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.event.release.tag_name }}" helmfile2compose.py
Stacked distribution (against another distribution)¶
- name: Build kubernetes2simple.py
run: |
python build-distribution.py kubernetes2simple \
--extensions-dir .dekube/extensions \
--base-distribution helmfile2compose
Dev workflow¶
# Build locally (reads core sources from sibling checkout)
cd my-distribution
python ../dekube-engine/build-distribution.py helmfile2compose \
--extensions-dir extensions --core-dir ../dekube-engine
# → helmfile2compose.py
# Build stacked distribution locally
cd kubernetes2simple
python ../dekube-engine/build-distribution.py kubernetes2simple \
--extensions-dir .dekube/extensions \
--base ../helmfile2compose/helmfile2compose.py
# → kubernetes2simple.py
# Test it
python helmfile2compose.py --from-dir /tmp/rendered --output-dir /tmp/out
See Build system for the full deep dive on how concatenation works, the sys.modules hack, and all the gotchas.