DEV Community

Beyond Kustomize and Helm: Managing Kubernetes Manifests as Pure Data with Hull

If you’ve spent enough time managing Kubernetes deployments, you’ve likely hit the configuration wall. You start with standard YAML, realize you need different values for dev and prod, and reach for Kustomize or Helm.

Fast forward six months, and you are trapped in one of two nightmares: either you are maintaining a sprawling directory tree of overlays/ just to patch a single image tag in Kustomize, or you are deeply nested in Helm template spaghetti, debugging a malformed manifest because a Go-template {{ if }} block missed a single whitespace indentation.

Kubernetes manifests are structured data. Yet, our standard industry tools either treat them as raw text strings to be chopped up (Helm) or as rigid objects requiring layers of external patch files (Kustomize).

Hull is an open-source Kubernetes package manager that takes a completely different approach: it treats YAML as a native AST (Abstract Syntax Tree), utilizes clean inline expression templating, and centralizes environment configuration to eliminate both Kustomize overlays and Helm text templating.

Here is a hands-on look at how to build a multi-environment deployment without the traditional boilerplate.

1. Getting Started

Hull is a single Go binary. You can install it directly via:

go install github.com/ebogdum/hull/cmd/hull@latest

Enter fullscreen mode Exit fullscreen mode

Let's scaffold a new package to see how the architecture compares:

hull create my-api && cd my-api

Enter fullscreen mode Exit fullscreen mode

This generates three core components:

  • hull.yaml: The orchestrator (defines environments, inheritance, and dependencies).
  • values.yaml: Your default configuration schema.
  • templates/: The actual Kubernetes manifests.

2. Centralizing Environments (Killing Kustomize Overlays)

In Kustomize, setting up dev and prod requires building out a directory tree with a base/ directory, an overlays/dev/ directory, and an overlays/prod/ directory—each containing its own kustomization.yaml and distinct patch files.

In Hull, this environment topology lives entirely within a single orchestrator file: hull.yaml.

apiVersion: hull/v1
name: my-api
version: 1.0.0

environments:
  dev:
    namespace: api-dev
    values:
      replicas: 1
      image:
        tag: "latest"

  prod:
    # Inheriting dev pulls in its configuration as a baseline.
    inherits: dev
    namespace: api-prod
    values:
      replicas: 3
      image:
        tag: "v1.4.2"

Enter fullscreen mode Exit fullscreen mode

Because prod explicitly inherits from dev, you only define the keys that actually change. When you target an environment, Hull resolves these blocks deterministically as data before rendering anything.

3. Inline Variables (Killing Helm Template Spaghetti)

Helm forces you to wrap text strings in Go-template syntax, which easily breaks indentation or fails silently if an object isn't completely defined. Hull evaluates expressions directly against the parsed YAML tree using ${...} syntax.

Open templates/deployment.yaml to see how clean the context injection looks:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${name}
spec:
  replicas: ${replicas}
  selector:
    matchLabels:
      app: ${name}
  template:
    metadata:
      labels:
        app: ${name}
    spec:
      containers:
        - name: app
          image: ${image.repository}:${image.tag}
          ports:
            - containerPort: 8080

          # Control flow is handled natively as actual YAML keys
          $if: ${features.cacheEnabled}
          env:
            - name: REDIS_HOST
              value: redis-cache

Enter fullscreen mode Exit fullscreen mode

Because $if is a native YAML key and the ${...} expressions are evaluated safely against the parsed tree, your output is structurally guaranteed to be valid YAML. You completely avoid the risk of emitting broken whitespace or malformed text blocks.

4. Debugging the Configuration Spaghetti

One of the hardest parts of layered configuration in both Helm and Kustomize is figuring out where a final value actually originated. Did replicas get set by the base chart defaults, an inherited value file, a specific environment override, or a runtime CLI flag?

Hull includes a trace command built specifically to map out this resolution path:

hull values . --env prod --trace

Enter fullscreen mode Exit fullscreen mode

The output points directly to the exact file or structural block that won the evaluation:

replicas: 3                     # source: environments.prod.values (hull.yaml)
image:
  repository: myorg/webapp      # source: values.yaml (package default)
  tag: v1.4.2                   # source: environments.prod.values (hull.yaml)

Enter fullscreen mode Exit fullscreen mode

5. Native State Tracking and Drift Detection

Kustomize relies entirely on piping raw text output to kubectl apply, meaning it has no concept of what is actually happening inside your cluster over time. Helm tracks state using internal Secrets, but doesn't natively reconcile changes made out-of-band.

Hull acts as a full deployment engine that actively tracks cluster state. Install your package directly with:

hull install my-api . --env prod --create-namespace

Enter fullscreen mode Exit fullscreen mode

Hull stamps resources with strict ownership metadata (managedBy=hull) and stores the exact rendered manifest. If a developer later bypasses GitOps or CI/CD and manually modifies the cluster—such as running kubectl scale deploy my-api --replicas=10—you can catch it instantly:

hull drift my-api

Enter fullscreen mode Exit fullscreen mode

Hull evaluates the live cluster state against the stored release and reports the precise structural diff. To bring the cluster back to your declared state, a single command handles it:

hull reconcile my-api

Enter fullscreen mode Exit fullscreen mode

The Takeaway

If you are tired of maintaining complex, deep directory structures in Kustomize or fighting whitespace bugs in Helm, Hull offers a clean, data-driven architecture. By replacing text-replacement and patch-files with native YAML AST handling and deterministic environment inheritance, it drastically simplifies how multi-environment Kubernetes setups are managed.

The project is fully open-source under the MIT license. Explore the documentation, OCI distribution features, and GitOps workflows over on GitHub: ebogdum/hull.

Top comments (0)