<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>BigConfig | Blog</title><description/><link>https://www.bigconfig.it/</link><language>en</language><item><title>The Evolution of DevOps: From Separation by Technology to Separation by Concerns</title><link>https://www.bigconfig.it/blog/the-evolution-of-devops-from-separation-by-technology-to-separation-by-concerns/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/the-evolution-of-devops-from-separation-by-technology-to-separation-by-concerns/</guid><description>Drawing a parallel to the React revolution in frontend development, this article explores how shifting from Separation by Technology to Separation by Concerns can solve the modern infrastructure bottleneck. Learn how BigConfig uses a component-based approach to unify Kubernetes, Terraform, and Ansible, enabling modularity, interchangeability, and a &quot;clean interface&quot; for your entire stack.

</description><pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The current state of DevOps feels eerily similar to the dark ages of Frontend development before the &lt;a href=&quot;https://react.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; React &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; revolution.&lt;/p&gt;
&lt;p&gt;In the early days of the web, we practiced Separation by Technology. We kept our CSS in one folder, our HTML in another, and our JavaScript in a third. We were told this was clean, but in reality, it was a nightmare to maintain. To change a single button, you had to hunt through three different files in three different directories.&lt;/p&gt;
&lt;p&gt;React changed the game by introducing Separation by Concerns. It recognized that a Component is the natural unit of work. A component encapsulates its logic, structure, and styling into a single, cohesive module.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-modern-devops-bottleneck&quot;&gt;The Modern DevOps Bottleneck&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Today, DevOps is still stuck in the Separation by Technology phase. We segregate our logic by the tools we use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terraform/OpenTofu&lt;/strong&gt; for infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible&lt;/strong&gt; for configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt; for orchestration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Kubernetes has moved us toward immutability, it has also introduced the &lt;a href=&quot;https://www.bigconfig.it/blog/the-yaml-trap-escaping-greenspun-s-tenth-rule-with-bigconfig/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; YAML trap &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;. Furthermore, the real world isn’t always immutable. We don’t discard a developer’s machine every time we need a minor package upgrade, and stateful services (like databases) require delicate, mutable handling that pure-container strategies struggle to manage.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;enter-bigconfig-the-component-based-approach&quot;&gt;Enter BigConfig: The Component-Based Approach&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;BigConfig applies the React philosophy to DevOps. Instead of managing a Terraform repo and an Ansible repo you manage Packages.&lt;/p&gt;
&lt;p&gt;For example, the BigConfig Package &lt;a href=&quot;https://github.com/amiorin/once&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; once &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; utilize four Terraform projects and two Ansible projects under the hood, but to the user, it is a single, functional unit.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-coupling-problem-a-practical-example&quot;&gt;The Coupling Problem: A Practical Example&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Consider a simple task: checking if the the server can send emails. Ansible needs to create a &lt;code dir=&quot;auto&quot;&gt;.mailrc&lt;/code&gt; file so the operator can use &lt;code dir=&quot;auto&quot;&gt;s-nail&lt;/code&gt; CLI to test the SMTP connection during an incident.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Traditional Way:&lt;/strong&gt; The infrastructure (SMTP infrastructure) is defined in OpenTofu, but the &lt;code dir=&quot;auto&quot;&gt;.mailrc&lt;/code&gt; configuration lives in an Ansible folder. This creates strong coupling. If you change your SMTP provider or port, you must manually update two different repositories.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The BigConfig Way:&lt;/strong&gt; Responsibility is delegated, not segregated. The Tofu SMTP component is responsible for generating the configuration data, while Ansible is simply the delivery mechanism that places the file on the server. The file source path acts as a clean interface. One change in the SMTP component automatically flows through the system.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;modularity-and-referential-transparency&quot;&gt;Modularity and Referential Transparency&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The power of a Separation of Concerns architecture lies in its modularity. In the BigConfig Package once, we categorize providers into functional types: Compute, DNS, and SMTP.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Interchangeability:&lt;/strong&gt; Our Compute component has three implementations: OCI, Hetzner, and DigitalOcean. Because these are built as components, you can swap one for another without touching your DNS or SMTP logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Referential Transparency:&lt;/strong&gt; We utilize “no-infra” components as placeholders. This allows you to bring your own pre-existing infrastructure into the system. You can replace a live resource with its values without changing the behavior of the rest of the stack. In programming, this is called referential transparency; in DevOps, it makes brownfields easier to support.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Degradation of Experience:&lt;/strong&gt; Usually, abstractions make debugging harder. BigConfig is designed so that the composition of components doesn’t hide the underlying tools. You can manage the entire Composition as one, or drop down to debug a single component (like a specific Tofu component) without any additional configuration overhead.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We are moving past the era of “Tool-First” DevOps. By adopting a component-based mindset, we can stop managing scripts and start building systems that are as modular, testable, and maintainable as our modern frontend applications.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>package manager</category><category>bigconfig</category><category>once</category><category>separation by concerns</category><category>separation by technology</category><category>ansible</category><category>terraform</category><category>kubernetes</category><category>referential transparency</category><category>component</category></item><item><title>Vibe Coding Meets Vibe Ops: Automating the Last Mile of Deployment</title><link>https://www.bigconfig.it/blog/vibe-coding-meets-vibe-ops-automating-the-last-mile-of-deployment/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/vibe-coding-meets-vibe-ops-automating-the-last-mile-of-deployment/</guid><description>Vibe coding has revolutionized how we build apps, but deployment is still stuck in the manual ages. Discover how BigConfig&apos;s once bridges the gap, automating compute, DNS, and SMTP into a seamless pipeline that brings us one step closer to Natural Language Infrastructure.

</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Vibe coding has made building web applications faster and more intuitive than ever, but the Ops side of the house hasn’t quite kept up. While 37signals’ &lt;a href=&quot;https://github.com/basecamp/once&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; ONCE &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; simplified the deployment architecture for vibe coders, managing the underlying infrastructure—Compute, DNS, and SMTP—remains a fragmented, manual process.&lt;/p&gt;
&lt;p&gt;Enter the BigConfig &lt;a href=&quot;https://github.com/amiorin/once&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; once &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;: the missing link that automates the manual out of your deployment pipeline.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;zero-friction-infrastructure&quot;&gt;Zero-Friction Infrastructure&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;BigConfig turns complex infrastructure provisioning into a few simple terminal commands. No more hunting for IP addresses or manually editing SSH configs.&lt;/p&gt;
&lt;ol role=&quot;list&quot;&gt;
&lt;li&gt;Provision your VPS instantly
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/amiorin/once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;once&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Jump straight into your server (SSH config is auto-populated)
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Start ONCE TUI in your server to deploy your vibe coded application
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/once.CY_vO7LE_24MOfa.webp&quot; alt=&quot;once&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;2232&quot; height=&quot;1466&quot;&gt;
&lt;img src=&quot;https://www.bigconfig.it/_astro/apps.CDZctbMl_t0li1.webp&quot; alt=&quot;apps&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;4480&quot; height=&quot;2520&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-compute&quot;&gt;1. Compute&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;While &lt;a href=&quot;https://www.oracle.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Oracle &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and &lt;a href=&quot;https://www.hetzner.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Hetzner &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; are supported out of the box, BigConfig is built for extensibility. Adding a new compute provider is trivial, ensuring you aren’t locked into a single ecosystem.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-dns&quot;&gt;2. DNS&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cloudflare.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Cloudflare &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; integration is ready to go. Point your domain and let BigConfig handle the records, ensuring your application is reachable as soon as the compute instance is live.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-smtp&quot;&gt;3. SMTP&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Transactional email is handled via &lt;a href=&quot;https://resend.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Resend &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; by default. Like the other modules, swapping this for another SMTP service is simple and straightforward.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-vision-dev--ops-in-plain-english&quot;&gt;The Vision: Dev &amp;#x26; Ops in Plain English&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The roadmap for BigConfig is headed toward a Natural Language Infrastructure. The next milestone is teaching Claude Code to utilize the BigConfig &lt;a href=&quot;https://github.com/amiorin/once&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; once &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Soon, a vibe coder won’t just write code in English—they will manage their entire production environment through conversation, blurring the line between development and operations entirely.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;current-manual-hurdles&quot;&gt;Current Manual Hurdles&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We’ve automated the heavy lifting, but a few Human-in-the-Loop steps remain—mostly due to provider security and billing requirements. In the age of agentification, these hurdles may not last long:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain Registration:&lt;/strong&gt; Snagging budget domains (like &lt;code dir=&quot;auto&quot;&gt;.website&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;.online&lt;/code&gt; for $0.85) via &lt;a href=&quot;https://www.namecheap.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Namecheap &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; is still a manual checkout process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle Cloud Setup:&lt;/strong&gt; Creating an account requires a physical credit card. To unlock the Always Free tier (4 cores, 24 GB RAM), you must manually switch to a Pay-As-You-Go account and generate your API credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Namecheap-Cloudflare Bridge:&lt;/strong&gt; Because Namecheap doesn’t offer an API for smaller accounts, pointing your Namecheap domain to Cloudflare DNS and generating an API token remains a manual task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resend Setup:&lt;/strong&gt; Account creation and initial API key generation must be done via their web dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The gap between vibe coding a masterpiece and actually shipping it to a live URL is narrowing. By wrapping the fragmented worlds of VPS provisioning, DNS routing, and SMTP configuration into a unified, programmable workflow, BigConfig transforms deployment from a weekend chore into a minor detail. While a few manual gates like billing and domain registration remain—reminders of the physical world we still inhabit—the path toward a fully conversational, Natural Language Infrastructure is clear.&lt;/p&gt;
&lt;p&gt;We are rapidly approaching a reality where the Ops in DevOps is as effortless as the Dev. If you’re ready to stop wrestling with dashboards and start shipping at the speed of thought, it’s time to give the BigConfig a spin.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>package manager</category><category>bigconfig</category><category>once</category><category>vibe coding</category><category>vibe operations</category><category>hetzner</category><category>oci</category><category>oracle</category><category>resend</category><category>namecheap</category></item><item><title>The Power of Framing: Why BigConfig is Rebranding as a Package Manager</title><link>https://www.bigconfig.it/blog/the-power-of-framing-why-bigconfig-is-rebranding-as-a-package-manager/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/the-power-of-framing-why-bigconfig-is-rebranding-as-a-package-manager/</guid><description>BigConfig started as a modest script to simplify complex Terraform projects, but it has grown into something far more ambitious. By moving beyond the &quot;library&quot; mindset and embracing the role of a package manager for infrastructure, BigConfig overcomes the traditional barriers of language adoption. This article explores how a unified Clojure-based workflow—spanning the REPL, the Shell, and the Library—offers a total solution for developers tired of juggling fragmented YAML templates and rigid deployment schemas.

</description><pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BigConfig began as a simple &lt;a href=&quot;https://babashka.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Babashka &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; script designed to DRY up a complex &lt;a href=&quot;https://developer.hashicorp.com/terraform&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Terraform &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; project for a data platform. Since those humble beginnings, it has evolved through several iterations into a robust template and workflow engine. But as the tool matured, I realized that technical power wasn’t enough; the way it was framed was the true barrier to adoption.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-language-barrier-and-the-loophole&quot;&gt;The Language Barrier (and the Loophole)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;BigConfig is powerful as a library, but I’ve faced a hard truth: very few developers will learn a language like &lt;a href=&quot;https://clojure.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Clojure &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; just to use a library. However, history shows that developers will learn a new language if it solves a fundamental deployment problem.&lt;/p&gt;
&lt;p&gt;People learned Ruby to master &lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Homebrew &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;; they learn &lt;a href=&quot;https://nixos.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Nix &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; for reproducible builds. Meanwhile, tools like &lt;a href=&quot;https://helm.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Helm &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; force users to juggle the awkward marriage of YAML and Go templates—a “solution” many endure only because no better alternative exists. To get developers to cross the language barrier, you have to offer more than a tool; you have to offer a total solution.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-package-manager-epiphany&quot;&gt;The “Package Manager” Epiphany&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I noticed a significant shift in engagement depending on how I framed the project. When I describe BigConfig as a library, it feels abstract—like “more work” added to a developer’s plate. When I introduce it as a package manager, the interest is immediate.&lt;/p&gt;
&lt;p&gt;In the mind of a developer, a library is a component you have to manage. A package manager is the system that manages things for you. By shifting the perspective, BigConfig goes from being a “Clojure utility” to an “Infrastructure Orchestrator.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-bigconfig-differs&quot;&gt;How BigConfig Differs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Like Nix and &lt;a href=&quot;https://guix.gnu.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Guix &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
, BigConfig embraces a full programming language. However, it avoids the “two-language architecture” common in those ecosystems—where you often have a compiled language for the CLI and a separate interpreted one for the user.&lt;/p&gt;
&lt;p&gt;BigConfig is Clojure all the way down (in the spirirt of Emacs). This allows it to support three distinct environments seamlessly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The REPL:&lt;/strong&gt; For interactive development and real-time exploration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Shell:&lt;/strong&gt; For traditional CLI workflows and CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Library:&lt;/strong&gt; For embedding directly into your own control planes or APIs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Beyond the language, BigConfig introduces robust client-side coordination, featuring an Atlantis-style locking mechanism that uses GitHub tags to prevent developer collisions in shared environments.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;limitless-abstraction&quot;&gt;Limitless Abstraction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The level of abstraction is where BigConfig truly shines. When you adopt the system, you aren’t locked into a rigid schema; you can adapt the entire engine to your specific needs. Complex tasks—like deploying the same architecture across different hyperscalers—are reduced from massive refactors to simply updating a property. It moves the conversation from &lt;em&gt;how&lt;/em&gt; to deploy to &lt;em&gt;what&lt;/em&gt; to deploy.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-roadmap&quot;&gt;The Roadmap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The next phase is focused on expanding the ecosystem and making package discovery seamless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hyperscaler Support:&lt;/strong&gt; Having already added &lt;a href=&quot;https://www.digitalocean.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; DigitalOcean &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
, &lt;a href=&quot;https://www.hetzner.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Hetzner &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
, and &lt;a href=&quot;https://www.oracle.com/cloud/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Oracle Cloud &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
, I am now prioritizing &lt;a href=&quot;https://aws.amazon.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; AWS &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, &lt;a href=&quot;https://cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Google Cloud &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
, and &lt;a href=&quot;https://azure.microsoft.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Azure &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Packages:&lt;/strong&gt; While the first “app”—a remote development environment—is a niche use case, I’m expanding into high-demand stacks like &lt;a href=&quot;https://airflow.apache.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Airflow &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
and &lt;a href=&quot;https://redplanetlabs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Rama &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Ecosystem:&lt;/strong&gt; I am currently defining the formal package manifest and building a registry where users can discover, version, and publish their own infrastructure packages.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The evolution of BigConfig is a testament to the idea that the right abstraction is just as important as the right code. By reframing the tool from a utility you have to manage into a system that manages for you, we bridge the gap between complex cloud resources and developer productivity.&lt;/p&gt;
&lt;p&gt;As we expand our hyperscaler support and formalize our package registry, the goal remains the same: to move infrastructure management away from the “how” and toward the “what.” Whether you are deploying a niche remote environment or a massive data stack like a Data Lake, BigConfig provides the language and the logic to make your infrastructure as versionable and reproducible as your software.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>package manager</category><category>bigconfig</category><category>clojure</category><category>babashka</category><category>nix</category><category>guix</category><category>helm</category></item><item><title>Simple over easy for operations</title><link>https://www.bigconfig.it/blog/simple-over-easy-for-operations/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/simple-over-easy-for-operations/</guid><description>While &quot;easy&quot; tools often promise quick starts by mimicking familiar backend languages, they frequently buckle under the non-linear complexity of real-world infrastructure operations. This article explores why building a custom, data-driven workflow engine in Clojure—leveraging immutability, qualified keywords, and a robust REPL—outperforms &quot;easy&quot; alternatives for orchestrating tools like Terraform and Ansible. By prioritizing a simple architectural foundation over the easy path of standard CI/CD scripts, we gain the ability to handle complex branching, resolve state isolation through nested options, and achieve instantaneous debugging that &quot;duct-tape&quot; solutions simply cannot provide.

