<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infra as Code on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/</link><description>Recent content in Infra as Code on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 03 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/index.xml" rel="self" type="application/rss+xml"/><item><title>Configuration Management — Puppet, Chef, Salt</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/config-management/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/config-management/</guid><description>&lt;p&gt;Before Terraform, before Kubernetes, before immutable infrastructure — configuration management tools were how you kept servers in a known state. Puppet, Chef, and Salt are the main players. All three solve the same core problem: a fleet of servers that should all look a certain way, continuously enforced.&lt;/p&gt;
&lt;p&gt;They predate the cloud-native era and show their age in places, but they are still running in a lot of enterprise environments and still the right tool in some specific contexts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-shared-model"&gt;The shared model
&lt;/h2&gt;&lt;p&gt;All three follow a similar pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You describe the desired state of a system (packages installed, files present, services running, users configured)&lt;/li&gt;
&lt;li&gt;An agent runs on each managed node and enforces that state continuously&lt;/li&gt;
&lt;li&gt;Changes are made by updating the desired-state declaration, not by SSHing in and running commands&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is fundamentally different from Ansible&amp;rsquo;s push model — Puppet, Chef, and Salt agents pull or receive configuration rather than waiting for a push. The continuous enforcement is the key property: a manual change to a managed server gets corrected on the next agent run.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="puppet"&gt;Puppet
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;: declarative DSL, agent pulls catalog from Puppet Server on a schedule (default 30 min).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Language&lt;/strong&gt;: Puppet DSL — a declarative, domain-specific language for describing resources. Not a general programming language.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-puppet" data-lang="puppet"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;:&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ensure&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;installed&lt;/span&gt;,&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;service&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;:&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ensure&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;running&lt;/span&gt;,&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enable&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Package&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;],&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;file&lt;/span&gt; { &lt;span style="color:#e6db74"&gt;&amp;#39;/etc/nginx/nginx.conf&amp;#39;&lt;/span&gt;:&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;source&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;puppet:///modules/nginx/nginx.conf&amp;#39;&lt;/span&gt;,&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;notify&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Service&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;],&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Puppet Forge&lt;/strong&gt;: a large community module library — the main reason Puppet still has traction. Many vendors publish official Puppet modules for their software.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it lives&lt;/strong&gt;: large enterprise environments that adopted it early and built significant module libraries around it. The inertia is real — migrating away is a significant project.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="chef"&gt;Chef
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;: agent-based, Ruby DSL. Configuration is written as cookbooks containing recipes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Language&lt;/strong&gt;: Ruby — specifically a DSL built on Ruby. More expressive than Puppet&amp;rsquo;s DSL, but requires Ruby knowledge and brings the associated complexity.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;package &lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service &lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; action &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:enable&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:start&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;template &lt;span style="color:#e6db74"&gt;&amp;#39;/etc/nginx/nginx.conf&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source &lt;span style="color:#e6db74"&gt;&amp;#39;nginx.conf.erb&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; notifies &lt;span style="color:#e6db74"&gt;:restart&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;service[nginx]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Chef Solo vs Chef Server&lt;/strong&gt;: Chef Solo runs without a server — cookbooks live locally or in version control. Chef Server is the full model with a central catalog and reporting. Knife is the CLI for managing Chef infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it lives&lt;/strong&gt;: similar story to Puppet — enterprises that adopted it early. Chef introduced the &amp;ldquo;infrastructure as code&amp;rdquo; framing before it was a widely used term. Less common in new projects, but still found running in organisations that built their automation around it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="salt"&gt;Salt
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;: agent-based (minions), master/minion architecture, ZeroMQ message bus for fast communication. Can also run agentless (SSH mode) like Ansible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Language&lt;/strong&gt;: YAML state files (SLS), with Jinja templating. More approachable than Puppet DSL or Chef Ruby.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;nginx&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pkg.installed&lt;/span&gt;: []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;service.running&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;enable&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;require&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;pkg&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;/etc/nginx/nginx.conf&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;file.managed&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;source&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;salt://nginx/files/nginx.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;watch_in&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;service&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Speed&lt;/strong&gt;: the ZeroMQ bus makes Salt significantly faster than Puppet or Chef for large fleets — sending a command to thousands of nodes and getting results back is near-instant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event-driven&lt;/strong&gt;: Salt has a reactor system — events on nodes can trigger automatic responses. More dynamic than Puppet or Chef&amp;rsquo;s scheduled pull model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it lives&lt;/strong&gt;: still used in organisations that needed to manage very large fleets efficiently. SaltStack was acquired by VMware, then Broadcom — commercial trajectory uncertain. The open-source Salt project continues.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="comparison"&gt;Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Puppet&lt;/th&gt;
 &lt;th&gt;Chef&lt;/th&gt;
 &lt;th&gt;Salt&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Language&lt;/td&gt;
 &lt;td&gt;Puppet DSL&lt;/td&gt;
 &lt;td&gt;Ruby&lt;/td&gt;
 &lt;td&gt;YAML + Jinja&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Model&lt;/td&gt;
 &lt;td&gt;Agent, pull&lt;/td&gt;
 &lt;td&gt;Agent, pull/push&lt;/td&gt;
 &lt;td&gt;Agent, push (ZeroMQ)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Learning curve&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;High (Ruby)&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Scale&lt;/td&gt;
 &lt;td&gt;Good&lt;/td&gt;
 &lt;td&gt;Good&lt;/td&gt;
 &lt;td&gt;Excellent&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Community&lt;/td&gt;
 &lt;td&gt;Large (Forge)&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;td&gt;Medium&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Status&lt;/td&gt;
 &lt;td&gt;Declining (new projects)&lt;/td&gt;
 &lt;td&gt;Declining&lt;/td&gt;
 &lt;td&gt;Uncertain (VMware/Broadcom)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="where-they-still-make-sense"&gt;Where they still make sense
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Long-lived fleets of traditional VMs or bare metal&lt;/strong&gt; — if you have thousands of servers that are not containers and are not going away, a continuous enforcement model still makes sense. Terraform provisions them; Puppet/Chef/Salt keeps them configured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Network devices&lt;/strong&gt; — see &lt;a class="link" href="../ansible/" &gt;Ansible&lt;/a&gt; for this use case; Ansible is the clearer choice, but Salt also has network automation modules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Legacy enterprise&lt;/strong&gt; — if the organisation has already built a Puppet or Chef codebase, migrating everything to a new tool is a significant project with real risk. Maintaining the existing automation often makes more sense than rewriting it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-general-direction"&gt;The general direction
&lt;/h2&gt;&lt;p&gt;New projects in the cloud-native era rarely reach for these tools. Immutable infrastructure (build a new image, replace the server) removes the need for continuous configuration enforcement. Kubernetes handles application configuration. Terraform handles provisioning. The gap these tools filled is smaller now.&lt;/p&gt;
&lt;p&gt;That said, not all infrastructure is cloud-native, and &amp;ldquo;replace everything&amp;rdquo; is rarely a realistic option in a large organisation. These tools will be running in enterprise environments for years.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="principles"&gt;Principles
&lt;/h2&gt;&lt;p&gt;A few rules of thumb that hold regardless of which tool you use:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only production requirements belong in production.&lt;/strong&gt; If a package, service, or configuration isn&amp;rsquo;t needed by the workload, it shouldn&amp;rsquo;t be there. Every additional component is a potential failure point and a maintenance burden. Configuration management makes it easy to declare everything — resist the temptation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separate CI from CD.&lt;/strong&gt; The pipeline that builds and tests your code is not the pipeline that applies configuration to servers. Keeping them separate means you can change one without touching the other, and failures in one don&amp;rsquo;t cascade.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KISS — as few moving parts as possible.&lt;/strong&gt; Configuration management stacks can grow complex fast: roles, profiles, hieradata, environments, external node classifiers. Start with the simplest thing that enforces the state you need. Add complexity only when the simple version demonstrably can&amp;rsquo;t handle the requirement.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="see-also"&gt;See also
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="../ansible/" &gt;Ansible&lt;/a&gt; — the agentless alternative that won the mindshare battle&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="../terraform/" &gt;Terraform&lt;/a&gt; — handles the provisioning side; often used alongside config management&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="../crossplane/" &gt;Crossplane&lt;/a&gt; — the Kubernetes-native model that may be the long-term successor for cloud-native environments&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Crossplane</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/crossplane/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/crossplane/</guid><description>&lt;p&gt;Crossplane is Kubernetes-native infrastructure management. Where Terraform runs as a CLI tool that applies changes and exits, Crossplane runs as a controller inside a Kubernetes cluster and continuously reconciles infrastructure — the same control loop model as Kubernetes itself.&lt;/p&gt;
&lt;p&gt;Cloud resources become Kubernetes objects. You &lt;code&gt;kubectl apply&lt;/code&gt; an RDS instance the same way you apply a Deployment. Crossplane&amp;rsquo;s controllers watch those objects and make the API calls to converge actual infrastructure to the desired state.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="core-concepts"&gt;Core concepts
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt; extend Crossplane with CRDs for a specific cloud. &lt;code&gt;provider-aws&lt;/code&gt; adds Kubernetes resources for every AWS service — S3 buckets, RDS instances, VPCs. Apply a provider, get hundreds of new resource types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Managed Resources (MRs)&lt;/strong&gt; are the individual cloud resources:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;s3.aws.upbound.io/v1beta1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-assets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;forProvider&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;region&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;eu-central-1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tags&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;Environment&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;prod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Crossplane creates this bucket and keeps it in sync. If someone deletes it outside of Crossplane, the controller recreates it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Composite Resources (XRs)&lt;/strong&gt; are the powerful part. You define your own CRDs — a &lt;code&gt;Platform&lt;/code&gt; or &lt;code&gt;DatabaseCluster&lt;/code&gt; — that compose multiple managed resources. A developer applies a &lt;code&gt;DatabaseCluster&lt;/code&gt; and gets an RDS instance, a subnet group, a parameter group, and security groups, all wired together, without needing to know any of the details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;XRDs (Composite Resource Definitions)&lt;/strong&gt; define the schema for composite resources — what fields the developer sees, what defaults apply.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compositions&lt;/strong&gt; define how a composite resource maps to managed resources — the implementation behind the abstraction.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-platform-engineering-model"&gt;The platform engineering model
&lt;/h2&gt;&lt;p&gt;Crossplane&amp;rsquo;s real value is as a platform layer. A platform team owns the Compositions — they define what a &amp;ldquo;compliant database&amp;rdquo; or &amp;ldquo;standard app environment&amp;rdquo; looks like. Dev teams consume the simplified abstractions without touching the underlying cloud resources.&lt;/p&gt;
&lt;p&gt;Self-service infrastructure with guardrails baked in.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="vs-terraform"&gt;vs Terraform
&lt;/h2&gt;&lt;p&gt;Crossplane and Terraform are not direct alternatives — they solve the problem differently.&lt;/p&gt;
&lt;p&gt;Terraform is a CLI tool: run plan, review, apply, exit. State is a file. Good for human-in-the-loop workflows and one-off provisioning.&lt;/p&gt;
&lt;p&gt;Crossplane is a control plane: always running, always reconciling. Better for continuous enforcement and self-service platforms. More complex to set up and operate.&lt;/p&gt;
&lt;p&gt;In practice: Terraform for provisioning foundational infrastructure (clusters, networks, accounts). Crossplane for what runs on top of the cluster — letting application teams provision their own databases, queues, and object storage through Kubernetes-native APIs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="upbound"&gt;Upbound
&lt;/h2&gt;&lt;p&gt;The commercial platform behind Crossplane. Managed control plane hosting, a marketplace of providers and compositions, and tooling for building and publishing your own platform APIs. Worth evaluating if you are building a serious internal platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="learning-curve"&gt;Learning curve
&lt;/h2&gt;&lt;p&gt;Steep. You need to understand Kubernetes controllers, CRDs, and the Crossplane composition model before you can be productive. The payoff is a genuinely powerful platform abstraction — but it is not a beginner tool.&lt;/p&gt;
&lt;p&gt;A good framing: Crossplane is a &lt;strong&gt;digital twin of your infrastructure&lt;/strong&gt;. The cluster holds the desired state of everything — cloud resources, application configuration, other tools — and continuously reconciles reality to match it.&lt;/p&gt;
&lt;p&gt;Genuinely cool and worth learning if you have a cluster. The provider model has expanded well beyond cloud infrastructure — from v2 onwards Crossplane can manage applications, not just infra. There are also providers for Ansible and Terraform/OpenTofu, which means Crossplane can be the orchestration layer that drives other IaC tools. One control plane to rule them all.&lt;/p&gt;
&lt;p&gt;The prerequisite is the cluster itself. If you already run Kubernetes, Crossplane is a natural extension of the same model you already operate. If you do not, it is not the tool to start with.&lt;/p&gt;</description></item><item><title>Infrastructure Testing — Molecule, Test Kitchen, InSpec, Chainsaw</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/infra-testing/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/infra-testing/</guid><description>&lt;p&gt;Infrastructure code needs testing like application code does. The tools here cover different layers: role testing, integration testing, compliance checking, and Kubernetes end-to-end testing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="molecule--ansible-role-testing"&gt;Molecule — Ansible role testing
&lt;/h2&gt;&lt;p&gt;Molecule is the standard testing framework for Ansible roles and playbooks. It manages the full test lifecycle: spin up instances, apply the role, verify the result, tear everything down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stages:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;molecule create # spin up test instances (Docker, Podman, EC2, etc.)
molecule converge # apply the role/playbook
molecule verify # run assertions
molecule destroy # clean up
molecule test # full cycle in sequence
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Drivers&lt;/strong&gt; control where instances run — Docker or Podman for local development, EC2 or other cloud providers for closer-to-production testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifiers&lt;/strong&gt; run assertions after converge. The default is Ansible itself (run a playbook of assertions), but &lt;a class="link" href="https://testinfra.readthedocs.io/" target="_blank" rel="noopener"
 &gt;Testinfra&lt;/a&gt; (pytest-based) and &lt;a class="link" href="https://github.com/goss-org/goss" target="_blank" rel="noopener"
 &gt;Goss&lt;/a&gt; are popular alternatives.&lt;/p&gt;
