<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bogdan “Bodo” Dumitrescu</title>
    <description>The latest articles on DEV Community by Bogdan “Bodo” Dumitrescu (@bogdan_bododumitrescu_).</description>
    <link>https://dev.to/bogdan_bododumitrescu_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3308041%2Fc8a4cfd4-9324-4f4a-a992-fef9a149fb05.jpg</url>
      <title>DEV Community: Bogdan “Bodo” Dumitrescu</title>
      <link>https://dev.to/bogdan_bododumitrescu_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bogdan_bododumitrescu_"/>
    <language>en</language>
    <item>
      <title>Managing Kubernetes Manifests as Pure Data with Hull</title>
      <dc:creator>Bogdan “Bodo” Dumitrescu</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:17:39 +0000</pubDate>
      <link>https://dev.to/bogdan_bododumitrescu_/beyond-kustomize-and-helm-managing-kubernetes-manifests-as-pure-data-with-hull-56m6</link>
      <guid>https://dev.to/bogdan_bododumitrescu_/beyond-kustomize-and-helm-managing-kubernetes-manifests-as-pure-data-with-hull-56m6</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Fast forward six months, and you are trapped in one of two nightmares: either you are maintaining a sprawling directory tree of &lt;code&gt;overlays/&lt;/code&gt; 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 &lt;code&gt;{{ if }}&lt;/code&gt; block missed a single whitespace indentation.&lt;/p&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ebogdum/hull" rel="noopener noreferrer"&gt;Hull&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;Here is a hands-on look at how to build a multi-environment deployment without the traditional boilerplate.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Getting Started
&lt;/h2&gt;

&lt;p&gt;Hull is a single Go binary. You can install it directly via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/ebogdum/hull/cmd/hull@latest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's scaffold a new package to see how the architecture compares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hull create my-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-api

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates three core components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hull.yaml&lt;/code&gt;: The orchestrator (defines environments, inheritance, and dependencies).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;values.yaml&lt;/code&gt;: Your default configuration schema.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;templates/&lt;/code&gt;: The actual Kubernetes manifests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Centralizing Environments (Killing Kustomize Overlays)
&lt;/h2&gt;

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

&lt;p&gt;In Hull, this environment topology lives entirely within a single orchestrator file: &lt;code&gt;hull.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hull/v1&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-api&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;

&lt;span class="na"&gt;environments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-dev&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latest"&lt;/span&gt;

  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Inheriting dev pulls in its configuration as a baseline.&lt;/span&gt;
    &lt;span class="na"&gt;inherits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-prod&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1.4.2"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  3. Inline Variables (Killing Helm Template Spaghetti)
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;${...}&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;templates/deployment.yaml&lt;/code&gt; to see how clean the context injection looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${name}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${replicas}&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${name}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${name}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${image.repository}:${image.tag}&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;

          &lt;span class="c1"&gt;# Control flow is handled natively as actual YAML keys&lt;/span&gt;
          &lt;span class="na"&gt;$if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${features.cacheEnabled}&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-cache&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;$if&lt;/code&gt; is a native YAML key and the &lt;code&gt;${...}&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Debugging the Configuration Spaghetti
&lt;/h2&gt;

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

&lt;p&gt;Hull includes a trace command built specifically to map out this resolution path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hull values &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--env&lt;/span&gt; prod &lt;span class="nt"&gt;--trace&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output points directly to the exact file or structural block that won the evaluation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;                     &lt;span class="c1"&gt;# source: environments.prod.values (hull.yaml)&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/webapp&lt;/span&gt;      &lt;span class="c1"&gt;# source: values.yaml (package default)&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.4.2&lt;/span&gt;                   &lt;span class="c1"&gt;# source: environments.prod.values (hull.yaml)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Native State Tracking and Drift Detection
&lt;/h2&gt;

&lt;p&gt;Kustomize relies entirely on piping raw text output to &lt;code&gt;kubectl apply&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Hull acts as a full deployment engine that actively tracks cluster state. Install your package directly with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hull &lt;span class="nb"&gt;install &lt;/span&gt;my-api &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--env&lt;/span&gt; prod &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hull drift my-api

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hull reconcile my-api

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The project is fully open-source under the MIT license. Explore the documentation, OCI distribution features, and GitOps workflows over on GitHub: &lt;strong&gt;&lt;a href="https://github.com/ebogdum/hull" rel="noopener noreferrer"&gt;ebogdum/hull&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Zephyr Events – A 2KB TypeScript event emitter that's race-condition safe</title>
      <dc:creator>Bogdan “Bodo” Dumitrescu</dc:creator>
      <pubDate>Tue, 07 Apr 2026 22:37:14 +0000</pubDate>
      <link>https://dev.to/bogdan_bododumitrescu_/zephyr-events-a-2kb-typescript-event-emitter-thats-race-condition-safe-4ng2</link>
      <guid>https://dev.to/bogdan_bododumitrescu_/zephyr-events-a-2kb-typescript-event-emitter-thats-race-condition-safe-4ng2</guid>
      <description>&lt;p&gt;I built a tiny event emitter that fixes a bug most people don't know they have: if a handler calls off() on itself during emit, the next handler gets skipped. EventEmitter3, Node's built-in EventEmitter, and mitt all have this problem.&lt;/p&gt;