</description><pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Building a workflow engine for infrastructure operations is not trivial. Most people start with a simple mental model: a desired state and a sequence of functions that produce side effects. In Clojure, this looks like a simple thread-first macro:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fn1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fn2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; {}    fn1    fn2    ...)&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your state &lt;code dir=&quot;auto&quot;&gt;{}&lt;/code&gt; is threaded through &lt;code dir=&quot;auto&quot;&gt;fn1&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;fn2&lt;/code&gt;. However, real-world operations are rarely linear. They require complex branching, error handling, and conditional jumps (e.g., “if success, continue; otherwise, jump to cleanup”).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;wiring-the-engine&quot;&gt;Wiring the Engine&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To handle non-linear flows, we associate functions with &lt;strong&gt;qualified keywords&lt;/strong&gt; (steps). Together with the next step, they form the “wiring”. You can override sequential execution by providing a &lt;code dir=&quot;auto&quot;&gt;next-fn&lt;/code&gt; to handle custom branching.&lt;/p&gt;
&lt;p&gt;The core execution loop looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;loop&lt;/span&gt;&lt;span&gt; [step first-step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [[f next-step] (&lt;/span&gt;&lt;span&gt;wire-fn&lt;/span&gt;&lt;span&gt; step step-fns)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;new-opts (&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; opts)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[next-step next-opts] (&lt;/span&gt;&lt;span&gt;next-fn&lt;/span&gt;&lt;span&gt; step next-step new-opts)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; next-step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;recur&lt;/span&gt;&lt;span&gt; next-step next-opts)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next-opts)))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;workflow-example&quot;&gt;Workflow Example&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is how we use this engine to create a client-side lock for Terraform using Git tags. The &lt;code dir=&quot;auto&quot;&gt;opts&lt;/code&gt; map represents our “World State”, shared across all functions.&lt;/p&gt;
&lt;p&gt;We invoke it like this: &lt;code dir=&quot;auto&quot;&gt;(lock [] {})&lt;/code&gt;. The first argument is a list of middleware-style step functions, and the second is the starting state.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&gt;workflow&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:first-step&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::generate-lock-id&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;:wire-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step _]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::generate-lock-id&lt;/span&gt;&lt;span&gt; [generate-lock-id &lt;/span&gt;&lt;span&gt;::delete-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::delete-tag&lt;/span&gt;&lt;span&gt; [delete-tag &lt;/span&gt;&lt;span&gt;::create-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::create-tag&lt;/span&gt;&lt;span&gt; [create-tag &lt;/span&gt;&lt;span&gt;::push-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::push-tag&lt;/span&gt;&lt;span&gt; [push-tag &lt;/span&gt;&lt;span&gt;::get-remote-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::get-remote-tag&lt;/span&gt;&lt;span&gt; [(&lt;/span&gt;&lt;span&gt;comp&lt;/span&gt;&lt;span&gt; get-remote-tag delete-tag) &lt;/span&gt;&lt;span&gt;::read-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::read-tag&lt;/span&gt;&lt;span&gt; [read-tag &lt;/span&gt;&lt;span&gt;::check-tag&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::check-tag&lt;/span&gt;&lt;span&gt; [check-tag &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; [identity]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;:next-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step next-step opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt; opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::push-tag&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;choice&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:on-success&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                              &lt;/span&gt;&lt;span&gt;:on-failure&lt;/span&gt;&lt;span&gt; next-step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                              &lt;/span&gt;&lt;span&gt;:opts&lt;/span&gt;&lt;span&gt; opts})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;::delete-tag&lt;/span&gt;&lt;span&gt; [next-step opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;choice&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:on-success&lt;/span&gt;&lt;span&gt; next-step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                   &lt;/span&gt;&lt;span&gt;:on-failure&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                   &lt;/span&gt;&lt;span&gt;:opts&lt;/span&gt;&lt;span&gt; opts})))})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;workflow {:first-step ::generate-lock-id             :wire-fn (fn [step _]                        (case step                          ::generate-lock-id [generate-lock-id ::delete-tag]                          ::delete-tag [delete-tag ::create-tag]                          ::create-tag [create-tag ::push-tag]                          ::push-tag [push-tag ::get-remote-tag]                          ::get-remote-tag [(comp get-remote-tag delete-tag) ::read-tag]                          ::read-tag [read-tag ::check-tag]                          ::check-tag [check-tag ::end]                          ::end [identity]))             :next-fn (fn [step next-step opts]                        (case step                          ::end [nil opts]                          ::push-tag (choice {:on-success ::end                                              :on-failure next-step                                              :opts opts})                          ::delete-tag [next-step opts]                          (choice {:on-success next-step                                   :on-failure ::end                                   :opts opts})))})&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;debugging-made-simple&quot;&gt;Debugging Made Simple&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In many CI/CD systems, debugging is a nightmare of “print” statements and re-running 10-minute pipelines. Because Clojure data structures are immutable and persistent, we can use a &lt;code dir=&quot;auto&quot;&gt;debug&lt;/code&gt; macro provided by BigConfig and a “spy” function to inspect the state at &lt;em&gt;every&lt;/em&gt; step.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;debug&lt;/span&gt;&lt;span&gt; tap-values&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; [(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [f step opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tap&gt;&lt;/span&gt;&lt;span&gt; [step opts]) &lt;/span&gt;&lt;span&gt;;; &quot;Spy&quot; on every state change&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt; step opts))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;::tools/tofu-opts&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;workflow/parse-args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;::tools/ansible-opts&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;workflow/parse-args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)})))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; [step opts]) ;; &amp;#x22;Spy&amp;#x22; on every state change               (f step opts))]            {::bc/env :repl             ::tools/tofu-opts (workflow/parse-args &amp;#x22;render&amp;#x22;)             ::tools/ansible-opts (workflow/parse-args &amp;#x22;render&amp;#x22;)})))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code dir=&quot;auto&quot;&gt;tap&gt;&lt;/code&gt;, you get the result “frozen in time”. You can render templates and inspect them without ever executing a side effect.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;solving-the-composability-problem-nested-options&quot;&gt;Solving the Composability Problem: Nested Options&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Operations often require calling the same sub-workflow multiple times. If every workflow uses the same top-level keys, they clash. We solve this with &lt;strong&gt;Nested Options&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By using the workflow’s namespace as a key, we isolate state. However, sometimes a child needs data from a sibling (e.g., Ansible needs an IP address generated by Terraform). We use an &lt;code dir=&quot;auto&quot;&gt;opts-fn&lt;/code&gt; to map these values explicitly at runtime.&lt;/p&gt;
&lt;p&gt;The specialized &lt;code dir=&quot;auto&quot;&gt;-&gt;workflow*&lt;/code&gt; constructor uses this &lt;code dir=&quot;auto&quot;&gt;next-fn&lt;/code&gt; to manage this state isolation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step next-step {&lt;/span&gt;&lt;span&gt;:keys&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;::bc/exit&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; opts}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;steps-set&lt;/span&gt;&lt;span&gt; step)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;swap!&lt;/span&gt;&lt;span&gt; opts* merge (&lt;/span&gt;&lt;span&gt;select-keys&lt;/span&gt;&lt;span&gt; opts [&lt;/span&gt;&lt;span&gt;::bc/exit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::bc/err&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;swap!&lt;/span&gt;&lt;span&gt; opts* assoc step opts))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;reset!&lt;/span&gt;&lt;span&gt; opts* opts))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cond&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; step &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt;) [&lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt; @opts*]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; exit &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)     [&lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; @opts*] &lt;/span&gt;&lt;span&gt;;; Error handling jump&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;:else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[next-step (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [[new-opts opts-fn]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; step-&gt;opts-and-opts-fn next-step [@opts* identity])]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;opts-fn&lt;/span&gt;&lt;span&gt; new-opts))]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; exit 0)     [::end @opts*] ;; Error handling jump    :else    [next-step (let [[new-opts opts-fn]                     (get step-&gt;opts-and-opts-fn next-step [@opts* identity])]                 (opts-fn new-opts))]))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This logic ensures that if a step is a sub-workflow, its internal state is captured within the parent’s state under its own key. The &lt;code dir=&quot;auto&quot;&gt;opts-fn&lt;/code&gt; allows us to bridge the gap—for instance, pulling a Terraform-generated IP address into the Ansible configuration dynamically.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-working-directory-and-the-maven-diamond-problem&quot;&gt;The Working Directory and the Maven Diamond Problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In operations, you must render configuration files before invoking tools. If you compose multiple workflows, you run into the “Maven Diamond Problem”: two different parent workflows sharing the same sub-workflow. To prevent them from overwriting each other’s files, we use dynamic, hashed prefixes for working directories:&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;.dist/default-f704ed4d/io/github/amiorin/alice/tools/ansible&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The hash &lt;code dir=&quot;auto&quot;&gt;f704ed4d&lt;/code&gt; is dynamic. If a workflow is moved or re-composed, the hash changes, ensuring total isolation during template rendering.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion-simple-over-easy&quot;&gt;Conclusion: Simple over Easy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Tools like &lt;a href=&quot;https://aws.amazon.com/step-functions/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; AWS Step Functions &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, &lt;a href=&quot;https://temporal.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Temporal &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, or &lt;a href=&quot;https://www.restate.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Restate &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; are powerful workflow engines, but for many operational tasks, they are not a good fit. &lt;strong&gt;BigConfig&lt;/strong&gt; has an edge because it is local and synchronous where it counts. It turns infrastructure into a local control loop orchestrating multiple tools.&lt;/p&gt;
&lt;p&gt;In the industry, “Easy” (using the same language as the backend, like Go) often wins over “Simple”. But Go lacks a REPL, immutable data structures, and the ability to implement a &lt;code dir=&quot;auto&quot;&gt;debug&lt;/code&gt; macro that allows for instantaneous feedback.&lt;/p&gt;
&lt;p&gt;Infrastructure eventually becomes a mess of “duct tape and prayers” when the underlying tools aren’t built for complexity. If you choose &lt;strong&gt;Simple&lt;/strong&gt; over &lt;strong&gt;Easy&lt;/strong&gt;, Clojure is the best language for operations—even if you’re learning Clojure for the first time.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;

&lt;div&gt; &lt;iframe src=&quot;https://www.youtube.com/embed/FHW8b4HQ_Og?si=mvswUjnTp-t7hbe5&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;/div&gt;</content:encoded><category>bigconfig</category><category>clojure</category><category>terraform</category><category>ansible</category><category>aws step functions</category><category>temporal</category><category>restate</category><category>go</category><category>simple</category><category>easy</category></item><item><title>Composability: Orchestrating Infrastructure with Babashka and BigConfig Package</title><link>https://www.bigconfig.it/blog/composability-orchestrating-infrastructure-with-babashka-and-bigconfig-package/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/composability-orchestrating-infrastructure-with-babashka-and-bigconfig-package/</guid><description>Stop wrestling with fragmented DevOps scripts. Learn how to use the BigConfig Package and Babashka to build unified, chainable workflows that bridge the gap between OpenTofu, Ansible, and custom Clojure automation.

</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the world of Clojure and DevOps, &lt;a href=&quot;https://babashka.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Babashka &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; has become the gold standard for fast, scripted automation. Today, we’re exploring how to leverage the &lt;a href=&quot;https://www.bigconfig.it/packages/intro/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig Package &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; with Babashka to create highly composable infrastructure workflows that are as flexible as they are powerful.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;getting-started&quot;&gt;&lt;strong&gt;Getting Started&lt;/strong&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of BigConfig Package’s greatest strengths is its CLI-first approach. You can invoke the &lt;code dir=&quot;auto&quot;&gt;walter&lt;/code&gt; package directly from your terminal to run complex sequences in a single line. For example, to cycle through an environment lifecycle:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;walter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;bb&lt;/strong&gt;: The Babashka runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;walter&lt;/strong&gt;: The specific task defined in your configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;create/delete&lt;/strong&gt;: Individual steps within the workflow. You can chain these steps in any order or frequency required by your pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;configuration&quot;&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To integrate &lt;code dir=&quot;auto&quot;&gt;walter&lt;/code&gt; package into your project, define your dependencies and tasks in your &lt;code dir=&quot;auto&quot;&gt;bb.edn&lt;/code&gt; file. This acts as the central configuration for your automation.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;bb.edn&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:deps&lt;/span&gt;&lt;span&gt; {io.github.amiorin/walter {&lt;/span&gt;&lt;span&gt;:git/sha&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;4ea205299cf34c29fa289eacb512a31bfe604d93&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:tasks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:requires&lt;/span&gt;&lt;span&gt; ([io.github.amiorin.walter.package &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; package]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[io.github.amiorin.walter.tools &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; walter]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[io.github.amiorin.alice.tools &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; alice])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tofu {&lt;/span&gt;&lt;span&gt;:doc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Render and apply OpenTofu (Terraform) plans&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;:task&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;walter/tofu*&lt;/span&gt;&lt;span&gt; *command-line-args* (&lt;/span&gt;&lt;span&gt;package/walter-opts&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:big-config.workflow/params&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:hyperscaler&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;oci&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}}))}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ansible {&lt;/span&gt;&lt;span&gt;:doc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Run Ansible playbooks&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;:task&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;walter/ansible*&lt;/span&gt;&lt;span&gt; *command-line-args* (&lt;/span&gt;&lt;span&gt;package/walter-opts&lt;/span&gt;&lt;span&gt; {}))}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ansible-local {&lt;/span&gt;&lt;span&gt;:doc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Run local Ansible playbooks via the &apos;alice&apos; package&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;:task&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;alice/ansible-local*&lt;/span&gt;&lt;span&gt; *command-line-args* (&lt;/span&gt;&lt;span&gt;package/walter-opts&lt;/span&gt;&lt;span&gt; {}))}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;walter {&lt;/span&gt;&lt;span&gt;:doc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Primary Walter workflow&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;:task&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;package/walter*&lt;/span&gt;&lt;span&gt; *command-line-args* {&lt;/span&gt;&lt;span&gt;:big-config.workflow/params&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:hyperscaler&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;oci&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}})}}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;key-components&quot;&gt;&lt;strong&gt;Key Components&lt;/strong&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;:deps&lt;/code&gt;&lt;/strong&gt;: Targets the specific Git SHA of the &lt;code dir=&quot;auto&quot;&gt;walter&lt;/code&gt; package for reproducible builds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow Structure&lt;/strong&gt;: A package typically splits logic between &lt;code dir=&quot;auto&quot;&gt;package.clj&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;tools.clj&lt;/code&gt;. The former composes multiple tools—like OpenTofu and Ansible—into a unified, high-level workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;parameters-and-composability&quot;&gt;&lt;strong&gt;Parameters and Composability&lt;/strong&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;walter&lt;/code&gt; task utilizes a primary parameter: &lt;code dir=&quot;auto&quot;&gt;:hyperscaler&lt;/code&gt;. Currently, the package offers out-of-the-box support for &lt;strong&gt;Oracle Cloud (OCI)&lt;/strong&gt; and &lt;strong&gt;Hetzner&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;uniformity-across-workflows&quot;&gt;&lt;strong&gt;Uniformity across workflows&lt;/strong&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The function &lt;code dir=&quot;auto&quot;&gt;(package/walter-opts ...)&lt;/code&gt; is the “glue” of the system. It handles two critical requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Directory Mapping&lt;/strong&gt;: Every tool needs a specific location to render configuration files. This function ensures these are correctly mapped based on the parent workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Options Consistency&lt;/strong&gt;: By populating the &lt;code dir=&quot;auto&quot;&gt;:big-config.workflow/params&lt;/code&gt;, it ensures that all tasks—whether tool-specific or package-wide—share the same options.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;cross-package-integration&quot;&gt;&lt;strong&gt;Cross-Package Integration&lt;/strong&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;BigConfig Package excels at mixing and matching. While &lt;code dir=&quot;auto&quot;&gt;tofu&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ansible&lt;/code&gt; are defined inside the &lt;code dir=&quot;auto&quot;&gt;walter&lt;/code&gt; package, you can seamlessly pull in tasks like &lt;code dir=&quot;auto&quot;&gt;ansible-local&lt;/code&gt; from entirely different packages (such as &lt;code dir=&quot;auto&quot;&gt;alice&lt;/code&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer Tip:&lt;/strong&gt; Even when nesting workflows inside a larger process, you retain the ability to invoke sub-workflows interactively. This makes debugging specific infrastructure segments significantly faster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;By transforming standard Terraform and Ansible code into a managed workflow, you gain the ability to invoke resources multiple times with surgical precision. Since the only persistent state is the Terraform state (ideally stored in an S3 backend), your entire team can operate the same resources from different machines.&lt;/p&gt;
&lt;p&gt;Beyond standard deployment, BigConfig Package provides built-in coordination steps like &lt;code dir=&quot;auto&quot;&gt;git-check&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;git-push&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;lock&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;unlock-any&lt;/code&gt;. These utilities ensure that whether you are working in a tool-specific workflow or a massive package-wide deployment, your team remains in sync and your iing and configuration management, BigConfig Package removes the friction typically found in multi-cloud deployments. This modular anfrastructure remains stable.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://www.bigconfig.it/packages/walter&quot;&gt; &lt;span&gt;BigConfig Package Walter&lt;/span&gt; &lt;/a&gt; &lt;span&gt;Fork this package or use the package template.&lt;/span&gt; &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; </content:encoded><category>bigconfig</category><category>bigconfig package</category><category>clojure</category><category>walter</category><category>terraform</category><category>ansible</category></item><item><title>Universal Infrastructure: Solving the Portability Gap with BigConfig</title><link>https://www.bigconfig.it/blog/universal-infrastructure-solving-the-portability-gap-with-bigconfig/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/universal-infrastructure-solving-the-portability-gap-with-bigconfig/</guid><description>Terraform and Ansible often struggle with true cross-provider portability. BigConfig Package bridges this gap, allowing developers to deploy both stateful and stateless applications seamlessly across any hyperscaler, from Oracle Cloud to Hetzner.

</description><pubDate>Mon, 09 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The primary challenge with Terraform and Ansible has always been portability. It is notoriously difficult to take a solution written for one environment and apply it to another without significant manual adjustments. Kubernetes achieved its massive success by leveling this playing field. With tools like Helm, you have a package manager that allows you to install applications without worrying about whether you are on-prem or using a specific hyperscaler.&lt;/p&gt;
&lt;p&gt;However, in the world of Kubernetes, a common sentiment is to avoid stateful applications like databases unless you have mastered every technical nuance. Most internal platforms use Kubernetes for stateless services while relying on managed databases provided by the hyperscaler.&lt;/p&gt;
&lt;p&gt;BigConfig Package changes this dynamic by enabling the creation of universal applications that are both stateful and stateless. &lt;a href=&quot;https://github.com/amiorin/walter&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Walter &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, the first application built with BigConfig, demonstrates this by deploying seamlessly across Oracle Cloud and Hetzner.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-portability-challenge&quot;&gt;The Portability Challenge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Infrastructure is rarely uniform. When moving between providers, you encounter several inconsistencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The IP address property is named differently depending on the provider.&lt;/li&gt;
&lt;li&gt;The default SSH user varies.&lt;/li&gt;
&lt;li&gt;The UID of the default user is often inconsistent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The solution involves defining a standardized schema for output parameters in every &lt;code dir=&quot;auto&quot;&gt;main.tf&lt;/code&gt; file:&lt;/p&gt;
  
