Skip to content

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:

  1. Scans the caller's globals for classes
  2. Skips base classes (Converter, Provider, IndexerConverter, IngressProvider, IngressRewriter) and _-prefixed names
  3. Identifies converters (have kinds + convert()), rewriters (have name + match() + rewrite()), and transforms (have transform(), no kinds)
  4. Checks for duplicate kind claims — fatal if two classes claim the same kind (sys.exit(1))
  5. 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.