<?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: Gatt Geng</title>
    <description>The latest articles on DEV Community by Gatt Geng (@gatt_geng_ce6a8f5b3886c67).</description>
    <link>https://dev.to/gatt_geng_ce6a8f5b3886c67</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%2F3996021%2Fdc258edd-3a28-4562-812d-7ca3187ffccc.jpg</url>
      <title>DEV Community: Gatt Geng</title>
      <link>https://dev.to/gatt_geng_ce6a8f5b3886c67</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gatt_geng_ce6a8f5b3886c67"/>
    <language>en</language>
    <item>
      <title>HelmSharp: render Helm charts from .NET without shelling out to helm</title>
      <dc:creator>Gatt Geng</dc:creator>
      <pubDate>Mon, 22 Jun 2026 03:47:32 +0000</pubDate>
      <link>https://dev.to/gatt_geng_ce6a8f5b3886c67/helmsharp-render-helm-charts-from-net-without-shelling-out-to-helm-1gng</link>
      <guid>https://dev.to/gatt_geng_ce6a8f5b3886c67/helmsharp-render-helm-charts-from-net-without-shelling-out-to-helm-1gng</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built a .NET library that renders Helm charts and drives Kubernetes releases without shelling out to the &lt;code&gt;helm&lt;/code&gt; CLI. 129/129 templates across ingress-nginx, cert-manager, external-dns, podinfo, and metrics-server now render successfully. The main entry point is HelmSharp.Action, with lower-level packages available for chart loading, rendering, Kubernetes operations, and release storage. MIT licensed, looking for feedback and early adopters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;At work, our .NET services deploy to Kubernetes through Helm. Every Docker image had to bundle the &lt;code&gt;helm&lt;/code&gt; binary — another dependency to manage, another layer in the image, another surface for CVEs. I wanted to cut that out entirely and do Helm-style rendering directly in-process.&lt;/p&gt;

&lt;p&gt;The .NET ecosystem doesn't really have this. There are YAML libraries. There are Kubernetes client libraries. There are template engines. But nothing ties them together the way &lt;code&gt;helm template&lt;/code&gt; does — values merging, named templates, &lt;code&gt;include&lt;/code&gt;, &lt;code&gt;range&lt;/code&gt;, &lt;code&gt;toYaml&lt;/code&gt;, the whole Sprig function set, all wired into a single render pipeline. So I started building one. (This is also my first real open source project — I'd spent years consuming OSS without contributing back, and HelmSharp is what came out of deciding to change that.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What HelmSharp Does
&lt;/h2&gt;