&lt;div&gt;&lt;h2 id=&quot;gluing-infrastructure-to-configuration&quot;&gt;Gluing Infrastructure to Configuration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This approach effectively glues the OpenTofu infrastructure step to the Ansible configuration step. By parsing the JSON output from the infrastructure layer, we can pass critical connection data directly into the workflow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defn&lt;/span&gt;&lt;span&gt; opts-fn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [dir (&lt;/span&gt;&lt;span&gt;workflow/path&lt;/span&gt;&lt;span&gt; opts &lt;/span&gt;&lt;span&gt;::tools/tofu&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;merge-with&lt;/span&gt;&lt;span&gt; merge opts {&lt;/span&gt;&lt;span&gt;::workflow/params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fs/exists?&lt;/span&gt;&lt;span&gt; dir)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;p/shell&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:dir&lt;/span&gt;&lt;span&gt; dir&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                              &lt;/span&gt;&lt;span&gt;:out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:string&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;tofu output --json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                    &lt;/span&gt;&lt;span&gt;:out&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;json/parse-string&lt;/span&gt;&lt;span&gt; keyword)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&gt;&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s/select-one&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;:params&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:value&lt;/span&gt;&lt;span&gt;])))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:ip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;192.168.0.1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                 &lt;/span&gt;&lt;span&gt;:sudoer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ubuntu&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;})})))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; (p/shell {:dir dir                                                              :out :string} &amp;#x22;tofu output --json&amp;#x22;)                                                    :out                                                    (json/parse-string keyword)                                                    (-&gt;&gt; (s/select-one [:params :value])))                                                {:ip &amp;#x22;192.168.0.1&amp;#x22;                                                 :sudoer &amp;#x22;ubuntu&amp;#x22;})})))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As long as the OpenTofu step adheres to the schema by providing an &lt;code dir=&quot;auto&quot;&gt;ip&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sudoer&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;uid&lt;/code&gt;, any new hyperscaler can be integrated into this BigConfig Package.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;handling-distribution-differences&quot;&gt;Handling Distribution Differences&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;What about variations in Linux distributions? This can be handled within Ansible or, similar to our Terraform approach, by using different source files based on the distribution.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defn&lt;/span&gt;&lt;span&gt; tofu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[step-fns opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [opts (&lt;/span&gt;&lt;span&gt;workflow/prepare&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;::workflow/name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::tofu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                &lt;/span&gt;&lt;span&gt;::render/templates&lt;/span&gt;&lt;span&gt; [{&lt;/span&gt;&lt;span&gt;:template&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;keyword-&gt;path&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::tofu&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                     &lt;/span&gt;&lt;span&gt;:overwrite&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                     &lt;/span&gt;&lt;span&gt;:hyperscaler&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;hcloud&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                     &lt;/span&gt;&lt;span&gt;:transform&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{{ hyperscaler }}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]]}]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;workflow/run-steps&lt;/span&gt;&lt;span&gt; step-fns opts)))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;path ::tofu)                                                     :overwrite true                                                     :hyperscaler &amp;#x22;hcloud&amp;#x22;                                                     :transform [[&amp;#x22;{{ hyperscaler }}&amp;#x22;]]}]}                               opts)]    (workflow/run-steps step-fns opts)))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The power lies in the dynamic folder pathing. By using the template variable &lt;code dir=&quot;auto&quot;&gt;&quot;{{ hyperscaler }}&quot;&lt;/code&gt; for the hyperscaler, the directory containing the infrastructure code becomes dynamic. This allows us to share core Ansible logic while diverging where necessary, ensuring the code remains clean and manageable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;By standardizing the handshake between infrastructure provisioning and configuration management, BigConfig Package removes the friction typically found in multi-cloud deployments. This modular approach ensures that your automation remains truly portable, allowing stateful workloads to run wherever they are needed most without being locked into a single provider’s ecosystem.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://www.bigconfig.it/packages/walter&quot;&gt; &lt;span&gt;BigConfig Package Walter&lt;/span&gt; &lt;/a&gt; &lt;span&gt;Fork this package or use the package template.&lt;/span&gt; &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; </content:encoded><category>bigconfig</category><category>bigconfig package</category><category>helm</category><category>kubernetes</category><category>clojure</category><category>remote development environment</category><category>walter</category><category>terraform</category><category>ansible</category></item><item><title>The YAML Trap: Escaping Greenspun’s Tenth Rule with BigConfig</title><link>https://www.bigconfig.it/blog/the-yaml-trap-escaping-greenspun-s-tenth-rule-with-bigconfig/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/the-yaml-trap-escaping-greenspun-s-tenth-rule-with-bigconfig/</guid><description>Greenspun’s Tenth Rule warns that every complex system eventually reinvents a buggy version of Lisp. From YAML-hell in CI/CD to the &quot;accidental&quot; programming languages of Helm, Terraform and Ansible, the DevOps world is living this prophecy. Discover how shifting from rigid configuration to the power of Clojure and BigConfig can help you escape the trap of ad-hoc infrastructure logic.

</description><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Greenspun’s Tenth Rule is a famous (and delightfully cynical) adage in computer science. While it was born in the era of C and Fortran, it has never been more relevant than it is today in the world of Platform Engineering.&lt;/p&gt;
&lt;p&gt;If you’ve ever felt like your CI/CD pipeline is held together by duct tape, YAML-indentation prayers, and sheer willpower, you’ve lived this rule.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-greenspuns-tenth-rule&quot;&gt;What is Greenspun’s Tenth Rule?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In the early 90s, Philip Greenspun stated:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The core insight is that once a system reaches a certain level of complexity, it inevitably requires high-level abstraction, automation, and dynamic logic. Instead of starting with a powerful, established language (like Lisp) built for those tasks, developers often “accidentally” reinvent a mediocre version of one using brittle configuration files and makeshift scripts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-rule-in-the-devops-ecosystem&quot;&gt;The Rule in the DevOps Ecosystem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In DevOps, we strive for Infrastructure as Code (IaC). However, because we started with static configuration formats (YAML/JSON) and tried to force them to perform complex logic, we’ve essentially proven Greenspun right.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-the-yaml-programming-trap&quot;&gt;1. The YAML “Programming” Trap&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Tools like Terraform, Ansible, Helm, and GitHub Actions began as simple configuration formats. But as users demanded loops, conditionals, and variables, these tools evolved into “accidental” languages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; You end up writing complex business logic inside strings within a YAML file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Reality:&lt;/strong&gt; You are using a “bug-ridden implementation” of a real programming language, but without the benefit of a debugger, a compiler, or proper unit testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;2-kubernetes-as-a-distributed-lisp&quot;&gt;2. Kubernetes as a Distributed Lisp&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Some architects argue that Kubernetes is the ultimate manifestation of this rule. Its control loop the constant cycle of reconciling desired state vs. actual state mimics the recursive nature of Lisp environments. It is, in essence, a programmable platform designed to manage other programs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;escaping-the-trap-putting-lisp-back-in-ops&quot;&gt;Escaping the Trap: Putting Lisp Back in Ops&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The industry has invested massive human capital into building Ansible roles, Helm charts, and Terraform modules. We shouldn’t throw them away, but we must stop trying to make them do things they weren’t designed for.&lt;/p&gt;
&lt;p&gt;How do we escape Greenspun’s trap without rebuilding everything from scratch? By assimilating these tools (to borrow a 90s Star Trek reference).&lt;/p&gt;
&lt;p&gt;This is the core design principle of BigConfig. Instead of fighting against limited YAML DSLs, BigConfig uses Clojure a modern, production-grade Lisp to wrap and orchestrate existing tools.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The BigConfig Philosophy:&lt;/strong&gt; Express infrastructure logic with the most powerful dynamic language available, while still leveraging the ecosystem you already have.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;why-it-matters-the-power-of-assimilation&quot;&gt;Why it Matters: The Power of Assimilation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Tenth Rule is a warning: Don’t reinvent the wheel poorly. If your infrastructure requires complex logic, stop forcing it into a flat config file.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;from-static-files-to-fractal-architecture&quot;&gt;From Static Files to Fractal Architecture&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;While a standard Helm package is limited strictly to Kubernetes, a &lt;a href=&quot;https://www.bigconfig.it/api/package/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig package &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; is a Clojure function. Because BigConfig assimilates Ansible and Terraform alongside Helm, it isn’t siloed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Truly Cloud Native:&lt;/strong&gt; A Kubernetes application that requires specific cloud resources (like an S3 bucket or an RDS instance) can be abstracted into a single, cohesive unit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;First-Class Functions:&lt;/strong&gt; In BigConfig, everything is a function. This leads to a fractal architecture where every layer from a single container to a multi-region cloud deployment is governed by the same recursive logic: Observe, Diff, and Act.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;ready-to-stop-writing-logic-in-yaml&quot;&gt;Ready to stop writing logic in YAML?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Operations is a hard problem. YAML is too rigid, and Go is too low-level for rapid infrastructure iteration. While Python and JavaScript are popular, they lack the REPL-driven development flow that makes infrastructure-as-code feel truly interactive.&lt;/p&gt;
&lt;p&gt;Clojure is the most robust Lisp available today and it won’t let you down.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Greenspun’s Tenth Rule isn’t just a witty observation; it’s a technical debt warning. When we try to solve 21st-century infrastructure challenges using static configuration files, we inevitably end up building “shadow” programming languages that are difficult to test, impossible to debug, and fragile to scale.&lt;/p&gt;
&lt;p&gt;By embracing a functional, Lisp-based approach through BigConfig, we stop fighting the limitations of YAML and start leveraging the power of actual logic. Instead of building a “bug-ridden implementation of half of Common Lisp,” we use the real thing Clojure to orchestrate, automate, and scale.&lt;/p&gt;
&lt;p&gt;The goal of Platform Engineering shouldn’t be to write more scripts; it should be to create elegant, recursive systems that can manage themselves. It’s time to move past the duct tape and prayers and give our infrastructure the robust, dynamic foundation it deserves.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>lisp</category><category>clojure</category><category>bigconfig</category><category>bigconfig package</category><category>helm</category><category>ansible</category><category>terraform</category><category>kubernetes</category></item><item><title>Introducing BigConfig Package</title><link>https://www.bigconfig.it/blog/introducing-bigconfig-package/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/introducing-bigconfig-package/</guid><description>BigConfig Package moves beyond the &quot;Kubernetes-only&quot; mindset of Helm by treating infrastructure and software as a single unit. By replacing specialized CLIs with Babashka (Clojure), it eliminates the friction between shell scripts and compiled binaries. This approach allows developers to move seamlessly between a CLI, a REPL for debugging, and a library for deep automation.

</description><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Helm has long been the gold standard for Kubernetes, but it hits a wall the moment you step outside the cluster. While there are projects to bridge this gap, they never quite achieved Helm’s level of ubiquity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BigConfig Package&lt;/strong&gt; is taking a different path. It aims to deliver both infrastructure and software as a unified, cohesive delivery.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-case-against-the-cli-proliferation&quot;&gt;The Case Against the “CLI Proliferation”&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of the most radical aspects of BigConfig Package is what it lacks: a dedicated CLI. In BigConfig’s philosophy, a CLI is just another tool that end-users should tailor to their specific needs. We argue that the current explosion of specialized CLIs is actually a symptom of unsolved underlying problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bash Limitations:&lt;/strong&gt; The fragility of shell scripting forces developers to wrap logic in binaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interoperability Gaps:&lt;/strong&gt; Integrating multiple CLI tools via scripting is often easier than managing complex library dependencies, leading to “scripting fatigue.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Go Paradox:&lt;/strong&gt; While static languages like Go are excellent for single binary deployments, they force the creation of monolithic tools that make composition more difficult (e.g., Terraform plugin is based on GRPC).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BigConfig solves these issues by leveraging Clojure and Babashka. By using a dynamic, high-performance language, we replace rigid tools with flexible Babashka tasks, offering the power of a library with the convenience of a script.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-three-environments-shell-repl-and-lib&quot;&gt;The Three Environments: Shell, REPL, and Lib&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most DevOps professionals only interact with tools (like Helm) in the Shell environment. BigConfig expands this to three distinct planes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Shell:&lt;/strong&gt; For quick, command-line execution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;REPL:&lt;/strong&gt; For real-time, interactive debugging and experimentation. No more “edit-coffee-fail” loops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lib:&lt;/strong&gt; For deep automation. You can orchestrate complex scenarios without reimplementing anything when switching gear from CLI tool to in-house solution.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-a-bigconfig-package&quot;&gt;What is a BigConfig Package?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is no need to reinvent the wheel. A BigConfig Package is simply a Clojure repository, and the package itself is just a Clojure function written with &lt;a href=&quot;https://www.bigconfig.it/api/workflow/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig Workflow &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our first application, &lt;a href=&quot;https://github.com/amiorin/walter&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Walter &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, tackles a challenge Helm simply wasn’t built for: Remote Development Environments on machines instead of containers.&lt;/p&gt;
&lt;p&gt;While devcontainers are popular, containerizing a full development environment on Kubernetes has significant overhead and limitations. Nix is the superior choice here. Its native multi-user support makes it ideal for high-powered “beefy” machines shared by multiple developers.&lt;/p&gt;
&lt;p&gt;In this setup, Terraform and Ansible do the heavy lifting but remain completely hidden once development is complete, leaving behind a clean interface that doesn’t “leak” its underlying complexity.&lt;/p&gt;
&lt;p&gt;The use case of Remote Development Environment is getting traction and by integrating a Linux user to run a self-hosted GitHub runner, you unlock massive efficiency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cost Reduction:&lt;/strong&gt; Run Dev and CI on the same hardware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant Caching:&lt;/strong&gt; CI pipelines never miss a cache hit because the environment is shared.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Absolute Parity:&lt;/strong&gt; “Works on my machine” becomes a reality because the Dev and CI environments are identical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native Speed:&lt;/strong&gt; Docker runs natively, eliminating the virtualization overhead found on macOS or Windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;comparison-helm-vs-babashka&quot;&gt;Comparison: Helm vs. Babashka&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;the-helm-way&quot;&gt;The Helm Way&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In this paradigm, you manage repositories and install charts. It typically requires juggling YAML, Go templates, and Bash.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;helm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bitnami&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://charts.bitnami.com/bitnami&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;helm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;helm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-redis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bitnami/redis&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;the-babashka-way&quot;&gt;The Babashka Way&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;With BigConfig, &lt;code dir=&quot;auto&quot;&gt;helm install&lt;/code&gt; becomes &lt;code dir=&quot;auto&quot;&gt;bb redis create&lt;/code&gt;. The CLI is defined by the package creator but can be customized by the user.&lt;/p&gt;
&lt;p&gt;We use &lt;code dir=&quot;auto&quot;&gt;create&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;install&lt;/code&gt; in this case because the infrastructure is part of the delivery. Using &lt;code dir=&quot;auto&quot;&gt;bb&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;bash&lt;/code&gt; provides a unified environment where everything is written in Clojure, replacing the fragmented mix of Bash, Compiled Go, and YAML.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;;; bb.edn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:deps&lt;/span&gt;&lt;span&gt; {io.github.amiorin/redis {&lt;/span&gt;&lt;span&gt;:git/sha&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;eaff6f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:tasks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:requires&lt;/span&gt;&lt;span&gt; ([redis &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; redis])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;redis {&lt;/span&gt;&lt;span&gt;:doc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Provision a Redis instance&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;:task&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;comp-wf/redis*&lt;/span&gt;&lt;span&gt; *command-line-args*)}}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redis&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The era of managing a dozen different CLIs just to deploy a single stack is reaching its breaking point. BigConfig offers a way out, not by building a bigger tool, but by providing a more flexible foundation. By unifying the way we handle infrastructure and applications through Clojure, we aren’t just deploying software; we are creating a more cohesive, reproducible, and developer-friendly ecosystem.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot;&gt;hear&lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>bigconfig package</category><category>helm</category><category>kubernetes</category><category>clojure</category><category>bigconfig</category><category>remote development environment</category><category>walter</category><category>terraform</category><category>ansible</category></item><item><title>My Keyboard Endgame: 34 Keys, 3 Layers, and a Macropad</title><link>https://www.bigconfig.it/blog/my-keyboard-endgame-34-keys-3-layers-and-a-macropad/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/my-keyboard-endgame-34-keys-3-layers-and-a-macropad/</guid><description>I spend a lot of time analyzing other people&apos;s &quot;endgame&quot; setups, and I honestly don&apos;t understand why so many users accept such steep learning curves.

The secret to a sustainable workflow is simplicity. If your layout is so complex that a month away from your desk &quot;drives you nuts&quot; when you return, it’s not an endgame—it’s a chore. Here is how I stripped my setup down to the essentials without losing productivity.

</description><pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I spend a lot of time analyzing other people’s “endgame” setups, and I honestly don’t understand why so many users accept such steep learning curves.&lt;/p&gt;
&lt;p&gt;The secret to a sustainable workflow is simplicity. If your layout is so complex that a month away from your desk “drives you nuts” when you return, it’s not an endgame—it’s a chore. Here is how I stripped my setup down to the essentials without losing productivity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/keyboard.BpyXHFoU_ZRv5kl.webp&quot; alt=&quot;keyboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;3742&quot; height=&quot;1001&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-hardware-philosophy&quot;&gt;The Hardware Philosophy&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;34 Keys is Enough&lt;/strong&gt;: I’m a &lt;a href=&quot;https://github.com/davidphilipbarr/Sweep&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Ferris Sweep MX &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; devotee. You can buy the the Silakka54 from Aliexpress if you are on a budget.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2 Thumb Keys&lt;/strong&gt;: You don’t need a cluster; two well-placed keys are sufficient.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Externalize the Complexity&lt;/strong&gt;: Don’t cram multimedia into layers. Use a dedicated macropad like the Elgato Stream Deck (or the budget-friendly Ajazz AKP03E). This gives you physical dials and buttons for the stuff that doesn’t belong on a typing layer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Trackpad&lt;/strong&gt;: The Apple Magic Trackpad remains king. I use &lt;a href=&quot;https://mouseless.click/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Mouseless &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; to reduce dependency, but I refuse to add clunky mouse layers to my keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-three-layer-rule&quot;&gt;The Three-Layer Rule&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most people over-engineer their firmware. I’ve cut out the “magic” (No Macros, No Tap Dance, No Key Overrides) in favor of logic.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layer 0&lt;/strong&gt;: Alpha. Standard typing.
&lt;img src=&quot;https://www.bigconfig.it/_astro/layer0.Bv0hdh_7_16Emjr.webp&quot; alt=&quot;layer 0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;1404&quot; height=&quot;672&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layer 1&lt;/strong&gt;: Numbers &amp;amp; Navigation. I use the &lt;code dir=&quot;auto&quot;&gt;zxcv&lt;/code&gt; row for numbers. This leaves 15 keys free for my Tiling Window Manager of choice, &lt;a href=&quot;https://nikitabobko.github.io/AeroSpace/guide&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; AeroSpace &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.
&lt;img src=&quot;https://www.bigconfig.it/_astro/layer1.CfX62BsE_1Y1Ddz.webp&quot; alt=&quot;layer 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;1390&quot; height=&quot;672&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layer 2&lt;/strong&gt;: Symbols. I don’t understand splitting symbols across layers. There are exactly 29 essential symbols—they fit perfectly on one layer. Keep them together so your brain doesn’t have to hunt.
&lt;img src=&quot;https://www.bigconfig.it/_astro/layer2.vky9CP90_1pVpbh.webp&quot; alt=&quot;layer 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;1368&quot; height=&quot;658&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;taming-the-necessary-evils&quot;&gt;Taming the “Necessary Evils”&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Home Row Mods&lt;/strong&gt;: These can be a headache. I’ve contained the “evil” by only using two keys for mods (&lt;code dir=&quot;auto&quot;&gt;Z/X&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;comma/dot&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Combos&lt;/strong&gt;: Only one—&lt;code dir=&quot;auto&quot;&gt;F+J&lt;/code&gt; for &lt;code dir=&quot;auto&quot;&gt;CAPS WORD&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Input Methods&lt;/strong&gt;: Stick to Tap-Hold and Layer Toggle only. Anything else increases the likelihood of misfires.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-terminal-centric-stack&quot;&gt;The Terminal-Centric Stack&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you live in the terminal, your hardware is only as good as your software’s ability to interpret it. My stack focuses on modern protocols and efficiency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Protocol&lt;/strong&gt;: Use &lt;a href=&quot;https://github.com/benotn/kkp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Kitty Keyboard Protocol (kkp) &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; for better key handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Terminal&lt;/strong&gt;: &lt;a href=&quot;https://ghostty.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Ghostty &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; (fast, native, modern).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiplexer&lt;/strong&gt;: &lt;a href=&quot;https://zellij.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Zellij &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; over tmux.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Editor&lt;/strong&gt;: &lt;a href=&quot;https://github.com/doomemacs/doomemacs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Doomemacs &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; (Vi bindings forever).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;my-skhd-configuration&quot;&gt;My skhd Configuration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Since I avoid complex firmware overrides, I handle my application-specific shortcuts via &lt;a href=&quot;https://github.com/asmvik/skhd&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; skhd &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;. This keeps my keyboard “dumb” and my configuration portable.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Use xxd -psd to find the hex code and remove 0a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Navigation: Prev/Next tab in Chrome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cmd - i&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;shift + cmd - 0x21&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cmd - o&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;shift + cmd - 0x1E&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Organization: Move tab left/right in Chrome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shift + cmd - i&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;shift + ctrl - 0x74&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;shift + cmd - o&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;shift + ctrl - 0x79&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# History: Back and Forward&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cmd - 0x2B&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;cmd - 0x21&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cmd - 0x2F&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;cmd - 0x1E&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Terminal Fix: Emacs in terminal doesn&apos;t support M-[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Mapping M-5 as a workaround&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;alt - 0x21&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;skhd -k &quot;alt - 5&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Keep it simple. If you can’t explain your layout to yourself in 30 seconds, it’s too complicated. Externalize your macros to a macropad, keep your symbols on one layer, and let your software do the heavy lifting.&lt;/p&gt;</content:encoded><category>keyboard</category><category>terminal</category><category>productivity</category><category>elgato</category><category>ajazz</category><category>stream deck</category><category>macropad</category></item><item><title>Replacing Integrant and Docker Compose with BigConfig System</title><link>https://www.bigconfig.it/blog/replacing-integrant-and-docker-compose-with-bigconfig-system/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/replacing-integrant-and-docker-compose-with-bigconfig-system/</guid><description>BigConfig System is a Clojure library that unifies application architecture and infrastructure management into a single, programmable workflow, eliminating the friction of switching between the REPL and external tools like Docker Compose.

By treating system lifecycles as a sequence of code-driven functions rather than static YAML configurations, it allows developers to manage complex processes—such as dynamic Postgres initialization and teardown—entirely within a running session.

This &quot;Emacs-style&quot; approach favors high-granularity evaluation and real-time state inspection over heavy-handed namespace refreshes, consolidating the entire development stack into a single Clojure-based source of truth for improved introspection and speed.

</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the Clojure ecosystem, we’ve grown accustomed to a structural divide. We manage our application architecture with libraries like &lt;a href=&quot;https://github.com/weavejester/integrant&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Integrant &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and our external dependencies (databases, queues, caches) with &lt;a href=&quot;https://docs.docker.com/compose/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Docker Compose &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; or &lt;a href=&quot;https://f1bonacc1.github.io/process-compose/cli/process-compose/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Process Compose &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While effective, this separation creates a constant “context switch.” Your application lives in the REPL, but its foundation is buried in YAML and EDN files. One requires evaluating expressions; the other requires multiple steps to refresh, start, or stop using Emacs or a Shell.&lt;/p&gt;
&lt;p&gt;To bridge this gap, I’ve been developing BigConfig System. It is a library inside the BigConfig ecosystem that treats system lifecycles as programmable workflows rather than static dependency graphs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-emacs-philosophy&quot;&gt;The “Emacs” Philosophy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I view BigConfig through the lens of the Emacs philosophy: the environment is something you should never have to leave.&lt;/p&gt;
&lt;p&gt;As projects scale, we traditionally reach for external orchestrators. However, from the perspective of a running REPL, they have foreign interfaces. By keeping the orchestration logic strictly within Clojure I’ve unified the application and its dependencies. Now, I can manage the entire stack, from environment variables to database migrations, without ever dropping out of my REPL session.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;solving-the-postgres-dependency&quot;&gt;Solving the Postgres Dependency&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To test the limits of BigConfig System, I moved away from static Postgres containers in favor of a dynamic, code-driven workflow. I needed to handle multiple profiles (like dev for long-running sessions and test for transient fixtures) simultaneously without port collisions or state pollution.&lt;/p&gt;
&lt;p&gt;Instead of a binary “started/stopped” toggle, BigConfig manages a granular execution sequence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment Prep:&lt;/strong&gt; Dynamically set variables for project-root/.postgres/[profile].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filesystem Cleanup:&lt;/strong&gt; Ensure a clean slate by wiping stale data from previous runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; Programmatically execute initdb.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart Startup:&lt;/strong&gt; Launch Postgres via babashka/process, grepping logs for the “ready to accept connections” regex.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provisioning:&lt;/strong&gt; Chain-load createuser, createdb, and sql-migrate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teardown:&lt;/strong&gt; A dedicated stop-fn ensures the process tree is destroyed cleanly.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;abandoning-the-refresh-cycle&quot;&gt;Abandoning the “Refresh” Cycle&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Many Clojure developers rely on &lt;code dir=&quot;auto&quot;&gt;cider-ns-refresh&lt;/code&gt;, but for a refined flow, I find it a bit heavy-handed. My development style centers on evaluating expressions directly within &lt;code dir=&quot;auto&quot;&gt;(comment ...)&lt;/code&gt; blocks.&lt;/p&gt;
&lt;p&gt;By using &lt;code dir=&quot;auto&quot;&gt;cider-eval-defun-at-point&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cider-inspect-last-result&lt;/code&gt;, I gain high-granularity control. The beauty of BigConfig is that the CIDER inspector updates automatically as the system state changes. I can see the system evolve in real-time, directly within my editor, without the jarring reset of a full namespace refresh.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-workflow-in-action&quot;&gt;The Workflow in Action&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is how BigConfig System expresses a managed process as a programmable workflow. Notice how the system lifecycle is defined as a series of step functions (&lt;code dir=&quot;auto&quot;&gt;background-process&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;stop&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [kill-timeout &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;shutdown-timeout &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;clj-timeout &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; Define a managed background process with a specific lifecycle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; Start the process in the background and block the main thread&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; until the regex is found or the timeout triggers.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defn&lt;/span&gt;&lt;span&gt; background-process [opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [re-opts (&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; {} [[&lt;/span&gt;&lt;span&gt;:cmd&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;clj -X big-config.system/main :shutdown-timeout %s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; shutdown-timeout)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:regex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;#&quot;token&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:timeout&lt;/span&gt;&lt;span&gt; clj-timeout]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::proc&lt;/span&gt;&lt;span&gt;]])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts (&lt;/span&gt;&lt;span&gt;re-process&lt;/span&gt;&lt;span&gt; re-opts opts)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;add-stop-fn&lt;/span&gt;&lt;span&gt; opts (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [{&lt;/span&gt;&lt;span&gt;:keys&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;::proc&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; opts}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;when&lt;/span&gt;&lt;span&gt; proc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;destroy!&lt;/span&gt;&lt;span&gt; proc kill-timeout))))))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; Define the system as a stateful, wired workflow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; -&gt;system (&lt;/span&gt;&lt;span&gt;-&gt;workflow&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:first-step&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                               &lt;/span&gt;&lt;span&gt;:wire-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step _]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                            &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;span&gt; [background-process &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                            &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; [stop]))}))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; sys1 starts and stops the system. It useful during the development of the system itself.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;;; sys2 starts only. This is useful in all the other cases.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; {} [[&lt;/span&gt;&lt;span&gt;:sys1&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sorted-map&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;-&gt;system&lt;/span&gt;&lt;span&gt; [log-step-fn] {&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;span&gt;}))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:sys2&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [system (&lt;/span&gt;&lt;span&gt;atom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sorted-map&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;-&gt;system&lt;/span&gt;&lt;span&gt; [log-step-fn] {&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                                                                    &lt;/span&gt;&lt;span&gt;::async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;})))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;stop!&lt;/span&gt;&lt;span&gt; @system)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@system)]])))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defn&lt;/span&gt;&lt;span&gt; main [&amp;#x26; {&lt;/span&gt;&lt;span&gt;:keys&lt;/span&gt;&lt;span&gt; [shutdown-timeout]}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; shutdown-timeout)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.addShutdownHook&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;Runtime/getRuntime&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Thread.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;[Shutdown Hook] Cleaning up resources before exit...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Thread/sleep&lt;/span&gt;&lt;span&gt; shutdown-timeout))))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Script is running... (Press Ctrl+C to stop)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;println&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;@(&lt;/span&gt;&lt;span&gt;promise&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;system (-&gt;workflow {:first-step ::start                               :wire-fn (fn [step _]                                          (case step                                            ::start [background-process ::end]                                            ::end [stop]))}))    ;; sys1 starts and stops the system. It useful during the development of the system itself.    ;; sys2 starts only. This is useful in all the other cases.    (into {} [[:sys1 (into (sorted-map) (-&gt;system [log-step-fn] {::bc/env :repl}))]              [:sys2 (let [system (atom (into (sorted-map) (-&gt;system [log-step-fn] {::bc/env :repl                                                                                    ::async true})))]                       (stop! @system)                       @system)]])))(defn main [&amp;#x26; {:keys [shutdown-timeout]}]  (assert shutdown-timeout)  (.addShutdownHook (Runtime/getRuntime)                    (Thread. (fn []                               (println &amp;#x22;\n[Shutdown Hook] Cleaning up resources before exit...&amp;#x22;)                               (Thread/sleep shutdown-timeout))))  (println &amp;#x22;Script is running... (Press Ctrl+C to stop)&amp;#x22;)  (println &amp;#x22;token&amp;#x22;)  @(promise))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/system.CUG25WY3_Z1dFpWY.webp&quot; alt=&quot;emacs&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;4336&quot; height=&quot;1692&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;is-it-better-than-integrant-and-docker-compose&quot;&gt;Is it better than Integrant and Docker Compose?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It is still early days, and I am currently refining the library. One major advantage is that there is no longer a split between EDN, YAML, and CLJ files—it’s now just a single CLJ file. The shift in developer experience is already palpable. By replacing the fragmented mix of Docker Compose, Process Compose, and Integrant with a unified Clojure-based workflow, I’ve gained a level of introspection and speed that configuration-based tools and libraries simply cannot provide.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/amiorin/big-config/blob/main/src/clj/big_config/system.clj&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig System &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://github.com/amiorin/rama-jdbc/blob/big-config/test/rama_jdbc/systems.clj&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Postgres example &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.bigconfig.it/libraries/system/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Manual &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My quest to eliminate configuration files has reached a new milestone with &lt;code dir=&quot;auto&quot;&gt;system.edn&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;compose.yaml&lt;/code&gt;. By committing to a “never leave the REPL” philosophy, I’ve replaced traditional CLI tools and libraries with direct evaluation. While abandoning namespace reloading in favor of evaluating only the final expression isn’t for everyone, it has fundamentally transformed my productivity.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>integrant</category><category>docker compose</category><category>process compose</category><category>bigconfig</category><category>system</category><category>bigconfig system</category></item><item><title>Beyond the Shell: Reimagining DevOps with the Clojure REPL</title><link>https://www.bigconfig.it/blog/beyond-the-shell-reimagining-devops-with-the-clojure-repl/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/beyond-the-shell-reimagining-devops-with-the-clojure-repl/</guid><description>Traditional DevOps workflows are often bogged down by the &quot;Edit/Run&quot; loop, requiring developers to constantly switch between editors, directories, and terminal tabs.

BigConfig solves this by leveraging a persistent Clojure REPL to create a unified &quot;Edit/Evaluate&quot; environment.

By orchestrating tools like Terraform through a centralized &quot;workbench map&quot;, BigConfig eliminates context switching and replaces traditional debugging with instant, stateful feedback.

It transforms fragmented operations into a seamless, code-driven flow where infrastructure and development live in a single, interactive workspace.

</description><pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are all familiar with the standard REPL experience provided by shells like Bash, Zsh, or Fish. However, far fewer developers have experienced the power of a Clojure REPL. I built BigConfig in Clojure because I believe we should stop relying on fragmented shell environments for operations and development. By moving to a unified Clojure REPL, we can significantly boost productivity.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-the-editrun-loop&quot;&gt;The Problem: The “Edit/Run” Loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Let’s look at the standard workflow for a tool like Terraform:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;cd&lt;/code&gt; into the project root.&lt;/li&gt;
&lt;li&gt;Edit the &lt;code dir=&quot;auto&quot;&gt;.tf&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Run &lt;code dir=&quot;auto&quot;&gt;terraform plan&lt;/code&gt; in the shell.&lt;/li&gt;
&lt;li&gt;Repeat until it works.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, imagine scaling this across multiple tools. Suddenly, you aren’t just running Terraform; you’re managing &lt;code dir=&quot;auto&quot;&gt;ansible&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;helm&lt;/code&gt;. Each tool lives in a different directory. The cognitive cost of switching directories and managing multiple shell sessions becomes a massive bottleneck. You either waste time &lt;code dir=&quot;auto&quot;&gt;cd&lt;/code&gt;-ing back and forth or you end up with twenty terminal tabs, losing track of which is which.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-bigconfig-alternative-editevaluate&quot;&gt;The BigConfig Alternative: “Edit/Evaluate”&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With BigConfig, this friction disappears. Instead of a “shell-per-directory” approach, we use a single, persistent JVM:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Jack-in once&lt;/strong&gt;: Like a shell, the JVM stays running, but you only need one instance for your entire project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unified Workspace&lt;/strong&gt;: Clojure files and tool-specific configurations (YAML, HCL, etc.) live in the same project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant Feedback&lt;/strong&gt;: You evaluate Clojure code directly within your editor. No context switching is required.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the traditional model, “Edit” and “Run” happen in two different programs (the editor and the shell). In BigConfig, “Edit” and “Evaluate” happen in the same program, your editor. Whether you are managing one tool or a hundred, the workflow remains identical.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;workflow-a-new-type-of-flow-control&quot;&gt;Workflow: A New Type of Flow Control&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To make this possible, BigConfig introduces a specialized flow control: &lt;strong&gt;the workflow.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While &lt;code dir=&quot;auto&quot;&gt;if&lt;/code&gt; is the classic example of flow control in code, a BigConfig workflow operates on a &lt;strong&gt;workbench map&lt;/strong&gt;. You define a series of &lt;strong&gt;steps&lt;/strong&gt; (specialized functions) that execute sequentially, reading from and writing to this shared map.&lt;/p&gt;
&lt;p&gt;For example, the workbench map might contain a sequence like &lt;code dir=&quot;auto&quot;&gt;[&quot;terraform init&quot;, &quot;terraform plan&quot;]&lt;/code&gt;. A specific step reads those commands and executes them in a directory defined elsewhere in the map. Even if you prefer writing HCL or YAML manually rather than generating it, BigConfig’s workflow engine automates the orchestration, so you don’t have to.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;real-world-application-developing-terraform-providers&quot;&gt;Real-World Application: Developing Terraform Providers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I use BigConfig for both operations and core development. Currently, I’m building a &lt;a href=&quot;https://github.com/amiorin/terraform-provider-bigconfig&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Terraform Provider in Clojure &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;. Because the gRPC protocol documentation can be sparse, I built a BigConfig workflow to log and analyze traffic.&lt;/p&gt;
&lt;p&gt;I use two primary workflows for this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The hcloud workflow:&lt;/strong&gt; This starts the Hetzner Terraform Provider in dev mode, launches a gRPC “man-in-the-middle” to log traffic, renders a test &lt;code dir=&quot;auto&quot;&gt;main.tf&lt;/code&gt;, executes Terraform, and transforms the resulting gRPC Java classes into readable Clojure maps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The dev workflow:&lt;/strong&gt; This is identical to the first, but it swaps the Hetzner provider for my own Clojure-based provider in development.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; hcloud-wf (&lt;/span&gt;&lt;span&gt;-&gt;workflow&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:first-step&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                            &lt;/span&gt;&lt;span&gt;:wire-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step step-fns]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;span&gt; [start-hcloud &lt;/span&gt;&lt;span&gt;::start-proxy&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::start-proxy&lt;/span&gt;&lt;span&gt; [start-proxy &lt;/span&gt;&lt;span&gt;::prepare&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::prepare&lt;/span&gt;&lt;span&gt; [prepare &lt;/span&gt;&lt;span&gt;::render&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::render&lt;/span&gt;&lt;span&gt; [render/render &lt;/span&gt;&lt;span&gt;::exec&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::exec&lt;/span&gt;&lt;span&gt; [(&lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; run/run-cmds step-fns) &lt;/span&gt;&lt;span&gt;::fix-messages&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::fix-messages&lt;/span&gt;&lt;span&gt; [fix-messages &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                         &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; [stop]))}))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sorted-map&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;hcloud-wf&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                 &lt;/span&gt;&lt;span&gt;::test-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;hcloud&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;})))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; dev-wf (&lt;/span&gt;&lt;span&gt;-&gt;workflow&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:first-step&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                         &lt;/span&gt;&lt;span&gt;:wire-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [step step-fns]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::start&lt;/span&gt;&lt;span&gt; [start &lt;/span&gt;&lt;span&gt;::start-proxy&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::start-proxy&lt;/span&gt;&lt;span&gt; [start-proxy &lt;/span&gt;&lt;span&gt;::prepare&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::prepare&lt;/span&gt;&lt;span&gt; [prepare &lt;/span&gt;&lt;span&gt;::render&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::render&lt;/span&gt;&lt;span&gt; [render/render &lt;/span&gt;&lt;span&gt;::exec&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::exec&lt;/span&gt;&lt;span&gt; [(&lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; run/run-cmds step-fns) &lt;/span&gt;&lt;span&gt;::fix-messages&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::fix-messages&lt;/span&gt;&lt;span&gt; [fix-messages &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                      &lt;/span&gt;&lt;span&gt;::end&lt;/span&gt;&lt;span&gt; [stop]))}))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sorted-map&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;dev-wf&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;::test-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;})))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;workflow {:first-step ::start                            :wire-fn (fn [step step-fns]                                       (case step                                         ::start [start-hcloud ::start-proxy]                                         ::start-proxy [start-proxy ::prepare]                                         ::prepare [prepare ::render]                                         ::render [render/render ::exec]                                         ::exec [(partial run/run-cmds step-fns) ::fix-messages]                                         ::fix-messages [fix-messages ::end]                                         ::end [stop]))}))(comment  (into (sorted-map) (hcloud-wf {::bc/env :repl                                 ::test-name &amp;#x22;hcloud&amp;#x22;})))(def dev-wf (-&gt;workflow {:first-step ::start                         :wire-fn (fn [step step-fns]                                    (case step                                      ::start [start ::start-proxy]                                      ::start-proxy [start-proxy ::prepare]                                      ::prepare [prepare ::render]                                      ::render [render/render ::exec]                                      ::exec [(partial run/run-cmds step-fns) ::fix-messages]                                      ::fix-messages [fix-messages ::end]                                      ::end [stop]))}))(comment  (into (sorted-map) (dev-wf {::bc/env :repl                              ::test-name &amp;#x22;first&amp;#x22;})))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;goodbye-debuggers&quot;&gt;Goodbye, Debuggers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I don’t need a traditional debugger for this. Because these workflows are just pure functions that transform a map, I can “attach” any value I want to inspect to the workbench map at any step.&lt;/p&gt;
&lt;p&gt;Thanks to Clojure’s &lt;strong&gt;qualified keywords&lt;/strong&gt;, I never have to worry about naming conflicts between different steps. I can see the entire state of my infrastructure and my code in one place, instantly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Traditional DevOps workflows are often bogged down by the “Edit/Run” loop, requiring developers to constantly switch between editors, directories, and terminal tabs. BigConfig solves this by leveraging a persistent Clojure REPL to create a unified “Edit/Evaluate” environment. By orchestrating tools like Terraform through a centralized “workbench map,” BigConfig eliminates context switching and replaces traditional debugging with instant, stateful feedback. It transforms fragmented operations into a seamless, code-driven flow where infrastructure and development live in a single, interactive workspace.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;update&quot;&gt;Update&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I received an interesting question and wanted to share my response here.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;question&quot;&gt;Question&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The REPL often suffers from reproducibility issues due to out-of-order execution. Does BigConfig address this in some way?&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;answer&quot;&gt;Answer&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is addressed both by Clojure itself and within BigConfig’s architecture.&lt;/p&gt;
&lt;p&gt;Clojure provides the foundation because out-of-order execution would be detrimental to any serious development. By using Clojure for operations—rather than Python or Node—we can leverage the same robust patterns used in traditional Clojure REPL-driven development.&lt;/p&gt;
&lt;p&gt;BigConfig specifically solves this by introducing a new control flow: the workflow. A workflow is a function that invokes other functions, called steps, similar to AWS Step Functions (which served as an inspiration for BigConfig). If you are modifying a specific step, such as &lt;code dir=&quot;auto&quot;&gt;prepare&lt;/code&gt;, you would edit it like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defn&lt;/span&gt;&lt;span&gt; prepare [opts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;; you are developing the code of this step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;into&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sorted-map&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;hcloud-wf&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;::bc/env&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:repl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                   &lt;/span&gt;&lt;span&gt;::test-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;hcloud&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}))))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;In my editor, I simply press &lt;code dir=&quot;auto&quot;&gt;C-c C-c&lt;/code&gt; to evaluate everything in the correct order. I can then inspect the workbench map in one window and the stdout in another, ensuring a predictable and reproducible state every time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/repl.BMVmBYla_Z1BwORS.webp&quot; alt=&quot;repl&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;4480&quot; height=&quot;2520&quot;&gt;&lt;/p&gt;</content:encoded><category>shell</category><category>bigconfig</category><category>clojure</category><category>gitops</category><category>terraform</category><category>ansible</category><category>helm</category><category>kubectl</category><category>gRPC</category></item><item><title>Hypermedia: The New/Old Architecture Set to Revolutionize Web and Mobile Development</title><link>https://www.bigconfig.it/blog/hypermedia-the-new-old-architecture-set-to-revolutionize-web-and-mobile-development/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/hypermedia-the-new-old-architecture-set-to-revolutionize-web-and-mobile-development/</guid><description>Hypermedia is the tech everyone is talking about, yet it remains a bit of a mystery for many. After diving deep, I’m convinced this new/old approach is not just a trend—it’s poised to disrupt how we build applications on both the web and mobile.