&lt;p&gt;Zephyr Events uses snapshot-based iteration so handlers can subscribe, unsubscribe, or clear listeners mid-emit without side effects. If you don't need that safety, there's a zephyrEventsFast mode that's up to 82% faster.&lt;/p&gt;

&lt;p&gt;1.9KB, zero dependencies, tree-shakeable&lt;br&gt;
Full TypeScript generics with strict handler signatures&lt;br&gt;
Wildcard listeners, shared handler maps, unsubscribe functions&lt;br&gt;
ESM, CommonJS, and UMD builds&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;zephyrEvents&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zephyr-events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user:login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zephyrEvents&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user:login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Welcome, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user:login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;unsub&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.npmjs.com/package/zephyr-events" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/zephyr-events&lt;/a&gt;&lt;br&gt;
Would love feedback, especially on the API design and whether the safe/fast split makes sense as a default.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>showdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Stop Wrestling with Config Files: A DevOps Guide to Sanity with Konfigo</title>
      <dc:creator>Bogdan “Bodo” Dumitrescu</dc:creator>
      <pubDate>Mon, 30 Jun 2025 07:53:57 +0000</pubDate>
      <link>https://dev.to/bogdan_bododumitrescu_/stop-wrestling-with-config-files-a-devops-guide-to-sanity-with-konfigo-1hh3</link>
      <guid>https://dev.to/bogdan_bododumitrescu_/stop-wrestling-with-config-files-a-devops-guide-to-sanity-with-konfigo-1hh3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvezvfb059jb13wal4i2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvezvfb059jb13wal4i2f.png" alt="Konfigo" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a DevOps engineer, you've probably felt the pain of managing configuration files. You've got JSON, YAML, TOML, &lt;code&gt;.env&lt;/code&gt; files, and maybe even some custom formats you'd rather not talk about. You're juggling configs for different environments (dev, staging, prod), and trying to keep everything in sync is a nightmare. What if I told you there's a better way?&lt;/p&gt;

&lt;p&gt;Enter Konfigo, a powerful command-line tool that's about to become your new best friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Konfigo? 🤔
&lt;/h2&gt;

&lt;p&gt;Konfigo is a versatile configuration management tool that helps you streamline your entire configuration workflow. It reads various configuration file formats, merges them intelligently, and processes the combined data against a user-defined schema for validation, transformation, and even batch output generation.&lt;/p&gt;

&lt;p&gt;Think of it as a Swiss Army knife for your configuration files. 🇨🇭&lt;/p&gt;