&lt;p&gt;HelmSharp is a multi-package .NET SDK (net8.0 / net9.0 / net10.0) that covers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Action&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-level Helm client — &lt;code&gt;TemplateAsync&lt;/code&gt;, &lt;code&gt;UpgradeInstallAsync&lt;/code&gt;, &lt;code&gt;RollbackAsync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Chart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chart loading from directories and &lt;code&gt;.tgz&lt;/code&gt;, values merging, &lt;code&gt;--set&lt;/code&gt; / &lt;code&gt;--set-json&lt;/code&gt; style overrides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Engine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Helm-style template rendering — 100+ Sprig/Helm functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Kube&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes apply, delete, and wait (no &lt;code&gt;kubectl&lt;/code&gt; needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Release&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Release history stored in Kubernetes Secrets (Helm-compatible)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HelmSharp.Repo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chart repository index, pull, and search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plus &lt;code&gt;Registry&lt;/code&gt;, &lt;code&gt;Storage&lt;/code&gt;, &lt;code&gt;PostRenderer&lt;/code&gt; extension points&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the lower-level rendering API — no result objects, no stdout strings, just in-process template evaluation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HelmChartLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/charts/my-chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HelmValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.2.3"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"replicaCount"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HelmTemplateRenderer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;manifests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// manifests is a string of valid YAML — feed it to kubectl apply, store it,&lt;/span&gt;
&lt;span class="c1"&gt;// diff it, whatever you'd do with `helm template` output.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;helm&lt;/code&gt; binary. No &lt;code&gt;Process.Start&lt;/code&gt;. Values are native .NET objects, not &lt;code&gt;--set&lt;/code&gt; strings. The output is just a string — you decide what to do with it.&lt;/p&gt;

&lt;p&gt;There's also a higher-level &lt;code&gt;HelmClient&lt;/code&gt; API (&lt;code&gt;TemplateAsync&lt;/code&gt;, &lt;code&gt;UpgradeInstallAsync&lt;/code&gt;, &lt;code&gt;RollbackAsync&lt;/code&gt;) that mirrors the Helm CLI workflow if you prefer that shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where We Are Now (v1.0.3)
&lt;/h2&gt;

&lt;p&gt;This week I hit a milestone I'm genuinely proud of: &lt;strong&gt;129 out of 129 templates across 5 real-world public charts now render without a single parser exception.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chart&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Templates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ingress-nginx&lt;/td&gt;
&lt;td&gt;4.12.1&lt;/td&gt;
&lt;td&gt;42/42 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cert-manager&lt;/td&gt;
&lt;td&gt;1.17.1&lt;/td&gt;
&lt;td&gt;41/41 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;external-dns&lt;/td&gt;
&lt;td&gt;1.21.1&lt;/td&gt;
&lt;td&gt;7/7 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;podinfo&lt;/td&gt;
&lt;td&gt;6.14.0&lt;/td&gt;
&lt;td&gt;21/21 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics-server&lt;/td&gt;
&lt;td&gt;3.13.1&lt;/td&gt;
&lt;td&gt;18/18 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This was blocked by two parser bugs that took a while to track down — one in pipeline splitting where &lt;code&gt;|&lt;/code&gt; inside parenthesized expressions like &lt;code&gt;(empty .x)&lt;/code&gt; was incorrectly treated as a pipeline separator, and one in else-if chain reconstruction where C# string interpolation was silently producing single braces instead of double braces. The kind of bug where you stare at it for hours, then the fix is four characters.&lt;/p&gt;

&lt;p&gt;The test suite is at &lt;strong&gt;214 tests&lt;/strong&gt; across three target frameworks, with a golden-test harness that runs &lt;code&gt;helm template&lt;/code&gt; as the oracle and diffs the output document-by-document against HelmSharp's renderer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The project follows numbered milestones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;M1 (in progress):&lt;/strong&gt; Helm template parity — functions, whitespace, built-in objects, control flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M2:&lt;/strong&gt; Subchart and dependency resolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M3:&lt;/strong&gt; Release lifecycle (install/upgrade/rollback/uninstall with hook execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M4:&lt;/strong&gt; Kubernetes resource lifecycle hardening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M5:&lt;/strong&gt; OCI registry and provenance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M6:&lt;/strong&gt; API polish, XML docs, stable surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No hard dates — I'm pushing forward steadily (you can see the cadence in the &lt;a href="https://github.com/GaTTGeng/HelmSharp/commits/master" rel="noopener noreferrer"&gt;commit history&lt;/a&gt;). That said, if someone needs a specific feature, I'm happy to re-prioritize. Open source moves faster when real use cases drive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Looking For
&lt;/h2&gt;

&lt;p&gt;If you're writing a .NET Kubernetes operator, a CD pipeline, or any tooling that currently shells out to &lt;code&gt;helm&lt;/code&gt; — this is for you. &lt;code&gt;dotnet add package HelmSharp.Action&lt;/code&gt; and render one of your charts. If it works, great. If it breaks, I want to know exactly how — open an issue with the chart and template that failed, and I'll fix it.&lt;/p&gt;

&lt;p&gt;If you find it useful, a star is appreciated — but stars are earned, not asked for. What I really want is feedback, bug reports, and feature requests from people who actually deploy things to Kubernetes.&lt;/p&gt;

&lt;p&gt;Contributions are welcome too. The golden-test harness makes it easy to verify template fixes — add a chart fixture, run the test, see the diff.&lt;/p&gt;

&lt;p&gt;If you use Helm in .NET-based tooling today, what would you expect from a managed SDK like this?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/GaTTGeng/HelmSharp" rel="noopener noreferrer"&gt;github.com/GaTTGeng/HelmSharp&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;NuGet:&lt;/strong&gt; &lt;a href="https://www.nuget.org/packages/HelmSharp.Action/" rel="noopener noreferrer"&gt;nuget.org/packages/HelmSharp.Action&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>dotnet</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