The strategic shift towards Hypermedia Driven Applications (HDA) fundamentally alters the economics of development, making internal solutions significantly more viable. As the cost of building web and mobile experiences drops, so too does the traditional calculus of &quot;build vs. buy.

To capitalize on this change, our next major step is integrating Internal Developer Portal capabilities directly into BigConfig. This addition completes BigConfig&apos;s transformation into the essential, comprehensive library for scalable operations, providing core building blocks like configuration as code, workflow orchestration, scaffolding, robust persistency, auditing, control planes, and now, internal developer portals.

</description><pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hypermedia is the tech everyone is talking about, yet it remains a bit of a mystery for many. After diving deep, I’m convinced this new/old approach is not just a trend—it’s poised to disrupt how we build applications on both the web and mobile.&lt;/p&gt;
&lt;p&gt;Here are my thoughts, inspired by the powerful concepts driving this shift.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-a-hypermedia-driven-application-hda&quot;&gt;What is a Hypermedia Driven Application (HDA)?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Hypermedia Driven Application (HDA) architecture—championed by projects like &lt;a href=&quot;https://htmx.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; htmx &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;—is an ingenious fusion of old and new.&lt;/p&gt;
&lt;p&gt;It takes the simplicity and flexibility of traditional Multi-Page Applications (MPAs) and marries it with the smooth user experience (UX) typically found only in complex Single-Page Applications (SPAs).&lt;/p&gt;
&lt;p&gt;After struggling to grasp this concept myself, I decided the best way to understand it was to build one. That journey led me to the fascinating world of &lt;a href=&quot;https://github.com/andersmurphy/hyperlith&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Hyperlith  &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and &lt;a href=&quot;https://data-star.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Datastar &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;. The ideas behind Hyperlith are truly revolutionary. Hyperlith stands for Hypermedia and Monolith.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-philosophy-dumb-terminals-smart-backend&quot;&gt;The Philosophy: Dumb Terminals, Smart Backend&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In the HDA model, your application’s logic, state, and rendering all reside in the backend.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your distributed application runs entirely on the server.&lt;/li&gt;
&lt;li&gt;The browser or mobile app becomes a dumb terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pages are conceptually replaced by views (for both web and mobile). When a user interacts with a control (like a button), it triggers an action on the server. This action recalculates the current view, which is then pushed to all relevant users.&lt;/p&gt;
&lt;p&gt;Think about a traditional leaderboard implemented as a web page: instead of polling or building complex real-time infrastructure, the view automatically updates for all online users as soon as the data changes on the server. The multiplayer capabilities are practically trivial!&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-disruption-why-hdas-win&quot;&gt;The Disruption: Why HDAs Win&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This server-side approach has several game-changing implications for development:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;unified-codebase-for-web-and-mobile&quot;&gt;Unified Codebase for Web and Mobile&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;With frameworks like Hyperlith and Datastar, you can achieve amazing results without starting from scratch. Instead of managing separate SPAs or native mobile apps, you manage a single backend that renders views suitable for any client.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;lightning-fast-mobile-development&quot;&gt;Lightning-Fast Mobile Development&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Mistake a typo in your app? Need to tweak a UI element? In the HDA model, you simply fix the code on the server. You don’t have to upload a new mobile app version and wait for store approval. The changes are live instantly for all users.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;effortless-ab-testing&quot;&gt;Effortless A/B Testing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Since all rendering happens on the server, implementing server-side A/B testing becomes incredibly simple. You can easily serve different versions of a view to different user segments without complicated client-side routing or code splitting.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;simplified-shared--user-specific-views&quot;&gt;Simplified Shared &amp;#x26; User-Specific Views&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The HDA architecture makes it easier to create views that are shared among users (like a stock ticker) and then progressively make portions user-specific (like a profile widget).&lt;/p&gt;
&lt;p&gt;It’s easier to start with a shared view and then make it specific than to start with isolated pages and struggle to add shared portions later.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;efficient-updates-and-network-performance&quot;&gt;Efficient Updates and Network Performance&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Many people worry that sending the complete HTML on every change won’t scale, especially on mobile networks. This concern is often misplaced.&lt;/p&gt;
&lt;p&gt;Instead of thinking of it as constant full-page refreshes, imagine it as a stream of bytes, highly compressed with algorithms like Brotli.&lt;/p&gt;
&lt;p&gt;The compression acts as your caching and diffing mechanism, achieving compression ratios of 100 or more! This efficiency is critical: a $1.5 MB/s sustained throughput on the server is equivalent to handling $150 MB/s without compression, drastically reducing infrastructure costs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;seamless-user-experience-with-dom-morphing&quot;&gt;Seamless User Experience with DOM Morphing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Modern apps like YouTube and Spotify offer that polished mini-player experience, letting you keep watching content while navigating to a new page. This amazing user experience is a natural fit for HDAs, thanks to a technique called DOM morphing.&lt;/p&gt;
&lt;p&gt;Instead of completely swapping out the entire page (like in a traditional MPA) or relying on complex client-side routing (like in an SPA), the HDA backend sends the new rendered view. The client then intelligently morphs the existing DOM to match the new version. This means that persistent elements—like an ongoing video player or music widget—can retain their place, focus, and internal state, resulting in a smooth, interruption-free user flow.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;bigconfig-and-internal-developer-portals&quot;&gt;BigConfig and Internal Developer portals&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The strategic shift towards Hypermedia Driven Applications (HDA) fundamentally alters the economics of development, making internal solutions significantly more viable. As the cost of building web and mobile experiences drops, so too does the traditional calculus of “build vs. buy.”&lt;/p&gt;
&lt;p&gt;To capitalize on this change, our next major step is integrating Internal Developer Portal capabilities directly into BigConfig. This addition completes BigConfig’s transformation into the essential, comprehensive library for scalable operations, providing core building blocks like configuration as code, workflow orchestration, scaffolding, robust persistency, auditing, control planes, and now, internal developer portals.&lt;/p&gt;
&lt;p&gt;I’m working on the code right now. Stay tuned, I will update the blog post as soon as I have the first version.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;see-it-in-action&quot;&gt;See it in Action&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/hda.a9a1Uimx_Z174yKQ.webp&quot; alt=&quot;Alt text&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;4480&quot; height=&quot;2520&quot;&gt;&lt;/p&gt;
&lt;p&gt;I built a simple HDA &lt;a href=&quot;https://example.bigconfig.it/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; example &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; to prove the concept. When you open it in two separate browser tabs and interact with one, the other updates instantly.&lt;/p&gt;
&lt;p&gt;The most incredible part? Zero JavaScript was required to implement the real-time, multiplayer capabilities. The state is managed centrally and seamlessly on the server.&lt;/p&gt;
&lt;p&gt;The future of application development is here, and it looks a lot like the past—just smarter, faster, and much more scalable.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>hypermedia</category><category>htmx</category><category>datastar</category><category>hyperlith</category><category>bigconfig</category><category>internal developer platform</category><category>mobile</category><category>web</category></item><item><title>Demystifying the control plane: the easy upgrade path from GitOps with BigConfig</title><link>https://www.bigconfig.it/blog/demystifying-the-control-plane-the-easy-upgrade-path-from-gitops-with-bigconfig/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/demystifying-the-control-plane-the-easy-upgrade-path-from-gitops-with-bigconfig/</guid><description>For many engineering teams, GitOps has been a game-changer, providing a declarative way to manage infrastructure and applications. But as complexity grows, you may find your processes hitting a ceiling. The natural next step? Upgrading to a control Plane.