&lt;p&gt;Here are some of the key features that make Konfigo a game-changer for DevOps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Multi-Format Support:&lt;/strong&gt; JSON, YAML, TOML, and &lt;code&gt;.env&lt;/code&gt; files are all supported. No more converting files by hand!&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Flexible Merging:&lt;/strong&gt; Intelligently merges multiple configuration sources, respecting order and immutability rules.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Powerful Schema Processing:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Variable Substitution:&lt;/strong&gt; Inject dynamic values from environment variables, dedicated variable files, or schema defaults.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Generation:&lt;/strong&gt; Create new configuration values (e.g., &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;random&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Transformation:&lt;/strong&gt; Modify keys and values (e.g., &lt;code&gt;renameKey&lt;/code&gt;, &lt;code&gt;changeCase&lt;/code&gt;, &lt;code&gt;addKeyPrefix&lt;/code&gt;, &lt;code&gt;addKeySuffix&lt;/code&gt;, &lt;code&gt;deleteKey&lt;/code&gt;, &lt;code&gt;trim&lt;/code&gt;, &lt;code&gt;replaceKey&lt;/code&gt;, &lt;code&gt;setValue&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Validation:&lt;/strong&gt; Enforce rules (&lt;code&gt;required&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, &lt;code&gt;minLength&lt;/code&gt;, &lt;code&gt;enum&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Batch Processing:&lt;/strong&gt; Use the &lt;code&gt;konfigo_forEach&lt;/code&gt; directive in a variables file to generate multiple tailored configuration outputs from a single schema and run.&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Environment Variable Integration:&lt;/strong&gt; Override any configuration value directly using environment variables.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Should a DevOps Person Care? 🤷‍♀️
&lt;/h2&gt;

&lt;p&gt;Okay, so Konfigo has a lot of features. But how does it actually make your life easier?&lt;/p&gt;

&lt;h3&gt;
  
  
  Tame the Multi-Headed Hydra of Configuration Formats
&lt;/h3&gt;

&lt;p&gt;Let's say you have a base configuration in YAML, but your production environment requires some overrides from a &lt;code&gt;.env&lt;/code&gt; file. With Konfigo, you can merge them with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;konfigo &lt;span class="nt"&gt;-s&lt;/span&gt; base.yaml,prod.env &lt;span class="nt"&gt;-oy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more writing custom scripts to parse and merge different formats. Konfigo handles it all for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate Your Configuration Workflow
&lt;/h3&gt;

&lt;p&gt;You can integrate Konfigo into your CI/CD pipelines to generate environment-specific configurations on the fly. For example, you can have a base configuration and then apply environment-specific overrides from different files.&lt;/p&gt;

&lt;p&gt;Here's a conceptual example of how you might use Konfigo in a CI/CD pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate production config&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;konfigo -s config.yaml,overrides/production.yaml -of generated/config.production.json&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh generated/config.production.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prevent Configuration Drift
&lt;/h3&gt;

&lt;p&gt;Configuration drift is a major source of headaches in any infrastructure. With Konfigo's schema validation, you can enforce a consistent structure and set of rules for your configurations.&lt;/p&gt;

&lt;p&gt;For example, you can create a schema that requires a specific key to be present, or that a value must be a number within a certain range. If a configuration doesn't match the schema, Konfigo will throw an error, and you can catch the problem before it ever reaches production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Configurations are Your Friend
&lt;/h3&gt;

&lt;p&gt;Stop hardcoding values in your configuration files! With Konfigo, you can use variables and data generation to create dynamic configurations.&lt;/p&gt;

&lt;p&gt;For example, you can use an environment variable to set the database host, or you can use the &lt;code&gt;timestamp&lt;/code&gt; generator to add a build timestamp to your configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;konfigo &lt;span class="nt"&gt;-s&lt;/span&gt; config.json &lt;span class="nt"&gt;-S&lt;/span&gt; schema.yml &lt;span class="nt"&gt;-V&lt;/span&gt; staging-vars.yml &lt;span class="nt"&gt;-of&lt;/span&gt; staging_config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simplify Complex Setups
&lt;/h3&gt;

&lt;p&gt;If you're managing configurations for a microservices architecture or a multi-tenant application, you know how complex it can get. Konfigo's batch processing feature can help you simplify this.&lt;/p&gt;

&lt;p&gt;You can create a template configuration and then use a variables file to generate multiple tailored configurations for each service or tenant.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Example
&lt;/h3&gt;

&lt;p&gt;Let's look at a simple example of how Konfigo can be used to validate and transform a configuration file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input (&lt;code&gt;config.json&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature_flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"new_dashboard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Schema (&lt;code&gt;schema.yaml&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
          &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;min&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;
    &lt;span class="na"&gt;feature_flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;new_dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
  &lt;span class="na"&gt;transformations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;addKeyPrefix&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db_&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;konfigo &lt;span class="nt"&gt;-s&lt;/span&gt; config.json &lt;span class="nt"&gt;-S&lt;/span&gt; schema.yaml &lt;span class="nt"&gt;-oj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"db_database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feature_flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"new_dashboard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, Konfigo does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Validates&lt;/strong&gt; the input to ensure that &lt;code&gt;database.port&lt;/code&gt; is at least 1024.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transforms&lt;/strong&gt; the input by adding the prefix &lt;code&gt;db_&lt;/code&gt; to the &lt;code&gt;database&lt;/code&gt; key.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ready to Give it a Try? 🚀
&lt;/h2&gt;

&lt;p&gt;I've only scratched the surface of what Konfigo can do. If you're tired of wrestling with configuration files, I highly recommend giving it a try.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="https://github.com/ebogdum/konfigo/releases" rel="noopener noreferrer"&gt;Download Konfigo&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="https://ebogdum.github.io/konfigo/" rel="noopener noreferrer"&gt;Read the Docs&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know what you think in the comments below! 👇&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cli</category>
      <category>go</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