&lt;p&gt;A minimal &lt;code&gt;molecule/default/molecule.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;driver&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;platforms&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;instance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu:22.04&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;provisioner&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ansible&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;verifier&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ansible&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wire into CI: &lt;code&gt;molecule test&lt;/code&gt; runs the full cycle and exits non-zero on failure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="test-kitchen--integration-testing-for-infrastructure"&gt;Test Kitchen — integration testing for infrastructure
&lt;/h2&gt;&lt;p&gt;Test Kitchen (kitchen.ci) is an integration testing framework originally built for Chef but now multi-platform. It creates instances, applies configuration, runs a verifier, and destroys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drivers&lt;/strong&gt;: Docker, Vagrant, and cloud providers (EC2, GCP, Azure). Plugins also exist for Ansible (&lt;code&gt;kitchen-ansible&lt;/code&gt;) and Terraform (&lt;code&gt;kitchen-terraform&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifiers&lt;/strong&gt;: InSpec (most common), Busser.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;kitchen.yml&lt;/code&gt; for a Chef cookbook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;driver&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;provisioner&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;chef_zero&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;verifier&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;inspec&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;platforms&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-22.04&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;suites&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;verifier&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;inspec_tests&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;test/integration/default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kitchen list &lt;span style="color:#75715e"&gt;# show instance state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kitchen converge &lt;span style="color:#75715e"&gt;# apply provisioner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kitchen verify &lt;span style="color:#75715e"&gt;# run verifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kitchen test &lt;span style="color:#75715e"&gt;# full cycle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test Kitchen is less commonly adopted outside Chef shops, but the multi-platform support means it can be useful for testing across providers in one framework.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="inspec--compliance-testing-as-code"&gt;InSpec — compliance testing as code
&lt;/h2&gt;&lt;p&gt;InSpec (now Progress Chef InSpec) is a compliance testing framework. You write assertions about the state of a system — packages installed, ports listening, file permissions, user accounts — in a Ruby DSL. InSpec executes them locally, over SSH, against Docker containers, or against cloud APIs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe package(&lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it { should be_installed }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe service(&lt;span style="color:#e6db74"&gt;&amp;#39;nginx&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it { should be_running }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it { should be_enabled }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe port(&lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it { should be_listening }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe file(&lt;span style="color:#e6db74"&gt;&amp;#39;/etc/nginx/nginx.conf&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; its(&lt;span style="color:#e6db74"&gt;&amp;#39;mode&amp;#39;&lt;/span&gt;) { should cmp &lt;span style="color:#e6db74"&gt;&amp;#39;0644&amp;#39;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Profiles&lt;/strong&gt; are the distributable unit — a collection of controls with metadata. The InSpec community maintains profiles for CIS benchmarks, DISA STIGs, and other compliance standards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where it fits&lt;/strong&gt;: compliance auditing, security baselines, post-deploy verification. Wired into CI as a final gate or run on a schedule against production.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;inspec exec path/to/profile --target ssh://user@host
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;inspec exec path/to/profile --target docker://container-id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="chainsaw--kubernetes-e2e-testing"&gt;Chainsaw — Kubernetes e2e testing
&lt;/h2&gt;&lt;p&gt;Chainsaw is a declarative end-to-end testing framework for Kubernetes. Originally from the Kyverno project, now standalone. Write test scenarios in YAML — apply manifests, assert on resource state, clean up — without writing Go or any test framework code.&lt;/p&gt;
&lt;p&gt;Particularly useful for testing Kubernetes operators, controllers, and admission webhooks: the things that are hard to unit test because they depend on the Kubernetes API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;chainsaw.kyverno.io/v1alpha1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;basic-deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;create deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;apply&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resource&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;test-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;assert&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resource&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;test-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;status&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;readyReplicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cleanup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;delete&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ref&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;test-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chainsaw test ./tests/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;vs Terratest for Kubernetes&lt;/strong&gt;: Terratest requires Go code; Chainsaw is YAML. For testing Kubernetes resources and operators specifically, Chainsaw is simpler to write and maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="terratest--go-based-infrastructure-testing"&gt;Terratest — Go-based infrastructure testing
&lt;/h2&gt;&lt;p&gt;Covered in the &lt;a class="link" href="../terraform/#testing-and-validation" &gt;Terraform page&lt;/a&gt;. The short version: Go tests that deploy real infrastructure, run assertions via cloud SDKs, then tear it down. The most complete integration testing approach for Terraform modules — and the highest cost in setup and test execution time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="choosing"&gt;Choosing
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Tool&lt;/th&gt;
 &lt;th&gt;Best for&lt;/th&gt;
 &lt;th&gt;Language&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Molecule&lt;/td&gt;
 &lt;td&gt;Ansible role testing&lt;/td&gt;
 &lt;td&gt;YAML + Python/Ansible&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Test Kitchen&lt;/td&gt;
 &lt;td&gt;Multi-platform integration testing&lt;/td&gt;
 &lt;td&gt;YAML + Ruby (InSpec)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;InSpec&lt;/td&gt;
 &lt;td&gt;Compliance assertions, security baselines&lt;/td&gt;
 &lt;td&gt;Ruby DSL&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Chainsaw&lt;/td&gt;
 &lt;td&gt;Kubernetes operators and controllers&lt;/td&gt;
 &lt;td&gt;YAML&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Terratest&lt;/td&gt;
 &lt;td&gt;Terraform module integration testing&lt;/td&gt;
 &lt;td&gt;Go&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item><item><title>Pulumi</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/pulumi/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/pulumi/</guid><description>&lt;p&gt;Pulumi takes the same approach as &lt;a class="link" href="../cdk/" &gt;AWS CDK&lt;/a&gt; — use a real programming language to define infrastructure — but without the CloudFormation layer underneath. Pulumi talks directly to cloud APIs, maintains its own state, and supports multiple clouds from a single codebase.&lt;/p&gt;
&lt;p&gt;Languages: TypeScript, Python, Go, Java, C#, and YAML. Same language across all supported clouds.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-it-works"&gt;How it works
&lt;/h2&gt;&lt;p&gt;Write infrastructure as code in your language of choice. Pulumi runs the program, builds a desired-state graph, diffs against current state, and makes the API calls to converge.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@pulumi/aws&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bucket&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aws&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;s3&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Bucket&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;assets&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;versioning&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tags&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;Environment&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;prod&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bucketName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bucket&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;bucket&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pulumi preview &lt;span style="color:#75715e"&gt;# show planned changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pulumi up &lt;span style="color:#75715e"&gt;# apply&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pulumi destroy &lt;span style="color:#75715e"&gt;# tear down&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="state"&gt;State
&lt;/h2&gt;&lt;p&gt;Pulumi manages state like Terraform — a backend stores what resources exist and their current configuration. Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pulumi Cloud&lt;/strong&gt; — hosted, free tier available, adds secrets management and team features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S3 / GCS / Azure Blob&lt;/strong&gt; — self-managed, same pattern as Terraform&amp;rsquo;s remote backend&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="vs-terraform"&gt;vs Terraform
&lt;/h2&gt;&lt;p&gt;The core tradeoff: Pulumi gives you a real programming language (real loops, real functions, real tests) at the cost of a smaller ecosystem and less community tooling than Terraform. HCL is a constraint but also a simplicity — Terraform configurations are declarative and readable without needing to understand the language runtime.&lt;/p&gt;
&lt;p&gt;Pulumi is the better choice when infrastructure logic is genuinely complex enough to benefit from a real language. For straightforward cloud resources, Terraform&amp;rsquo;s declarative HCL is often simpler to read and maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="vs-aws-cdk"&gt;vs AWS CDK
&lt;/h2&gt;&lt;p&gt;Both use real languages. The difference: CDK generates CloudFormation and inherits all of CloudFormation&amp;rsquo;s limitations (see the &lt;a class="link" href="../cdk/" &gt;CDK page&lt;/a&gt;). Pulumi talks directly to cloud APIs, which means better drift visibility, faster deploys, and support outside AWS.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="testing"&gt;Testing
&lt;/h2&gt;&lt;p&gt;Because infrastructure is code, it tests like code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unit tests with Jest (TypeScript), pytest (Python), or Go test — test the resource graph without deploying&lt;/li&gt;
&lt;li&gt;Integration tests that deploy to an isolated stack and assert against live infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="opentofu--terraform-note"&gt;OpenTofu / Terraform note
&lt;/h2&gt;&lt;p&gt;After HashiCorp changed Terraform&amp;rsquo;s licence to BSL in 2023, the community forked it as &lt;a class="link" href="https://opentofu.org/" target="_blank" rel="noopener"
 &gt;OpenTofu&lt;/a&gt;. If the licence change matters to your organisation, OpenTofu is a drop-in replacement. Pulumi was not affected.&lt;/p&gt;
&lt;p&gt;Only explored in POC and lab contexts, not production. The honest take: Pulumi feels like the Gradle of IaC. The real-language argument is compelling in theory — proper loops, functions, tests, abstractions — but in practice it is easy to get wrong, and HCL&amp;rsquo;s constraint is also its strength. A Terraform configuration is readable by anyone; a Pulumi TypeScript codebase requires understanding the language runtime and the person who wrote it.&lt;/p&gt;
&lt;p&gt;The cases where Pulumi wins: complex infrastructure logic that genuinely benefits from a real language, or teams already deep in a single language who want infrastructure to live alongside application code. For standard cloud infrastructure, &lt;a class="link" href="../terraform/" &gt;Terraform&lt;/a&gt; is the lower-friction choice.&lt;/p&gt;</description></item><item><title>Ansible</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/ansible/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/ansible/</guid><description>&lt;p&gt;Ansible is an open-source automation tool for configuration management, application deployment, and orchestration. The key selling point: it&amp;rsquo;s agentless — you push from a control machine over SSH, no daemon running on managed hosts.&lt;/p&gt;
&lt;h2 id="why-ansible"&gt;Why Ansible
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free and open source&lt;/strong&gt; — Red Hat maintains it, commercially supported via Ansible Automation Platform (formerly Tower)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agentless&lt;/strong&gt; — no software to install on managed nodes; plain SSH is enough&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple&lt;/strong&gt; — playbooks are YAML, readable without special knowledge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible&lt;/strong&gt; — works on servers, cloud platforms, network devices, and bare-metal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-it-does"&gt;What it does
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Configuration management&lt;/strong&gt; — define what state a system should be in; Ansible gets it there and keeps it there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application deployment&lt;/strong&gt; — deploy multi-tier applications with a playbook; let Ansible figure out the ordering and state transitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; — coordinate complex workflows across databases, networks, front-end and back-end services in the right order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security and compliance&lt;/strong&gt; — enforce firewall rules, user policies, and security baselines across all hosts from a single playbook run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloud provisioning&lt;/strong&gt; — provision infrastructure on AWS, Azure, GCP, OpenStack, or bare-metal with the same tooling.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt; — small programs pushed to nodes over SSH, executed, then removed. Ansible ships with 750+ modules for packages, services, files, cloud APIs, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plugins&lt;/strong&gt; — extend Ansible&amp;rsquo;s core: connection types, callbacks, caching, filtering. Write your own or use community plugins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inventory&lt;/strong&gt; — a file (INI or YAML) listing all managed hosts, their IPs, groups, and variables. Can also pull dynamic inventory from AWS, GCP, Azure, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playbooks&lt;/strong&gt; — YAML files describing tasks to run on which hosts. The core unit of work. Each play maps a group of hosts to a set of tasks; each task calls a module.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;APIs&lt;/strong&gt; — extend connection transports beyond SSH (WinRM for Windows, network device APIs, etc.).&lt;/p&gt;
&lt;h2 id="ansible-automation-platform"&gt;Ansible Automation Platform
&lt;/h2&gt;&lt;p&gt;Red Hat&amp;rsquo;s commercial wrapper around Ansible. Adds a web UI, RBAC, job scheduling, audit logging, and a workflow editor. Worth it once you have multiple teams running automation.&lt;/p&gt;
&lt;h2 id="where-ansible-still-excels"&gt;Where Ansible still excels
&lt;/h2&gt;&lt;p&gt;Network device configuration is the clearest remaining stronghold. Ansible has mature modules for switches, routers, and firewalls (Cisco IOS, Arista EOS, Juniper JunOS, and many others) and the agentless SSH model works well for network gear that can&amp;rsquo;t run an agent. For network automation, Ansible is still state of the art.&lt;/p&gt;
&lt;p&gt;For server configuration management — the original use case — most teams should consider moving to other models. Terraform manages provisioning, container images handle application configuration (immutable infrastructure), and Kubernetes operators handle runtime state. The gap Ansible used to fill is smaller.&lt;/p&gt;
&lt;h2 id="the-configuration-management-landscape"&gt;The configuration management landscape
&lt;/h2&gt;&lt;p&gt;Ansible is one of several tools in this space. The others worth knowing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Salt&lt;/strong&gt; (SaltStack) — agent-based, event-driven, fast for large fleets. More complex than Ansible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Puppet&lt;/strong&gt; — agent-based, declarative DSL, strong in enterprise. Puppet Forge has a large module library.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chef&lt;/strong&gt; — agent-based, Ruby DSL, infrastructure as code before the term was common. Less common now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a class="link" href="../config-management/" &gt;Configuration Management — Puppet, Chef, Salt&lt;/a&gt; for a full comparison.&lt;/p&gt;
&lt;p&gt;All four predate the Kubernetes era. Ansible has survived best because it is agentless and YAML-based — lower barrier. The others are still found in large enterprise environments with long-lived infrastructure.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.ansible.com/" target="_blank" rel="noopener"
 &gt;docs.ansible.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.ansible.com/" target="_blank" rel="noopener"
 &gt;ansible.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AWS CDK</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/cdk/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/cdk/</guid><description>&lt;p&gt;AWS CDK (Cloud Development Kit) lets you define infrastructure using real programming languages — Java, TypeScript, Python — rather than a DSL. You write code, CDK synthesizes it into a CloudFormation template, and CloudFormation deploys it to AWS.&lt;/p&gt;
&lt;p&gt;The appeal is obvious if your team lives in Java: familiar language, familiar tooling, loops and abstractions, unit tests with JUnit. No HCL to learn.&lt;/p&gt;
&lt;p&gt;In practice it is slower and more cumbersome than it looks, and the CloudFormation layer underneath causes real problems.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Write Java (imperative)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mvn package&lt;/code&gt; — compile and run unit tests&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdk synth&lt;/code&gt; — generate the CloudFormation template&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdk diff&lt;/code&gt; — compare deployed stack with current state (optional)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdk deploy&lt;/code&gt; — deploy stack to AWS via CloudFormation&lt;/li&gt;
&lt;li&gt;CloudFormation makes the API calls to AWS&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key thing to understand: CDK does not talk to AWS directly. It generates CloudFormation, and CloudFormation does the work. Everything downstream of &lt;code&gt;cdk synth&lt;/code&gt; is CloudFormation behaviour, not CDK behaviour.&lt;/p&gt;
&lt;h2 id="the-cloudformation-problem"&gt;The CloudFormation problem
&lt;/h2&gt;&lt;p&gt;CloudFormation does not track changes made outside of stacks. Someone logs into the console and modifies a security group — CloudFormation does not know, your CDK code does not know, and &lt;code&gt;cdk diff&lt;/code&gt; will not show it. That is drift, and it accumulates silently until a deployment overrides something that was manually changed in production, or a &lt;code&gt;cdk destroy&lt;/code&gt; removes something that was depended on.&lt;/p&gt;
&lt;p&gt;CloudFormation does have a drift detection feature, but it is not part of the normal workflow — you trigger it manually from the console, it shows you the diff, and then expects you to go fix it yourself.&lt;/p&gt;
&lt;p&gt;Compared to &lt;a class="link" href="../terraform/" &gt;Terraform&lt;/a&gt;, which maintains its own state file and surfaces drift explicitly in &lt;code&gt;terraform plan&lt;/code&gt;, this is a meaningful operational weakness.&lt;/p&gt;
&lt;h2 id="cross-stack-dependencies"&gt;Cross-stack dependencies
&lt;/h2&gt;&lt;p&gt;Splitting infrastructure into smaller stacks is the right instinct — smaller blast radius, faster deploys, clearer ownership. The problem is how CloudFormation handles values shared between stacks.&lt;/p&gt;
&lt;p&gt;When Stack A exports a value (a VPC ID, a security group ARN) and Stack B imports it, CloudFormation creates a hard dependency. You cannot modify or delete that export while any other stack is consuming it. CloudFormation will block the update and leave you stuck — Stack A cannot deploy because the export is in use, and Stack B may be in a broken state because it depends on something that no longer exists as expected. Getting out requires carefully coordinating the removal of the import before you can change the export.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix: use SSM Parameter Store instead of CloudFormation exports.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stack A writes its outputs to SSM:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;StringParameter.&lt;span style="color:#a6e22e"&gt;Builder&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;VpcIdParam&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;parameterName&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/infra/vpc/id&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;stringValue&lt;/span&gt;(vpc.&lt;span style="color:#a6e22e"&gt;getVpcId&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Stack B reads from SSM at deploy time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;String vpcId &lt;span style="color:#f92672"&gt;=&lt;/span&gt; StringParameter.&lt;span style="color:#a6e22e"&gt;valueForStringParameter&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/infra/vpc/id&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No CloudFormation export, no hard dependency, no blocked deployments. Stacks can be updated and deployed independently. SSM also gives you a clean place to browse shared infrastructure values and reference them from outside CDK (application config, scripts, other tools).&lt;/p&gt;
&lt;h2 id="testing"&gt;Testing
&lt;/h2&gt;&lt;p&gt;Testing in CDK is limited to validating the synthesized CloudFormation template — you are not testing infrastructure behaviour, you are testing that your code generates the JSON you expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; — JUnit assertions against the synthesized template (e.g. &amp;ldquo;does this stack contain an S3 bucket with versioning enabled&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; — deploy to an isolated environment and validate via AWS SDK&lt;/li&gt;
&lt;li&gt;No native plan/apply preview; you must deploy to see what actually happens&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pros"&gt;Pros
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Familiar Java constructs — loops, conditionals, reusable classes&lt;/li&gt;
&lt;li&gt;Full IDE support, refactoring, type safety&lt;/li&gt;
&lt;li&gt;Unit testing with JUnit&lt;/li&gt;
&lt;li&gt;Good for teams already invested in Java who want infrastructure close to application code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cons"&gt;Cons
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Generates CloudFormation — debugging complex stacks means reading generated JSON/YAML&lt;/li&gt;
&lt;li&gt;More boilerplate than HCL for straightforward infrastructure&lt;/li&gt;
&lt;li&gt;Drift detection relies on CloudFormation, which has no visibility into out-of-band changes&lt;/li&gt;
&lt;li&gt;Importing existing infrastructure can be awkward&lt;/li&gt;
&lt;li&gt;Smaller community example base compared to Terraform, especially in Java vs TypeScript/Python&lt;/li&gt;
&lt;li&gt;Deployments are slower than Terraform&amp;rsquo;s direct API approach&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="vs-terraform"&gt;vs Terraform
&lt;/h2&gt;&lt;p&gt;For repeatable, standardised infrastructure with a clear plan/apply workflow and reliable drift detection, &lt;a class="link" href="../terraform/" &gt;Terraform&lt;/a&gt; is the stronger choice. CDK makes sense when infrastructure is deeply coupled to application code and the team is committed to a single language across both.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.aws.amazon.com/cdk/v2/guide/home.html" target="_blank" rel="noopener"
 &gt;AWS CDK documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.aws.amazon.com/cdk/api/v2/" target="_blank" rel="noopener"
 &gt;AWS CDK API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cdkpatterns.com/" target="_blank" rel="noopener"
 &gt;CDK Patterns&lt;/a&gt; — community example library&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/aws/aws-cdk" target="_blank" rel="noopener"
 &gt;AWS CDK GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Terraform</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/terraform/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/terraform/</guid><description>&lt;p&gt;Terraform is HashiCorp&amp;rsquo;s infrastructure-as-code tool: you declare what infrastructure you want in HCL (HashiCorp Configuration Language), and Terraform figures out how to create, update, or destroy resources to match that declaration. It works across providers — AWS, GCP, Azure, Kubernetes, GitHub, and hundreds more — through a plugin architecture.&lt;/p&gt;
&lt;h2 id="core-concepts"&gt;Core concepts
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt; are plugins that translate Terraform resources into API calls. Declared in &lt;code&gt;required_providers&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;terraform&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;required_providers&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; aws &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;hashicorp/aws&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;provider&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;eu-west-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt; are the things you&amp;rsquo;re managing — an EC2 instance, an S3 bucket, a DNS record:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_s3_bucket&amp;#34; &amp;#34;assets&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bucket &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;my-app-assets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_s3_bucket_versioning&amp;#34; &amp;#34;assets&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bucket &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;assets&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;versioning_configuration&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Data sources&lt;/strong&gt; read existing infrastructure without managing it — useful for referencing shared resources:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;data&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_vpc&amp;#34; &amp;#34;main&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;filter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tag:Name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; values &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="state"&gt;State
&lt;/h2&gt;&lt;p&gt;Terraform tracks what it has created in a &lt;strong&gt;state file&lt;/strong&gt; (&lt;code&gt;terraform.tfstate&lt;/code&gt;). This is the source of truth for what exists — Terraform computes diffs against state, not live infrastructure.&lt;/p&gt;
&lt;p&gt;In teams, state must be stored remotely and locked during operations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;terraform&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;backend&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;s3&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bucket &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;my-terraform-state&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;prod/terraform.tfstate&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;eu-west-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dynamodb_table &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;terraform-locks&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Never commit state files to version control — they contain secrets and will diverge between team members.&lt;/p&gt;
&lt;h2 id="the-planapply-cycle"&gt;The plan/apply cycle
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform init &lt;span style="color:#75715e"&gt;# download providers, initialise backend&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform plan &lt;span style="color:#75715e"&gt;# show what will change — read this carefully&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform apply &lt;span style="color:#75715e"&gt;# create/update/destroy resources&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform destroy &lt;span style="color:#75715e"&gt;# tear everything down&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt; is the key step: it shows exactly what Terraform intends to do before touching anything. Review the plan — a &lt;code&gt;+&lt;/code&gt; is create, &lt;code&gt;~&lt;/code&gt; is update in-place, &lt;code&gt;-/+&lt;/code&gt; is replace (destroy and recreate), &lt;code&gt;-&lt;/code&gt; is destroy.&lt;/p&gt;
&lt;h2 id="variables-and-outputs"&gt;Variables and outputs
&lt;/h2&gt;&lt;p&gt;Variables make configurations reusable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;variable&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;environment&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; default &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;staging&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_instance&amp;#34; &amp;#34;app&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ami &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ami-0abc123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; instance_type &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.environment &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;prod&amp;#34; ? &amp;#34;t3.medium&amp;#34; : &amp;#34;t3.micro&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tags &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Environment &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pass values via &lt;code&gt;terraform.tfvars&lt;/code&gt;, environment variables (&lt;code&gt;TF_VAR_environment&lt;/code&gt;), or the &lt;code&gt;-var&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Outputs expose values after apply — useful for passing information between modules:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;output&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bucket_name&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; value &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;assets&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="modules"&gt;Modules
&lt;/h2&gt;&lt;p&gt;Modules are reusable, composable units of Terraform configuration — a directory of &lt;code&gt;.tf&lt;/code&gt; files called as a block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;module&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vpc&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;terraform-aws-modules/vpc/aws&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;5.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cidr &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;10.0.0.0/16&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; azs &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;eu-west-1a&amp;#34;, &amp;#34;eu-west-1b&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Encapsulate common patterns (a standard VPC setup, an ECS service, a Lambda with IAM role) in a module rather than repeating the same resources across configurations.&lt;/p&gt;
&lt;h2 id="multiple-environments"&gt;Multiple environments
&lt;/h2&gt;&lt;p&gt;Workspaces exist but are limited — shared state, easy to confuse, no real isolation. The two patterns that actually work in practice:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Folder per environment&lt;/strong&gt; — &lt;code&gt;envs/dev/&lt;/code&gt;, &lt;code&gt;envs/staging/&lt;/code&gt;, &lt;code&gt;envs/prod/&lt;/code&gt; each have their own backend config and &lt;code&gt;tfvars&lt;/code&gt;. Explicit, auditable, easy to reason about. The downside is duplication across env directories, mitigated by shared modules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Terragrunt&lt;/strong&gt; — a thin wrapper around Terraform that eliminates the duplication. Environments inherit from a root config, each only overrides what differs. One place to bump a module version and it propagates everywhere.&lt;/p&gt;
&lt;h3 id="terragrunt"&gt;Terragrunt
&lt;/h3&gt;&lt;p&gt;Terragrunt solves the main friction of the folder-per-environment pattern: repetition. Backend config, provider version, module source — these are the same across every environment. Terragrunt lets you define them once at the root and inherit downward.&lt;/p&gt;
&lt;p&gt;A typical layout:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;infra/
 terragrunt.hcl # root config — backend, provider defaults
 dev/
 terragrunt.hcl # env-level overrides
 vpc/
 terragrunt.hcl # module call
 eks/
 terragrunt.hcl
 prod/
 terragrunt.hcl
 vpc/
 terragrunt.hcl
 eks/
 terragrunt.hcl
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The root &lt;code&gt;terragrunt.hcl&lt;/code&gt; defines the remote state pattern once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;remote_state&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; backend &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;s3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bucket &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;my-terraform-state&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;${path_relative_to_include()}/terraform.tfstate&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;eu-west-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each module&amp;rsquo;s &lt;code&gt;terragrunt.hcl&lt;/code&gt; just declares its source and inputs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;include&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;root&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;find_in_parent_folders&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;terraform&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &amp;#34;git::https://github.com/myorg/terraform-modules.git//vpc?ref&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;v1&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;inputs &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cidr &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;10.1.0.0/16&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terragrunt plan &lt;span style="color:#75715e"&gt;# plan a single module&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terragrunt apply &lt;span style="color:#75715e"&gt;# apply a single module&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terragrunt run-all plan &lt;span style="color:#75715e"&gt;# plan all modules in a directory tree&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terragrunt run-all apply &lt;span style="color:#75715e"&gt;# apply all, respects dependency order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;run-all&lt;/code&gt; handles dependency ordering automatically — if &lt;code&gt;eks&lt;/code&gt; depends on &lt;code&gt;vpc&lt;/code&gt;, Terragrunt applies &lt;code&gt;vpc&lt;/code&gt; first.&lt;/p&gt;
&lt;h2 id="testing-and-validation"&gt;Testing and validation
&lt;/h2&gt;&lt;p&gt;Terraform has multiple layers of validation, from syntax checking to full integration tests against real infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Syntax and input validation&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform validate &lt;span style="color:#75715e"&gt;# checks syntax, module references, variable constraints&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform plan &lt;span style="color:#75715e"&gt;# dry run — shows exactly what will change before touching anything&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;validate&lt;/code&gt; catches configuration errors early. &lt;code&gt;plan&lt;/code&gt; is the key safety step — a &lt;code&gt;+&lt;/code&gt; is create, &lt;code&gt;~&lt;/code&gt; is update in-place, &lt;code&gt;-/+&lt;/code&gt; is replace, &lt;code&gt;-&lt;/code&gt; is destroy. Read it before every apply.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Native unit tests (v1.6+)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tests live in &lt;code&gt;.tftest.hcl&lt;/code&gt; files alongside the configuration. &lt;code&gt;run&lt;/code&gt; blocks execute a plan or apply in isolation; &lt;code&gt;assert&lt;/code&gt; blocks check the result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;run&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vpc_has_correct_cidr&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;plan&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; condition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; aws_vpc.main.cidr_block &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;10.0.0.0/16&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error_message &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;VPC CIDR does not match expected value&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run with &lt;code&gt;terraform test&lt;/code&gt;. Good for validating modules before promotion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For end-to-end validation against real AWS resources, &lt;a class="link" href="https://terratest.gruntwork.io/" target="_blank" rel="noopener"
 &gt;Terratest&lt;/a&gt; is the most widely used framework — Go tests that deploy infrastructure, run assertions via the AWS SDK, then tear it all down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Policy and compliance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OPA/Rego, Conftest, or HashiCorp Sentinel can enforce policies at plan time — before anything is deployed. Useful for ensuring security groups are not wide open, IAM roles follow least privilege, tags are present on all resources.&lt;/p&gt;
&lt;h2 id="beyond-infrastructure"&gt;Beyond infrastructure
&lt;/h2&gt;&lt;p&gt;The industry standard, and the scope is wider than cloud resources. The provider model means anything with an API can be managed as Terraform code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database schemas and users&lt;/strong&gt; — PostgreSQL, MySQL, MongoDB providers manage schemas, roles, and users as code. Database configuration follows the same plan/apply workflow as the VPC it runs in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS&lt;/strong&gt; — Route53, Cloudflare, and others are first-class providers. DNS changes go through version control and code review.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt; — repositories, teams, branch protection, secrets. Useful for managing org configuration at scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt; — namespaces, RBAC, CRDs. Useful for bootstrapping before other tooling runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets management&lt;/strong&gt; — Vault, AWS Secrets Manager, SSM Parameter Store all have providers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The implication: Terraform can be the single source of truth for everything from network topology to application database users. One workflow, one state. If there is a provider for it, Terraform is probably the right tool.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.hashicorp.com/terraform/docs" target="_blank" rel="noopener"
 &gt;Terraform documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/" target="_blank" rel="noopener"
 &gt;Terraform Registry&lt;/a&gt; — providers and modules&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/terraform-aws-modules" target="_blank" rel="noopener"
 &gt;terraform-aws-modules&lt;/a&gt; — well-maintained AWS module library&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://terragrunt.gruntwork.io/docs/" target="_blank" rel="noopener"
 &gt;Terragrunt documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>