Often, teams hesitate, believing this shift requires a costly, painful rewrite of their entire configuration management solution when it is written in Terraform or Ansible. They fear increased cognitive load and massive sunk costs.

This is where BigConfig changes the narrative. With BigConfig, you can upgrade your existing GitOps solution to a full-fledged control plane without rewriting everything from scratch or overwhelming your team.

</description><pubDate>Wed, 05 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For many engineering teams, GitOps has been a game-changer, providing a declarative way to manage infrastructure and applications. But as complexity grows, you may find your processes hitting a ceiling. The natural next step? Upgrading to a control plane.&lt;/p&gt;
&lt;p&gt;Often, teams hesitate, believing this shift requires a costly, painful rewrite of their entire configuration management solution when it is written in Terraform or Ansible. They fear increased cognitive load and massive sunk costs.&lt;/p&gt;
&lt;p&gt;This is where BigConfig changes the narrative.&lt;/p&gt;
&lt;p&gt;With BigConfig, you can upgrade your existing GitOps solution to a full-fledged control plane without rewriting everything from scratch or overwhelming your team.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;control-plane-components&quot;&gt;Control plane components&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A control plane is essentially a brain that manages your entire system. It operates on three fundamental components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The API: The interface where users (or other systems) declare &lt;em&gt;what&lt;/em&gt; they want.&lt;/li&gt;
&lt;li&gt;The desired state: The single source of truth—the definitive record of &lt;em&gt;how&lt;/em&gt; the system should look.&lt;/li&gt;
&lt;li&gt;The control loop: The tireless worker that continuously observes the actual state and makes changes to match the desired state.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;desired-state-persistence&quot;&gt;Desired state persistence&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The desired state is the heart of the control plane, and it needs to be persisted in a reliable, flexible way.&lt;/p&gt;
&lt;p&gt;When dealing with complex, nested configuration data—like a complete picture of your developers, teams, environments, policies, and services—a traditional SQL data model can quickly become cumbersome. Instead, storing the state as a flexible, nested map is far more efficient and manageable.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;audit-log&quot;&gt;Audit log&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A key requirement for a robust control plane is a detailed audit log. We don’t just want the final state; we want to know &lt;em&gt;how&lt;/em&gt; it got there like we do in GitOps.&lt;/p&gt;
&lt;p&gt;BigConfig uses a pattern often referred to as Event Sourcing (though we’ll keep it simple here). Instead of storing only the current state, we store &lt;em&gt;all the events&lt;/em&gt; that generated that state. This gives you an immutable, perfect audit trail and allows you to rebuild the state at any point in time.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bigconfig-store&quot;&gt;BigConfig Store&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;BigConfig tackles this persistence challenge by leveraging Redis Sorted Sets to store both the events and periodic snapshots of the state. This provides high performance and structure for our time-series data.&lt;/p&gt;
&lt;p&gt;Let’s look at a conceptual example in code (using Clojure) that demonstrates how BigConfig manages concurrent updates without inconsistency:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;deftest&lt;/span&gt;&lt;span&gt; inc-test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;testing&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;inc from 2 instances works&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; [port (&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;system-state&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                   &lt;/span&gt;&lt;span&gt;:redis/server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                   &lt;/span&gt;&lt;span&gt;:port&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;default-opts&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:store-key&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;random-uuid&lt;/span&gt;&lt;span&gt;) str)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;:initial-state&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:cnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;:wcar-opts&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:spec&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;:port&lt;/span&gt;&lt;span&gt; port}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                    &lt;/span&gt;&lt;span&gt;:pool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:none&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;:snapshot-every&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;:business-fn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; [state _event _timestamp]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; state &lt;/span&gt;&lt;span&gt;:cnt&lt;/span&gt;&lt;span&gt; inc))}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;store1 (&lt;/span&gt;&lt;span&gt;sut/store!&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default-opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;store2 (&lt;/span&gt;&lt;span&gt;sut/store!&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default-opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;times &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dotimes&lt;/span&gt;&lt;span&gt; [_ times]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sut/handle!&lt;/span&gt;&lt;span&gt; store1 {&lt;/span&gt;&lt;span&gt;:op&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:inc&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sut/handle!&lt;/span&gt;&lt;span&gt; store2 {&lt;/span&gt;&lt;span&gt;:op&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:inc&lt;/span&gt;&lt;span&gt;}))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [{&lt;/span&gt;&lt;span&gt;:cnt&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; times &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)} {&lt;/span&gt;&lt;span&gt;:cnt&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; times &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)}] [@store1 @store2])))))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; (system-state)                   :redis/server                   :port)          default-opts {:store-key (-&gt; (random-uuid) str)                        :initial-state {:cnt 0}                        :wcar-opts {:spec {:port port}                                    :pool :none}                        :snapshot-every 2                        :business-fn (fn [state _event _timestamp]                                       (update state :cnt inc))}          store1 (sut/store! default-opts)          store2 (sut/store! default-opts)          times 10]      (dotimes [_ times]        (sut/handle! store1 {:op :inc})        (sut/handle! store2 {:op :inc}))      (is (= [{:cnt (- (* times 2) 1)} {:cnt (* times 2)}] [@store1 @store2])))))&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;In this counter test, we define key parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;:store-key&lt;/code&gt;: The key used in Redis.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;:initial-state&lt;/code&gt;: The starting Desired State (e.g., &lt;code dir=&quot;auto&quot;&gt;{:cnt 0}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;:snapshot-every&lt;/code&gt;: Defines how frequently a state snapshot is saved to Redis, dramatically accelerating recovery.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;:business-fn&lt;/code&gt;: The pure function that defines how the state changes based on an event. Purity is critical; it guarantees that replaying events (for recovery or auditing) yields the exact same Desired State.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice how two concurrent writers (&lt;code dir=&quot;auto&quot;&gt;store1&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;store2&lt;/code&gt;) process events via the &lt;code dir=&quot;auto&quot;&gt;handle!&lt;/code&gt; function, yet the final state remains consistent. This is the power of a well-architected control plane store. The desired state can grow to encompass everything: your list of developers, teams, services, policies—all in one place.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;bigconfig-workflow&quot;&gt;BigConfig Workflow&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The final component is the control loop. It is a dedicated &lt;em&gt;reader&lt;/em&gt; of the desired state. Its sole purpose is to observe the actual state of your infrastructure and ensure it &lt;em&gt;matches&lt;/em&gt; the desired state specified in the store.&lt;/p&gt;
&lt;p&gt;BigConfig seamlessly integrates here.&lt;/p&gt;
&lt;p&gt;BigConfig workflow takes the comprehensive desired state, renders it into the necessary configuration files (reused from an existing &lt;strong&gt;Terraform&lt;/strong&gt; or &lt;strong&gt;Ansible&lt;/strong&gt; GitOps project), and applies them to the target environment. You keep your existing configuration logic while BigConfig provides the intelligence and state management &lt;em&gt;above&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;The result? You upgrade from configuration automation to a more dynamic, auditable, and consistent control plane without the nightmare of a complete rewrite.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;example-code&quot;&gt;Example code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’m working on the code right now. Stay tuned, I will update the blog post as soon as I have the first version.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The shift from GitOps to a full-fledged control plane is the inevitable evolution for engineering teams grappling with growing complexity, but it doesn’t have to be a painful, expensive overhaul. BigConfig provides the crucial bridge, allowing you to move beyond simple configuration automation without sacrificing your investment in existing Terraform or Ansible logic. By introducing a robust control plane built on a flexible desired state, an immutable audit log (stored efficiently in Redis Sorted Sets), and a workflow engine, BigConfig ensures consistency and concurrency even as your system scales. This model separates the declaration of &lt;em&gt;what&lt;/em&gt; you want from the execution of &lt;em&gt;how&lt;/em&gt; to get it, enabling you to gain the dynamic, auditable, and resilient desired state persistence of a modern control plane while retaining your established configuration workflows.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>gitops</category><category>control plane</category><category>bigconfig</category></item><item><title>Local-First GitHub Actions Strategy</title><link>https://www.bigconfig.it/blog/github-actions/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/github-actions/</guid><description>If you’ve spent any significant time with GitHub Actions (GHA), you know the drill: it can be a massive time-saver, but when things go wrong, the development loop is painfully slow. Committing, pushing, waiting for the run to fail, and then repeating… it’s a productivity killer. Over time, I’ve refined a strategy that cuts this frustrating cycle short. My philosophy is simple: Avoid any GitHub Actions feature that isn’t available or easy to replicate locally.

</description><pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve spent any significant time with GitHub Actions (GHA), you know the drill: it can be a massive time-saver, but when things go wrong, the development loop is painfully slow. Committing, pushing, waiting for the run to fail, and then repeating… it’s a productivity killer.&lt;/p&gt;
&lt;p&gt;Over time, I’ve refined a strategy that cuts this frustrating cycle short. My philosophy is simple: Avoid any GitHub Actions feature that isn’t available or easy to replicate locally.&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://github.com/amiorin/big-config/blob/main/.github/workflows/ci.yml&quot; target=&quot;_blank&quot;&gt; &lt;span&gt;Show me the code&lt;/span&gt; &lt;/a&gt;  &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; 
&lt;div&gt;&lt;h2 id=&quot;the-slow-ci-development-loop&quot;&gt;The Slow CI Development Loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The biggest headache with GitHub Actions isn’t writing the YAML; it’s debugging it.&lt;/p&gt;
&lt;p&gt;When your build or test step fails, the quickest way to fix it is to reproduce the failure on your local machine. But if your CI heavily relies on GHA features, that local reproduction becomes difficult or impossible.&lt;/p&gt;
&lt;p&gt;This leads to the dreaded “commit-and-wait” debugging cycle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;docker-first-ci-recipe&quot;&gt;Docker-First CI Recipe&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My solution is to treat GitHub Actions purely as an execution environment for a set of simple, executable shell commands. Everything complex—the build, the test environment, the dependencies—is encapsulated within a Docker container.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;my-typical-ci-workflow-steps&quot;&gt;My Typical CI Workflow Steps:&lt;/h3&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Free Disk Space: My development container is a beast (around 18 GB), so the first step is necessary cleanup.&lt;/li&gt;
&lt;li&gt;Download Devcontainer: Pull the large, pre-built image that looks exactly like my devmachine.&lt;/li&gt;
&lt;li&gt;Clone the Repository: Standard checkout action.&lt;/li&gt;
&lt;li&gt;Run Tests Inside the Container: Execute the tests by using &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt; and mounting the repository volume.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I don’t write my own GitHub Actions. I rely only on standard shell commands and &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt;. If I need something complex, I use battle-tested, popular actions written by others (like &lt;code dir=&quot;auto&quot;&gt;actions/checkout&lt;/code&gt;), but I don’t try to author my own actions.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;developing-ci-without-committing&quot;&gt;Developing CI Without Committing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The real game-changer is the ability to run and debug the CI code locally without ever making a commit. This is the inner loop that makes CI development fast.&lt;/p&gt;
&lt;p&gt;Since my CI logic is just a single step executed by GHA, I can extract and run that exact step on my machine.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-local-debug-command&quot;&gt;The Local Debug Command&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This single line of bash and command-line tools allows me to extract the critical &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; step from my workflow file and execute it locally:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.github/workflows/ci.yml&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;yaml&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.jobs.test.steps[3].run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GITHUB_WORKSPACE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;pwd&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;cat .github/workflows/ci.yml&lt;/code&gt;: Reads the workflow file.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;jet -i yaml -o json&lt;/code&gt;: Converts the YAML to JSON (I prefer using &lt;code dir=&quot;auto&quot;&gt;jet&lt;/code&gt;, but you could use other tools).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;jq -r .jobs.test.steps[3].run&lt;/code&gt;: Extracts the exact &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; command script from the correct job and step.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GITHUB_WORKSPACE=$(pwd) bash -s&lt;/code&gt;: Executes the extracted script in a new shell, mimicking how GHA works by defining the necessary &lt;code dir=&quot;auto&quot;&gt;GITHUB_WORKSPACE&lt;/code&gt; environment variable and feeding the script in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This technique is incredibly fast. I can iterate on my &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt; command or environment variables until the script runs perfectly, all locally, and only then do I commit and push.&lt;/p&gt;
&lt;p&gt;Example of CI step extracted with the singe line of bash above.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vscode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GITHUB_WORKSPACE=&lt;/span&gt;&lt;span&gt;$GITHUB_WORKSPACE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$GITHUB_WORKSPACE&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$GITHUB_WORKSPACE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ghcr.io/amiorin/big-container:latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fish&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cd $GITHUB_WORKSPACE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;&amp;#x26; bb build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;&amp;#x26; direnv allow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;&amp;#x26; direnv exec . just test&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;the-performance-trade-off-and-the-ultimate-fix&quot;&gt;The Performance Trade-off and the Ultimate Fix&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My strategy has a performance “tax”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5-10 minutes to free disk space.&lt;/li&gt;
&lt;li&gt;5-10 minutes to download the 18 GB devcontainer image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the unavoidable cost of a standardized, containerized environment on GitHub’s shared runners. If this 10-20 minute setup time is simply unacceptable for your project, there is one simple, permanent solution:&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://docs.github.com/en/actions/concepts/runners/self-hosted-runners&quot; target=&quot;_blank&quot;&gt; &lt;span&gt;Use a Self-Hosted Runner&lt;/span&gt; &lt;/a&gt;  &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; 
&lt;p&gt;By using a self-hosted runner, your CI build machine is always ready. Your container is already pulled, and disk space is managed. Your CI job, once triggered, can jump straight to running tests in seconds, avoiding the entire pain of figuring out GitHub’s official caching architecture.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;GitHub Actions is a powerful tool, but its debugging experience can be maddening. By adopting a local-first, Docker-centric approach and reducing GHA to a simple execution engine, you can dramatically accelerate your CI development loop and reclaim your productivity.&lt;/p&gt;
&lt;p&gt;Think locally, test locally, and only then commit globally. It’s the fastest path to a green build.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>local-first</category><category>github actions</category><category>devcontainer</category><category>devmachine</category></item><item><title>Control planes in BigConfig</title><link>https://www.bigconfig.it/blog/control-plane-in-big-config/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/control-plane-in-big-config/</guid><description>A control plane acts as an API for provisioning and managing infrastructure. Well-known examples include AWS itself and Kubernetes (K8s).

A common pain point when using infrastructure-as-code (IaC) tools like Terraform (using HCL) and GitOps is the difficulty in transitioning or upgrading to an internal control plane.

Teams that have invested significant time in writing Terraform’s HCL code traditionally face a complete rewrite to build a custom control plane API over their existing infrastructure definitions.

This limitation is no longer the case with BigConfig. We&apos;ve demonstrated that a functional control plane can be created with as little as 200 lines of code, leveraging existing Terraform configurations.

</description><pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;control plane&lt;/strong&gt; acts as an &lt;strong&gt;API&lt;/strong&gt; for provisioning and managing infrastructure. Well-known examples include &lt;strong&gt;AWS&lt;/strong&gt; itself and &lt;strong&gt;Kubernetes (K8s)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A common pain point when using infrastructure-as-code (IaC) tools like &lt;strong&gt;Terraform&lt;/strong&gt; (using HCL) and &lt;strong&gt;GitOps&lt;/strong&gt; is the difficulty in transitioning or upgrading to an internal control plane. Teams that have invested significant time in writing Terraform’s &lt;strong&gt;HCL code&lt;/strong&gt; traditionally face a complete rewrite to build a custom control plane API over their existing infrastructure definitions.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;transforming-terraform-into-a-control-plane&quot;&gt;Transforming Terraform into a Control Plane&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This limitation is no longer the case with &lt;strong&gt;BigConfig&lt;/strong&gt;. We’ve demonstrated that a functional control plane can be created with as little as &lt;strong&gt;200 lines of code&lt;/strong&gt;, leveraging existing Terraform configurations.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;example-scenario-secure-aws-iam-user-management&quot;&gt;Example Scenario: Secure AWS IAM User Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Consider the need to create a &lt;strong&gt;control plane for managing AWS users&lt;/strong&gt;. Directly exposing the comprehensive &lt;strong&gt;IAM API&lt;/strong&gt; to engineers is a security risk and the GitOps approach is not scaling. Instead, a minimal API is required that allows engineers to only &lt;strong&gt;create, delete, and rename users&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-key-to-bridging-the-gap&quot;&gt;The Key to Bridging the Gap&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The secret to transforming any Terraform code into a control plane lies in how Terraform manages resource identifiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terraform HCL Specification:&lt;/strong&gt; Resources are defined with the syntax &lt;code dir=&quot;auto&quot;&gt;resource &quot;&amp;#x3C;TYPE&gt;&quot; &quot;&amp;#x3C;LABEL&gt;&quot;&lt;/code&gt;. The &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;LABEL&gt;&lt;/code&gt; is a static, user-defined identifier that acts as a &lt;strong&gt;primary key&lt;/strong&gt; within the configuration file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform State:&lt;/strong&gt; The &lt;strong&gt;Terraform state file&lt;/strong&gt; maintains a crucial mapping between this static HCL &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;LABEL&gt;&lt;/code&gt; and the actual primary key used by the cloud provider (e.g., the &lt;strong&gt;ARN&lt;/strong&gt; in AWS).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control Plane Requirement:&lt;/strong&gt; In a control plane, the list of managed resources is &lt;strong&gt;dynamic&lt;/strong&gt;, not static like in an HCL file. To function correctly, the equivalent of the Terraform &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;LABEL&gt;&lt;/code&gt; must be &lt;strong&gt;persisted between invocations&lt;/strong&gt; and &lt;strong&gt;generated automatically&lt;/strong&gt; by the control plane logic, effectively mimicking the role of the HCL file’s label but in a dynamic context.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;BigConfig&lt;/strong&gt; addresses this by programmatically managing this dynamic labeling and state mapping, enabling the existing Terraform code to be driven by a new, restrictive, and secure API—the control plane.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;try-the-control-plane-cli-locally&quot;&gt;Try the Control Plane CLI Locally&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;You can test this powerful concept right now, even without an AWS account:&lt;/p&gt;

&lt;ol role=&quot;list&quot;&gt;
&lt;li&gt;Install the Control Plane CLI:
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;clojure&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Ttools&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;big-config/control-plane&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;{:git/sha &quot;aff7c186999c19ad2b4f07752abd13ec5b3b6651&quot; :git/url &quot;https://github.com/amiorin/big-config.git&quot;}&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ctlp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Create a convenient alias:
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;clojure -Tctlp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Show the help
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Define a new user via the API (CLI) :
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create-user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;&quot;alice&quot;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Like any control plane, it maintains a desired state (saved locally). Let’s inspect the &lt;strong&gt;Control Plane State&lt;/strong&gt; (not the Terraform state):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;print-state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The output shows how the user is now managed by a &lt;strong&gt;random UUID&lt;/strong&gt; that is persisted across commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:region&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;eu-west-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:aws-account-id&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;251213589273&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:module&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:users&lt;/span&gt;&lt;span&gt; {#uuid &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ee39e089-9697-4db9-80d9-c931af720535&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;alice&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:target-dir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dist&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Finally, apply this desired state to your infrastructure using standard Terraform/OpenTofu commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Run tofu plan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff-state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Run tofu apply&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply-state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This dynamic state management is powered by the &lt;strong&gt;Prevalent System design pattern&lt;/strong&gt; and uses pure functions to handle state logic. You can explore the implementation details in the GitHub repository.&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://github.com/amiorin/big-config/blob/control-plane/src/clj/control_plane.clj&quot;&gt; &lt;span&gt;Show me the code&lt;/span&gt; &lt;/a&gt;  &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; 
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Terraform remains the go-to tool for modern DevOps and infrastructure management. While some platform teams have attempted to replace it with control planes, this demonstration showed the true strength of Terraform: its ability to abstract the low-level cloud provider APIs. This capability actually makes control plane implementation faster and more efficient rather than competing with it.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>control plane</category><category>terraform</category><category>bigconfig</category><category>gitops</category><category>hcl</category><category>iac</category></item><item><title>Configuration Hell? How BigConfig Tames the Modern Dev Environment</title><link>https://www.bigconfig.it/blog/configuration-hell-how-bigconfig-tames-the-modern-dev-environment/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/configuration-hell-how-bigconfig-tames-the-modern-dev-environment/</guid><description>Setting up a local development environment today is rarely a trivial matter. The days of simply git clone and npm install are long gone. Modern architectures, particularly those embracing microservices, polyglot persistence, and cloud-native practices, have turned the humble setup process into a multi-layered nightmare.

If you&apos;ve ever spent an afternoon debugging why your local database port clashes with your integration environment, or wrestled with five different tools requiring three different credential formats, you know the pain.

Let&apos;s dive into a concrete example — a complex but typical setup — and see how BigConfig transforms this chaos into an automated, zero-cost development experience.

</description><pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Setting up a local development environment today is rarely a trivial matter. The days of simply &lt;code dir=&quot;auto&quot;&gt;git clone&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt; are long gone. Modern architectures, particularly those embracing microservices, polyglot persistence, and cloud-native practices, have turned the humble setup process into a multi-layered nightmare.&lt;/p&gt;
&lt;p&gt;If you’ve ever spent an afternoon debugging why your local database port clashes with your integration environment, or wrestled with five different tools requiring three different credential formats, you know the pain.&lt;/p&gt;
&lt;p&gt;Let’s dive into a concrete example — a complex but typical setup — and see how &lt;a href=&quot;https://www.bigconfig.it/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; transforms this chaos into an automated, zero-cost development experience.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-configuration-challenge-a-deep-dive-into-rama-jdbc&quot;&gt;The Configuration Challenge: A Deep Dive into Rama JDBC&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In our case, we’re configuring the &lt;a href=&quot;https://github.com/amiorin/rama-jdbc/blob/main/.big-config/src/rama_jdbc.clj&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; rama-jdbc &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; development environment. Rama JDBC implements the robust &lt;strong&gt;Outbox Pattern&lt;/strong&gt; using SQL triggers. This already introduces a host of configuration demands:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cloud-Native Credentials:&lt;/strong&gt; Connecting to our staging &lt;strong&gt;AWS RDS&lt;/strong&gt; instance to introspect and replicate the target schema requires fetching credentials securely from &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polyglot Tooling:&lt;/strong&gt; Our system is a mix of languages and utilities:
&lt;ul&gt;
&lt;li&gt;Backend services are in &lt;strong&gt;Go&lt;/strong&gt;, using &lt;a href=&quot;https://github.com/rubenv/sql-migrate&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; sql-migrate &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; for database migrations.&lt;/li&gt;
&lt;li&gt;Automation and utility scripts are implemented using &lt;strong&gt;Babashka&lt;/strong&gt; and the &lt;strong&gt;Just&lt;/strong&gt; task runner.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local Database Mirroring:&lt;/strong&gt; To ensure parity, we need a local SQL database. We use &lt;a href=&quot;https://f1bonacc1.github.io/process-compose/cli/process-compose/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Process Compose &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; to manage its lifecycle, which needs distinct configurations for dev and test.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL UI Pain Points:&lt;/strong&gt; For schema inspection, we use the &lt;strong&gt;DuckDB UI&lt;/strong&gt;. Critically, DuckDB only supports connection via an initialization SQL file, &lt;em&gt;not&lt;/em&gt; environment variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conflicting Formats:&lt;/strong&gt; We have tools that require a simple &lt;code dir=&quot;auto&quot;&gt;host&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;port&lt;/code&gt;, while others demand a full &lt;strong&gt;JDBC URL&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;the-dynamic-port-nightmare&quot;&gt;The Dynamic Port Nightmare&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The configuration complexity peaks when dealing with &lt;strong&gt;TCP ports&lt;/strong&gt;. To manage multiple concurrent development contexts (e.g., a &lt;strong&gt;Dev&lt;/strong&gt; feature branch and a &lt;strong&gt;Test&lt;/strong&gt; branch), we use &lt;strong&gt;Git Worktrees&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This means ports cannot be the default ones; they must be &lt;em&gt;dynamic but deterministic&lt;/em&gt;, dependent on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;service name&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;path of the current working directory&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In total, this setup requires managing &lt;strong&gt;five dynamic ports&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL Server (Dev &amp;#x26; Test instances)&lt;/li&gt;
&lt;li&gt;Process Compose (Dev &amp;#x26; Test instances)&lt;/li&gt;
&lt;li&gt;DuckDB UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manually managing and passing these five dynamic ports and all the various configuration formats (JDBC URL, host/port, SQL init file) across &lt;strong&gt;Go, Babashka, Just, DuckDB,&lt;/strong&gt; and the main Clojure application is a recipe for errors and lost development time.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-bigconfig-solution-configuration-as-code&quot;&gt;The BigConfig Solution: configuration as code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is where &lt;strong&gt;BigConfig&lt;/strong&gt; steps in. By leveraging a single, centralized configuration system written in Clojure, we can define the &lt;em&gt;logic&lt;/em&gt; for all these variables, ensuring consistency across every tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BigConfig&lt;/strong&gt; allows us to define a master configuration where:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Port Calculation Logic is Centralized:&lt;/strong&gt; A single Clojure function can take the &lt;code dir=&quot;auto&quot;&gt;(service-name path)&lt;/code&gt; as input and deterministically calculate the correct TCP port for the current worktree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Format Transformation is Automated:&lt;/strong&gt; The same central configuration can:
&lt;ul&gt;
&lt;li&gt;Calculate the &lt;strong&gt;host&lt;/strong&gt; and &lt;strong&gt;port&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Automatically assemble the &lt;strong&gt;JDBC URL&lt;/strong&gt; for Clojure tools.&lt;/li&gt;
&lt;li&gt;Generate the specific &lt;strong&gt;SQL initialization file&lt;/strong&gt; required by DuckDB.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Credentials Injection:&lt;/strong&gt; Logic to securely fetch credentials from AWS Secrets Manager is handled once, and the resulting secrets are injected into the necessary configurations for both the Go migrations and the local server.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;zero-cost-automation&quot;&gt;Zero-Cost Automation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The biggest win is the developer experience. The entire end-to-end setup, which was a “nightmare” of manual steps, is now reduced to a single, idempotent command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;Babashka&lt;/strong&gt; task runner uses &lt;strong&gt;BigConfig&lt;/strong&gt; to coordinate everything: calculating ports, generating configuration files, and ensuring full parity between the local development container and the &lt;strong&gt;GitHub CI Runner&lt;/strong&gt;. The “build” step becomes a &lt;strong&gt;zero-cost&lt;/strong&gt; operation that simply ensures your environment is perfectly configured and ready to go.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-repl-advantage-speed-at-scale&quot;&gt;The REPL Advantage: Speed at Scale&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ultimately, development productivity is measured by the speed of the feedback loop. Even with a complex, port-heavy environment, the &lt;strong&gt;Clojure REPL&lt;/strong&gt; workflow remains lightning-fast.&lt;/p&gt;
&lt;p&gt;Most of the time, development is spent hot-reloading code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Change code → Evaluate expression → Inspect result.&lt;/strong&gt; This takes milliseconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Crucially, when a database change &lt;em&gt;is&lt;/em&gt; necessary, BigConfig’s automation keeps the environment responsive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Need a new migration → Rebuild the database.&lt;/strong&gt; This, too, takes milliseconds, thanks to the deterministic and automated environment setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;BigConfig&lt;/strong&gt; doesn’t just manage complexity; it preserves the core advantage of Clojure — the immediate, fluid feedback loop of the REPL — even when faced with the configuration hurdles of a complex, modern, polyglot application. It allows you to focus on the code, not the configuration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You can verify the developer experience described here. Just clone &lt;a href=&quot;https://github.com/amiorin/rama-jdbc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; rama-jdbc &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; inside &lt;a href=&quot;https://github.com/amiorin/big-container&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; big-container &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ghcr.io/amiorin/big-container&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/amiorin/rama-jdbc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspaces/rama-jdbc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspaces/rama-jdbc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# First time will take minutes and devenv progress is not showed in the terminal.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Second time will take milliseconds&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt; bb build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start postgres and run the migrations for the dev environment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;just&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pc-dev&lt;/span&gt;&lt;span&gt; &amp;#x26;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Run the test for the test environment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;clojure&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-M:shared:test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>configuration</category><category>devmachine</category><category>devcontainer</category><category>zero-cost</category><category>rama-jdbc</category></item><item><title>A New Approach to Dotfiles management with BigConfig</title><link>https://www.bigconfig.it/blog/a-new-approach-to-dotfiles-management-with-bigconfig/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/a-new-approach-to-dotfiles-management-with-bigconfig/</guid><description>Managing dotfiles—the configuration files that personalize your user environment—is a crucial part of a developer&apos;s workflow. The go-to tools for this have long been Chezmoi and Stow. While Stow is celebrated for its simplicity, Chezmoi offers powerful templating and secret management. However, what if you need the best of both worlds?

This is where BigConfig comes in, offering a new way to manage your configurations by combining the simplicity of a declarative approach with the power of code.

</description><pubDate>Tue, 30 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Managing dotfiles—the configuration files that personalize your user environment—is a crucial part of a developer’s workflow. The go-to tools for this have long been &lt;a href=&quot;https://www.chezmoi.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Chezmoi &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and &lt;a href=&quot;https://brandon.invergo.net/news/2012-05-26-using-gnu-stow-to-manage-your-dotfiles.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Stow &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;. While Stow is celebrated for its simplicity, Chezmoi offers powerful templating and secret management. However, what if you need the best of both worlds? This is where &lt;a href=&quot;https://www.bigconfig.it/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; comes in, offering a new way to manage your configurations by combining the simplicity of a declarative approach with the power of code.&lt;/p&gt;

  
&lt;div&gt;&lt;h2 id=&quot;the-developer-experience&quot;&gt;The Developer Experience&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The developer experience with BigConfig is centered around &lt;strong&gt;Babashka&lt;/strong&gt; tasks, making it feel like a standard Clojure project. You get a clear set of commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; [macos&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;ubuntu]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; [macos&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;ubuntu]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; [macos&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;ubuntu&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;all]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;diff&lt;/code&gt; command is particularly useful, allowing you to compare your current dotfiles against the rendered versions before installing them. This gives you a clear, human-readable way to see exactly what’s about to change.&lt;/p&gt;
&lt;p&gt;By keeping the code and the dotfiles in the same repository, BigConfig provides a cohesive and powerful developer experience.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-with-the-old-ways&quot;&gt;The Problem with the Old Ways&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Like many developers, I use both macOS and Ubuntu, and the dotfiles for these environments are similar but not identical. Some configurations only exist on one platform, while others need slight tweaks. Stow, while easy for symlinking, lacks the flexibility for this kind of conditional logic. Chezmoi, on the other hand, embeds its logic within the filename, which can become unwieldy and less transparent as your needs grow.&lt;/p&gt;
&lt;p&gt;This complexity led me to seek a solution that treats dotfile management as an automation problem—one that’s best solved with code.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;introducing-bigconfig&quot;&gt;Introducing BigConfig&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;BigConfig takes a different approach. Instead of embedding logic in filenames, it uses a &lt;strong&gt;data structure&lt;/strong&gt; to define the rendering pipeline for your dotfiles. This means your configuration files are kept clean, and the logic for how they are applied is externalized in a dedicated file.&lt;/p&gt;
&lt;p&gt;The core of this system is a two-stage rendering process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stage 1:&lt;/strong&gt; Common dotfiles are merged with platform-specific ones (e.g., &lt;code dir=&quot;auto&quot;&gt;common&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;macos&lt;/code&gt; are merged into &lt;code dir=&quot;auto&quot;&gt;resources/stage-2/macos&lt;/code&gt;). At this stage, secrets and tokens are not yet resolved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stage 2:&lt;/strong&gt; The merged files from Stage 1 are rendered, and all secrets and environment variables are resolved, creating the final configuration in a &lt;code dir=&quot;auto&quot;&gt;dist/&lt;/code&gt; directory. This is the directory used for installation and comparison.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This structure allows you to maintain a clean separation of concerns: your source files (&lt;code dir=&quot;auto&quot;&gt;resources/stage-1&lt;/code&gt;) are never committed with secrets, and the final rendered output (&lt;code dir=&quot;auto&quot;&gt;dist/&lt;/code&gt;) is never committed at all. Your private information is stored securely in an &lt;code dir=&quot;auto&quot;&gt;.envrc.private&lt;/code&gt; file, which is kept out of your Git repository.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-look-at-the-code&quot;&gt;A Look at the Code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;BigConfig’s rendering logic is defined in a &lt;strong&gt;Clojure&lt;/strong&gt; data structure. Here’s a snippet that shows how this two-step process is defined:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[{&lt;/span&gt;&lt;span&gt;:template&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;stage-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:target-dir&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;resources/stage-2/%s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; profile)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:overwrite&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:transform&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;common&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;:raw&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{{ profile }}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;:raw&lt;/span&gt;&lt;span&gt;]]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;:template&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;stage-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:target-dir&lt;/span&gt;&lt;span&gt; dir&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:overwrite&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:transform&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{{ profile }}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]]}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This data structure is a clear “recipe” for how your dotfiles should be built. It tells BigConfig:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;:template&lt;/code&gt;&lt;/strong&gt;: The source directory for the files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;:target-dir&lt;/code&gt;&lt;/strong&gt;: The destination directory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;:overwrite :delete&lt;/code&gt;&lt;/strong&gt;: Ensure the target is clean before rendering.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;:transform&lt;/code&gt;&lt;/strong&gt;: The core logic for copying and rendering files. For example, &lt;code dir=&quot;auto&quot;&gt;[&quot;common&quot; :raw]&lt;/code&gt; copies the contents of the &lt;code dir=&quot;auto&quot;&gt;common&lt;/code&gt; directory without treating them as templates, while &lt;code dir=&quot;auto&quot;&gt;[&quot;{{ profile }}&quot;]&lt;/code&gt; copies the contents of the &lt;code dir=&quot;auto&quot;&gt;macos&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;ubuntu&lt;/code&gt; directory and treats the files within as templates, resolving variables and secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This declarative approach makes the process transparent and easy to debug.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;adopting-bigconfig-for-your-dotfiles&quot;&gt;Adopting BigConfig for your dotfiles&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you want to adopt BigConfig for your dotfiles, just follow this &lt;a href=&quot;https://www.bigconfig.it/use-cases/dotfiles/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; tutorial &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; or have a look at &lt;a href=&quot;https://github.com/amiorin/dotfiles-v3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; mine &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It might seem excessive to learn Clojure and a tool like BigConfig just to manage dotfiles, but the real value comes from applying that investment across a broader range of automation tasks. Clojure’s strengths—its functional nature, immutability, and powerful data manipulation capabilities—make it a strong choice for configuration-as-code. By using a single, cohesive language, you can unify your automation stack and create a more maintainable, expressive system.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-clojure-for-configuration-as-code&quot;&gt;Why Clojure for Configuration-as-Code?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Clojure’s core design principles make it an excellent fit for complex configuration tasks. Unlike static languages or rigid data formats like YAML or JSON, Clojure’s Lisp-based syntax treats &lt;strong&gt;code as data&lt;/strong&gt;. This allows you to programmatically generate and manipulate configuration files with functions, macros, and conditional logic.&lt;/p&gt;
&lt;p&gt;For example, instead of manually copying and pasting large YAML blocks across multiple environments, you could define a single function that takes environment-specific parameters (e.g., development, staging, production) and generates the correct configuration for each. This reduces redundancy and the risk of manual errors.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;expanding-beyond-dotfiles&quot;&gt;Expanding Beyond Dotfiles&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;While managing dotfiles is a great starting point, the true return on investment for learning Clojure and a tool like BigConfig lies in its applicability to other areas of the software development lifecycle.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;local-development-bootstrap&quot;&gt;Local Development Bootstrap&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A unified configuration system can automate the setup of a new developer’s machine. Instead of relying on a multi-step, error-prone manual process, you can use Clojure to define a single script that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installs necessary dependencies (e.g., Homebrew packages, language runtimes).&lt;/li&gt;
&lt;li&gt;Clones required repositories.&lt;/li&gt;
&lt;li&gt;Configures local databases, environment variables, and services.&lt;/li&gt;
&lt;li&gt;Sets up the development environment, including IDE settings and editor configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;cicd-pipelines-with-github-actions&quot;&gt;CI/CD Pipelines with GitHub Actions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;GitHub Actions workflows are defined in YAML, which can become unwieldy and difficult to manage as they grow in complexity. By using a tool that integrates Clojure, you can dynamically generate these workflow files. This allows you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a single source of truth for your build, test, and deploy steps.&lt;/li&gt;
&lt;li&gt;Parameterize workflows to run across different platforms or branches.&lt;/li&gt;
&lt;li&gt;Create reusable, composable functions to define common CI/CD patterns, making your pipelines more DRY (&lt;strong&gt;D&lt;/strong&gt;on’t &lt;strong&gt;R&lt;/strong&gt;epeat &lt;strong&gt;Y&lt;/strong&gt;ourself).&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;infrastructure-as-code-iac-with-terraform-and-ansible&quot;&gt;Infrastructure as Code (IaC) with Terraform and Ansible&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The modern approach to managing cloud infrastructure is through code, but traditional IaC tools like Terraform and Ansible have their own configuration languages (HCL and YAML, respectively). While powerful, these languages can lack the full expressiveness of a general-purpose programming language. Using Clojure, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generate Terraform HCL files&lt;/strong&gt;: Create complex Terraform configurations for large-scale cloud deployments, such as a Kubernetes cluster, by leveraging Clojure’s data manipulation capabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create dynamic Ansible playbooks&lt;/strong&gt;: Instead of a static playbook, you can write Clojure code that generates an Ansible playbook based on dynamic inputs or the state of your infrastructure. This is particularly useful for provisioning environments that vary slightly from one another.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;kubernetes-k8s-cluster-management&quot;&gt;Kubernetes (K8s) Cluster Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Kubernetes configuration is notoriously verbose, with extensive YAML manifests for deployments, services, and ingresses. By using Clojure as a configuration generator, you can simplify this process by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Templating YAML manifests&lt;/strong&gt;: Define a base template in Clojure and generate multiple, consistent Kubernetes manifests from it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automating cluster deployments&lt;/strong&gt;: Use a single script to deploy an entire application stack, from pods and services to persistent volumes and secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforcing best practices&lt;/strong&gt;: Embed validation and sanity checks within your Clojure code to ensure all generated manifests adhere to your organization’s standards before deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Stow and Chezmoi are great, but for those with complex, multi-platform needs, they can fall short. BigConfig doesn’t try to hide the underlying logic; it embraces it. It recognizes that managing dotfiles is an automation task that benefits from explicit, readable code. Just as &lt;a href=&quot;https://astro.build/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Astro &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, a modern static site generator, has gained popularity over tools like &lt;a href=&quot;https://gohugo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Hugo &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; by being more transparent and flexible, BigConfig offers a similar paradigm shift for dotfile management.&lt;/p&gt;
&lt;p&gt;Ultimately, your dotfiles are part of your automation workflow. Shouldn’t your tool for managing them be as powerful and flexible as the rest of your toolchain? BigConfig says yes.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>dotfiles</category><category>chezmoi</category><category>stow</category><category>bigconfig</category></item><item><title>Why Ansible Still Rules for Your Dev Environment</title><link>https://www.bigconfig.it/blog/why-ansible-still-rules-for-your-dev-environment/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/why-ansible-still-rules-for-your-dev-environment/</guid><description>Back in the day, before Red Hat acquired Ansible, I was using it to provision Cloudera clusters in massive data centers. And let me tell you, its killer feature wasn&apos;t some complex, enterprise-grade capability. It was pure simplicity.

You just needed SSH, and you were ready to go. The feedback loop was in seconds—a refreshing change from the slow, manual processes we were used to. It was a DevOps dream.

Then came Docker. For many use cases, containers were the new king. They offered a more lightweight, portable solution for shipping applications. And for a while, it seemed like Ansible might get relegated to the history books.

But not so fast. While Docker took over for application deployment, Ansible found its true calling: provisioning the remote development environment.

</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Back in the day, before Red Hat acquired Ansible, I was using it to provision Cloudera clusters in massive data centers. And let me tell you, its killer feature wasn’t some complex, enterprise-grade capability. It was pure &lt;strong&gt;simplicity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You just needed SSH, and you were ready to go. The feedback loop was in seconds—a refreshing change from the slow, manual processes we were used to. It was a DevOps dream.&lt;/p&gt;
&lt;p&gt;Then came Docker. For many use cases, containers were the new king. They offered a more lightweight, portable solution for shipping applications. And for a while, it seemed like Ansible might get relegated to the history books.&lt;/p&gt;
&lt;p&gt;But not so fast. While Docker took over for application deployment, Ansible found its true calling: &lt;strong&gt;provisioning the remote development environment&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-new-remote-frontier&quot;&gt;The New Remote Frontier&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Remote development has gone mainstream. Whether you’re using a GUI or a terminal, working on a remote machine makes you more productive. It’s not just a trend; it’s a fundamental shift.&lt;/p&gt;
&lt;p&gt;Think about it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No more “it works on my computer!”&lt;/strong&gt; Everyone’s environment is the same. No more chasing down dependency hell.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No more wasted time.&lt;/strong&gt; Your environment is always up to date. The days of &lt;code dir=&quot;auto&quot;&gt;git pull&lt;/code&gt; followed by hours of fixing a broken dev setup are gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More resources, less cost.&lt;/strong&gt; Remote machines can be shared, giving you access to powerful hardware for a fraction of the price.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy authentication.&lt;/strong&gt; With SSH agent forwarding, pulling and pushing changes to your code repositories is seamless.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s a developer’s paradise. But there’s a catch…&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-with-ansibles-language&quot;&gt;The Problem with Ansible’s Language&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ansible is fantastic for this, but its configuration language—YAML—has a few pain points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It’s another language to master.&lt;/strong&gt; You have to learn the specific syntax and structure, which can feel like a steep climb on top of everything else you already know.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It’s not flexible enough.&lt;/strong&gt; Manually curating dozens of YAML files—like &lt;code dir=&quot;auto&quot;&gt;packages.yml&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;repos.yml&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;ssh-config.yml&lt;/code&gt;—can be tedious and error-prone. The more complex your environment, the messier it gets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wouldn’t it be great if you could just &lt;strong&gt;write code&lt;/strong&gt; to generate your configurations?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;introducing-bigconfig-the-code-first-approach&quot;&gt;Introducing BigConfig: The Code-First Approach&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is where a tool like BigConfig comes in.  Imagine a world where you write a simple script to generate your Ansible inventory and playbook files. No more manual YAML curation. You can leverage the power of a real programming language to create dynamic configurations.&lt;/p&gt;
&lt;p&gt;Here’s the secret: &lt;strong&gt;JSON is valid YAML.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This simple fact allows us to generate JSON files with a &lt;code dir=&quot;auto&quot;&gt;.yml&lt;/code&gt; extension. BigConfig can take your code and spit out a perfect, machine-readable Ansible configuration.&lt;/p&gt;
&lt;p&gt;The next logical step? Making the provisioning of your remote development environment an &lt;strong&gt;API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You could simply send a request to a service, and it would provision a new, perfect environment for a new team member in minutes. BigConfig can handle this too, turning a manual, file-based process into a programmable, scalable service.&lt;/p&gt;
&lt;p&gt;While some tools come and go, others adapt and find their niche. For provisioning remote development environments, Ansible remains a powerhouse. It just needs a little help from the next generation of tools to unleash its full potential.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;show-me-the-code&quot;&gt;Show me the code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/amiorin/dotfiles-v3/tree/ansible/dist/machines/ansible&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; This project &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; is specific to my setup but it can be forked and adapted to your needs. I use an iMac and two minipc (soyo and firebat) to develop in the terminal using ssh and tailscale so that I can also code when I am on the road with my MacBook Pro. The ansible project provisions multiple users on both minipc. I use &lt;a href=&quot;https://nixos.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Nix &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and &lt;a href=&quot;https://devenv.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; devenv &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;.&lt;/p&gt;

 
&lt;div&gt;&lt;h2 id=&quot;go-faster&quot;&gt;Go faster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Imagine that you have to provision hundreds of users per machine. It will take too long but if every host becomes the combination of the user + the host then Ansible will provision these users in parallel because they look like different hosts.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.bigconfig.it/_astro/ansible-run.JYGP4P3Y_eN3dj.webp&quot; alt=&quot;A starry night sky.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;2322&quot; height=&quot;2520&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Even in an age dominated by containers and cloud-native solutions, Ansible remains a crucial tool, not for what it once was, but for what it has become. Its core strength—its &lt;strong&gt;simplicity&lt;/strong&gt;—makes it the ideal choice for a new, pervasive use case: provisioning remote development environments.&lt;/p&gt;
&lt;p&gt;While Docker excels at application deployment, Ansible found its niche in ensuring developers have consistent, powerful, and reproducible workspaces. This solves the persistent problem of “it works on my machine” and significantly reduces time spent on setup and maintenance. It’s a fundamental shift in how we approach development, making it more efficient and collaborative.&lt;/p&gt;
&lt;p&gt;However, Ansible’s YAML configuration language can be cumbersome. The need to manually manage multiple files becomes a bottleneck as environments grow in complexity. This is where a code-first approach, like the one offered by &lt;strong&gt;BigConfig&lt;/strong&gt;, provides a powerful solution. By leveraging the fact that &lt;strong&gt;JSON is valid YAML&lt;/strong&gt;, you can use a real programming language to dynamically generate configurations. This not only makes the process more flexible and less error-prone but also opens the door to treating environment provisioning as an &lt;strong&gt;API&lt;/strong&gt;—a scalable, programmable service that can instantly onboard new team members.&lt;/p&gt;
&lt;p&gt;In short, Ansible’s journey is a testament to its adaptability. It has evolved from a tool for provisioning data centers to the cornerstone of modern remote development. Paired with a tool like BigConfig, its simple, powerful core is unlocked, proving that some of the best tools aren’t those that are replaced, but those that find a new purpose.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; hear &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>ansible</category><category>bigconfig</category><category>devenvironment</category><category>devmachine</category><category>code-first</category></item><item><title>Reimplementing the AWS EKS API with Clojure using BigConfig, Rama, and Pedestal</title><link>https://www.bigconfig.it/blog/reimplementing-aws-eks-with-big-config/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/reimplementing-aws-eks-with-big-config/</guid><description>The world of cloud infrastructure often involves interacting with complex APIs. While services like AWS EKS provide robust management for Kubernetes clusters, there might be scenarios where you need a more tailored or localized control plane. This article will guide you through reimplementing the AWS EKS API using a powerful Clojure stack: Pedestal for the API, BigConfig to wrap Terraform and Ansible in a workflow, and Rama for state and jobs.

</description><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The world of cloud infrastructure often involves interacting with complex APIs. While services like AWS EKS provide robust management for Kubernetes clusters, there might be scenarios where you need a more tailored or localized control plane. This article will guide you through reimplementing the AWS EKS API using a powerful Clojure stack: &lt;a href=&quot;https://pedestal.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Pedestal &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; for the API, &lt;a href=&quot;https://www.bigconfig.it/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; to wrap Terraform and Ansible in a workflow, and &lt;a href=&quot;https://redplanetlabs.com&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; Rama &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; for state and jobs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;why-reimplement&quot;&gt;Why Reimplement?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Before we dive into the how, let’s consider the why. K8s, Spark, ClickHouse, Postgres, and so on are all good candidates for an in-house software as a service solution. Reimplementing a cloud API might seem counterintuitive, but it can be beneficial for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Avoiding vendor lock-in:&lt;/strong&gt; This can be relevant for some companies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-cloud strategy:&lt;/strong&gt; You need an EKS-like solution in multiple cloud providers and you need a generic API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saas:&lt;/strong&gt; You have an open source software and the Saas is your source of revenue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metal:&lt;/strong&gt; You cannot use the cloud but you want to provide the same developer experience in your company.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration costs:&lt;/strong&gt; Buying EKS and integrating it with the rest of your infrastructure is not feasible or very expensive. Building an EKS-like solution is cheaper.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is a simplified blueprint for educational and experimental purposes. It will not cover the full breadth and complexity of the actual AWS EKS API.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-clojure-stack&quot;&gt;The Clojure Stack&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here’s a quick overview of the tools we’ll be using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BigConfig:&lt;/strong&gt; A workflow and a template engine that enables us to have a zero-cost build step before running any devops tool like Terraform or Ansible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rama:&lt;/strong&gt; A distributed stream processing and analytics engine that can also function as a durable, highly concurrent data store. We’ll use Rama to manage our cluster definitions and state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pedestal:&lt;/strong&gt; A comprehensive web framework for Clojure that emphasizes data-driven development and offers excellent support for both synchronous and asynchronous request handling. It will serve as our API gateway.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s imagine the core entities we want to manage: EKS Clusters. For simplicity, we’ll focus on creating and describing clusters.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;first-principles&quot;&gt;First principles&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reuse GitOps:&lt;/strong&gt; Building a single K8s cluster with GitOps should be reuseable. Replacing GitOps with an API should not require to reimplement everything from scratch. The solution should contain a more generalized version of the GitOps one for one cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative when possible:&lt;/strong&gt; Terraform should be used to create resources instead of the AWS APIs whenever it is possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;sequence-diagram&quot;&gt;Sequence diagram&lt;/h3&gt;&lt;/div&gt;
&lt;img alt=&quot;Diagram&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; src=&quot;https://www.bigconfig.it/d2/docs/blog/reimplementing-aws-eks-with-big-config-0.svg&quot; width=&quot;1322&quot; height=&quot;1382&quot;&gt;
&lt;div&gt;&lt;h3 id=&quot;deliverables&quot;&gt;Deliverables&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pedestal API&lt;/strong&gt;: to create and describe clusters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rama Module&lt;/strong&gt;: to store the desired state, and invoke the BigConfig module with the desired state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BigConfig Module&lt;/strong&gt;: this is where the heavy lifting is happening:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workflow&lt;/strong&gt;: achieving the desired state will require multiple steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock&lt;/strong&gt;: to be sure that changes are ACID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt;: to generate the configuration files for Terraform based on the desired state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply&lt;/strong&gt;: to run &lt;code dir=&quot;auto&quot;&gt;terraform apply&lt;/code&gt; programmatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;perks-of-the-solution&quot;&gt;Perks of the solution&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modularity:&lt;/strong&gt; Every deliverable can be developed in parallel by adopting contracts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniformity:&lt;/strong&gt; BigConfig, Rama, and Pedestal deliverables are all written in Clojure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative:&lt;/strong&gt; Creating an EC2 instance programmatically can be done faster with Terraform and we don’t need to worry about the life cycle management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reusability:&lt;/strong&gt; The GitOps code can be reused. The code to provision one K8s cluster with GitOps or multiple K8s clusters with an API, doesn’t require to change from Terraform to the AWS SDK. The API is just a virtual admin. The GitOps version developed interactively by an admin can be package as a Clojure dependency and reused inside Rama. &lt;strong&gt;This is a killer feature of BigConfig.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;the-code&quot;&gt;The code&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A first version that uses the Prevalent System design pattern instead of Rama and the CLI instead of Pedestal is available. Instead of EKS, the proof of concept create, delete, and rename AWS users.&lt;/p&gt;
&lt;div&gt; &lt;span&gt; &lt;a href=&quot;https://github.com/amiorin/big-config/blob/control-plane/src/clj/control_plane.clj&quot;&gt; &lt;span&gt;Show me the code&lt;/span&gt; &lt;/a&gt;  &lt;/span&gt; &lt;svg aria-hidden=&quot;true&quot; width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot;&gt;&lt;path d=&quot;M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z&quot; /&gt;&lt;/svg&gt; &lt;/div&gt; 
&lt;div&gt;&lt;h3 id=&quot;further-enhancements&quot;&gt;Further Enhancements&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is a basic example, but you can extend it significantly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More EKS Features:&lt;/strong&gt; Implement more aspects of the EKS API, such as node groups, Fargate profiles, or update operations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication and Authorization:&lt;/strong&gt; Integrate with a robust authentication system to secure your API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Handling:&lt;/strong&gt; Implement more sophisticated error handling and meaningful error messages by adopting OpenTelemetry.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;By combining BigConfig, Rama, and Pedestal, we’ve built a foundation for a in-house EKS-like API in Clojure. This approach provides a high degree of control, flexibility, and the ability to tailor your infrastructure management precisely to your needs. This project serves as an excellent starting point for exploring the potential of building custom cloud-native services with Clojure.&lt;/p&gt;
&lt;p&gt;Would you like to have a follow-up on this topic? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot;&gt;hear&lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>control plane</category><category>bigconfig</category><category>gitops</category><category>rama</category><category>pedestal</category><category>terraform</category><category>tofu</category><category>eks</category></item><item><title>The killer feature of BigConfig</title><link>https://www.bigconfig.it/blog/killer-feature-big-config/</link><guid isPermaLink="true">https://www.bigconfig.it/blog/killer-feature-big-config/</guid><description>For anyone working with Infrastructure as Code (IaC), managing configurations and deployments efficiently is key. Engineers are constantly seeking ways to enhance their workflows. Today, we&apos;re diving into a powerful combination: OpenTofu and BigConfig, highlighting a killer feature that makes your build step practically invisible!

</description><pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For anyone working with Infrastructure as Code (IaC), managing configurations and deployments efficiently is key. Engineers are constantly seeking ways to enhance their workflows. Today, we’re diving into a powerful combination: &lt;a href=&quot;https://opentofu.org&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; OpenTofu &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt; and &lt;a href=&quot;https://www.bigconfig.it/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt; BigConfig &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;1em&quot; height=&quot;1em&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt; &lt;path d=&quot;M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6&quot;&gt;&lt;/path&gt; &lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt; &lt;line x1=&quot;10&quot; x2=&quot;21&quot; y1=&quot;14&quot; y2=&quot;3&quot;&gt;&lt;/line&gt; &lt;/svg&gt; &lt;/a&gt;, highlighting a killer feature that makes your build step practically invisible!&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-challenge-of-configuration-management&quot;&gt;The Challenge of Configuration Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;IaC tools like OpenTofu (an open-source alternative to Terraform) empower teams to define, provision, and manage infrastructure through code. However, as projects scale, especially in complex environments, the build and deployment process can become a multi-step chore. This often involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git checks:&lt;/strong&gt; Ensuring your working directory is clean and up-to-date.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lock acquire:&lt;/strong&gt; Making sure that changes are apply in order and incrementally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution:&lt;/strong&gt; Iterate on the infracoding until it works.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git pushes:&lt;/strong&gt; Committing changes back to your repository if the change is successful&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment-specific deployments:&lt;/strong&gt; Handling different configurations for different environments like &lt;code dir=&quot;auto&quot;&gt;staging&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;production&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This manual orchestration can be time-consuming and prone to errors.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;enter-bigconfig-simplifying-complex-workflows&quot;&gt;Enter BigConfig: Simplifying Complex Workflows&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;BigConfig is a fantastic tool designed to encapsulate and automate these complex command sequences. It allows you to define a series of steps and execute them with a single command. Think of it as a smart wrapper for your common IaC operations. By centralizing these tasks, BigConfig significantly reduces cognitive load and improves consistency.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-killer-feature-an-invisible-build-step-with-a-shell-alias&quot;&gt;The Killer Feature: An Invisible Build Step with a Shell Alias&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here’s where the magic truly happens! By combining OpenTofu, BigConfig, and a simple shell alias, we can create an &lt;strong&gt;invisible build step&lt;/strong&gt;. Imagine replacing a series of manual operations with just one, familiar invocation.&lt;/p&gt;
&lt;p&gt;Consider this powerful shell alias:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tofu&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;bb render git-check lock exec git-push unlock-any -- alpha prod tofu&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Let’s break down what this alias does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;alias tofu=&quot;...&quot;&lt;/code&gt;&lt;/strong&gt;: This redefines your &lt;code dir=&quot;auto&quot;&gt;tofu&lt;/code&gt; command for the session. Now, whenever you type &lt;code dir=&quot;auto&quot;&gt;tofu&lt;/code&gt;, it executes BigConfig instead of &lt;code dir=&quot;auto&quot;&gt;tofu&lt;/code&gt;. Every step of the workflow is executed only if the previous step is successful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;render&lt;/code&gt;&lt;/strong&gt;: This is the BigConfig step to initiate a build process and achieve DRY like Atmos. If this fails, there is not reason to proceed. That’s why this step is always present and it is the first step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;git-check&lt;/code&gt;&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;render&lt;/code&gt; should not make the Git working directory dirty and &lt;code dir=&quot;auto&quot;&gt;git-check&lt;/code&gt; ensures your Git working directory is clean and up to date.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;lock&lt;/code&gt;&lt;/strong&gt;: It then acquires a lock for the module &lt;code dir=&quot;auto&quot;&gt;alpha&lt;/code&gt; and the profile &lt;code dir=&quot;auto&quot;&gt;prod&lt;/code&gt;, preventing concurrent changes from other developers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt;&lt;/strong&gt;: This is the core execution step, where BigConfig will run your OpenTofu commands. In case of failure of &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt;, the workflow will stop and the next steps will not be executed. In particular the lock will not be released and the changes will not be pushed so that the developer can fix or revert the change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;git-push&lt;/code&gt;&lt;/strong&gt;: This automatically pushes the just applied change to your Git repository. You should be always one commit ahead of &lt;code dir=&quot;auto&quot;&gt;origin&lt;/code&gt; when you make changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;unlock-any&lt;/code&gt;&lt;/strong&gt;: This ensures that any locks are released. &lt;code dir=&quot;auto&quot;&gt;any&lt;/code&gt; means that the owner is ignored. This step can be used alone if another developer forget to release the lock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;-- alpha prod tofu&lt;/code&gt;&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; is the separator between the workflow definition and &lt;code dir=&quot;auto&quot;&gt;module&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;profile&lt;/code&gt;, and the shell command, in this case &lt;code dir=&quot;auto&quot;&gt;tofu&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;how-this-transforms-your-workflow&quot;&gt;How This Transforms Your Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A simple alias is now extending the capabilities of OpenTofu. Now OpenTofu has the capabilities of Atlantis and Atmos. But BigConfig is not specific to OpenTofu and it can be used also with Ansible, K8s, and your &lt;a href=&quot;https://github.com/amiorin/dotfiles-v3&quot;&gt;dotfiles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;
OpenTofu was not enough and other tools were required like Atlantis and Atmos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;
Any DevOps tool can be augmented to have the capabilities of Atlantis and Atmos.&lt;/p&gt;
&lt;p&gt;The sequential workflow, with all its checks, locks, and pushes, becomes completely &lt;strong&gt;invisible&lt;/strong&gt; to the user. You interact with OpenTofu as you normally would, but all the surrounding boilerplate is handled automatically by BigConfig.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-for-your-team&quot;&gt;Benefits for Your Team&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased Productivity:&lt;/strong&gt; Engineers can focus on writing IaC, not on the deployment mechanics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Errors:&lt;/strong&gt; Automated checks and consistent execution minimize human error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardized Deployments:&lt;/strong&gt; Ensures that every deployment follows the same robust process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster Onboarding:&lt;/strong&gt; New team members can quickly get up to speed without memorizing complex sequences.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;get-started&quot;&gt;Get Started!&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you’re using OpenTofu and looking to streamline your IaC workflows, exploring BigConfig and implementing a similar shell alias is highly recommended. It’s a small change that yields massive benefits, transforming your change process from a visible chore into an invisible, seamless part of your development process. Happy infrastructure building! 🚀&lt;/p&gt;
&lt;p&gt;Are you still using Atlantis or Atmos? What are your thoughts? I’d love to &lt;a href=&quot;https://www.albertomiorin.com/contact#form&quot;&gt;hear&lt;/a&gt; your experiences.&lt;/p&gt;</content:encoded><category>bigconfig</category><category>gitops</category><category>terraform</category><category>tofu</category></item></channel></rss>