{"id":45,"date":"2026-10-01T16:28:16","date_gmt":"2026-10-01T16:28:16","guid":{"rendered":"https:\/\/pomax-v3.weeltec.com\/?p=45"},"modified":"2026-10-01T16:28:16","modified_gmt":"2026-10-01T16:28:16","slug":"infrastructure-as-code-review-is-the-point","status":"publish","type":"post","link":"https:\/\/pomax-v3.weeltec.com\/?p=45","title":{"rendered":"Infrastructure as Code: The Review Is the Point"},"content":{"rendered":"<div class=\"wt-post\">\n<style>.wt-post { --wt-bg: #0A0C0F; --wt-bg-2: #0E1116; --wt-surface: #12161C; --wt-line: #1F252E; --wt-line-strong: #2B333E; --wt-text: #E9ECEF; --wt-muted: #98A2AD; --wt-accent: #B9F24D; --wt-accent-hover: #C8FF5E; --wt-accent-dim: rgba(185, 242, 77, 0.10); --wt-accent-line: rgba(185, 242, 77, 0.25); --wt-danger: #F07A6B; --wt-ok: #7ED99A; --wt-sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif; --wt-mono: ui-monospace, \"Cascadia Code\", \"JetBrains Mono\", \"SF Mono\", Menlo, Consolas, monospace; --wt-radius: 4px; --wt-h1: var(--wt-text); --wt-h2: var(--wt-text); --wt-h3: var(--wt-text); box-sizing: border-box; background: var(--wt-bg); color: var(--wt-text); font-family: var(--wt-sans); font-size: 1rem; line-height: 1.7; padding: clamp(1.75rem, 4vw, 3rem); border: 1px solid var(--wt-line); border-radius: 0; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } .wt-post *, .wt-post *::before, .wt-post *::after { box-sizing: border-box; } .wt-post ::selection { background: var(--wt-accent); color: var(--wt-bg); } .wt-post.wt-post p { color: var(--wt-text); font-family: var(--wt-sans); font-size: 1rem; line-height: 1.7; margin: 0 0 1.15rem; max-width: 68ch; } .wt-post.wt-post p:last-child { margin-bottom: 0; } .wt-post.wt-post h1, .wt-post.wt-post h2, .wt-post.wt-post h3, .wt-post.wt-post h4, .wt-post.wt-post h5, .wt-post.wt-post h6 { font-family: var(--wt-sans); font-weight: 700; letter-spacing: -0.025em; line-height: 1.15; text-wrap: balance; } .wt-post.wt-post h2 { color: var(--wt-h2); font-size: clamp(1.45rem, 3vw, 2rem); margin: 2.4rem 0 0.9rem; display: flex; align-items: baseline; gap: 0.6rem; } .wt-post.wt-post h2::before { content: \"\"; flex: none; width: 8px; height: 8px; background: var(--wt-accent); transform: translateY(-2px); } .wt-post.wt-post h3 { color: var(--wt-h3); font-size: 1.15rem; font-weight: 650; margin: 1.8rem 0 0.7rem; padding-left: 0.85rem; border-left: 2px solid var(--wt-accent-line); } .wt-post.wt-post h2:first-child, .wt-post.wt-post h3:first-child { margin-top: 0; } .wt-post.wt-post strong { color: #FFFFFF; font-weight: 650; } .wt-post.wt-post em { color: var(--wt-muted); font-style: italic; } .wt-post.wt-post a { color: var(--wt-accent); text-decoration: none; border-bottom: 1px solid var(--wt-accent-line); transition: color 0.15s ease, border-color 0.15s ease; } .wt-post.wt-post a:hover { color: var(--wt-accent-hover); border-bottom-color: var(--wt-accent-hover); } .wt-post.wt-post ul, .wt-post.wt-post ol { margin: 0 0 1.3rem; padding: 0; list-style: none; max-width: 68ch; } .wt-post.wt-post li { position: relative; padding-left: 1.6rem; margin-bottom: 0.55rem; color: var(--wt-text); line-height: 1.65; } .wt-post.wt-post ul > li::before { content: \"\\25AE\"; color: var(--wt-accent); position: absolute; left: 0; top: 0; font-size: 0.85em; line-height: 1.65; } .wt-post.wt-post ol { counter-reset: wt-li; } .wt-post.wt-post ol > li { counter-increment: wt-li; } .wt-post.wt-post ol > li::before { content: counter(wt-li) \".\"; font-family: var(--wt-mono); font-size: 0.8em; color: var(--wt-accent); position: absolute; left: 0; top: 0; line-height: 1.9; } .wt-post.wt-post blockquote { margin: 1.8rem 0; padding: 1.1rem 1.4rem; background: var(--wt-accent-dim); border-left: 2px solid var(--wt-accent); border-radius: 0; color: var(--wt-text); font-size: 1.05rem; font-style: normal; line-height: 1.6; } .wt-post.wt-post blockquote p { margin: 0; color: var(--wt-text); font-style: normal; } .wt-post.wt-post blockquote::before { content: none; } .wt-post.wt-post code, .wt-post.wt-post kbd, .wt-post.wt-post pre { font-family: var(--wt-mono); font-size: 0.88em; } .wt-post.wt-post code { background: var(--wt-surface); border: 1px solid var(--wt-line); border-radius: var(--wt-radius); padding: 0.1em 0.4em; color: var(--wt-accent); } .wt-post.wt-post pre { background: #0C0F13; border: 1px solid var(--wt-line-strong); border-radius: var(--wt-radius); padding: 1.1rem 1.25rem; overflow-x: auto; color: var(--wt-text); line-height: 1.7; margin: 0 0 1.3rem; } .wt-post.wt-post pre code { background: none; border: 0; padding: 0; color: inherit; } .wt-post.wt-post hr { border: 0; border-top: 1px solid var(--wt-line); margin: 2.2rem 0; } .wt-post.wt-post img { max-width: 100%; height: auto; border-radius: var(--wt-radius); border: 1px solid var(--wt-line); } @media (max-width: 640px) { .wt-post.wt-post h2 { font-size: 1.35rem; } .wt-post.wt-post blockquote { padding: 0.9rem 1.1rem; } } @media (prefers-reduced-motion: reduce) { .wt-post.wt-post a { transition: none; } }<\/style>\n<p>Teams adopt infrastructure as code for the wrong reason. They want a script that creates a server. What they actually get \u2014 if they do it properly \u2014 is a permanent, reviewable record of every change to their production estate.<\/p>\n<p>The provisioning is the easy part. The review is where the value lives, and it's the part most teams quietly skip by letting one person with the credentials apply changes from a laptop.<\/p>\n<h2>A plan nobody reads is a script with extra ceremony<\/h2>\n<p>Terraform, Pulumi, CloudFormation \u2014 the tool barely matters. What matters is that every change is proposed as a diff, reviewed before it runs, and applied by a system rather than a human with too much access.<\/p>\n<p>The failure mode is familiar. CI runs <em>terraform apply<\/em> automatically on merge. Nobody reads the plan, because the plan is 400 lines of noise. Then someone bumps an AMI and the plan quietly destroys and recreates a database because a field changed that forces replacement. The diff was right there. Nobody looked.<\/p>\n<h3>Make the plan legible<\/h3>\n<p>If a plan is too large to read, it's too large to merge. Break the change up. Put the destructive actions at the top. Fail the pipeline on any plan that deletes or replaces a stateful resource unless it carries an explicit override label, reviewed by a second person.<\/p>\n<h3>Review the plan, not the syntax<\/h3>\n<p>Two reviewers looking at HCL formatting are adding nothing. The useful review question is: what does this do to the running system? That means the plan output is the artefact under review, posted on the pull request, not a log line buried in a job.<\/p>\n<h2>Five things that break in real IaC repos<\/h2>\n<ul>\n<li><strong>State is a single point of failure.<\/strong> One remote state file shared by everything, no locking, and a corrupted write takes out the whole estate. Split state by lifecycle, and enable locking before you need it.<\/li>\n<li><strong>Manual changes.<\/strong> Someone fixes an outage in the console at 2am and never back-ports it. Drift accumulates until the next apply silently reverts the fix \u2014 and the outage returns.<\/li>\n<li><strong>Secrets in the repo.<\/strong> Variables file with an access key committed once and never rotated. It's in the history forever, even after you delete the line.<\/li>\n<li><strong>No environment parity.<\/strong> Staging is hand-built and production is code. The first deploy proves they were never the same thing.<\/li>\n<li><strong>Everyone has apply rights.<\/strong> No separation between proposing a change and approving it. The review is theatre when the same person can merge and apply.<\/li>\n<\/ul>\n<h2>The workflow that actually holds up<\/h2>\n<p>A change starts on a branch. CI runs a speculative plan against the real state and posts it as a comment. A human reads it, not the code. Merge triggers an apply that is gated \u2014 either an approval step or a policy check that blocks destructive actions on protected resources.<\/p>\n<p>Drift detection runs on a schedule, and any divergence from code is itself an incident with an owner. That's the part teams neglect: without drift detection, your code describes an intention, not reality, and the gap only surfaces during the next emergency.<\/p>\n<h3>Prove it from scratch<\/h3>\n<p>Once a quarter, stand up an environment from the repository alone, with no manual steps and no tribal knowledge, and time it. That single exercise finds undocumented dependencies, hard-coded values, and the credentials someone pasted into a console six months ago. A repo that can't rebuild its own environment isn't infrastructure as code \u2014 it's documentation that happens to run.<\/p>\n<blockquote>\n<p>Infrastructure as code isn't valuable because it automates a server. It's valuable because it makes every change to production something a human can read, question, and reject.<\/p>\n<\/blockquote>\n<h2>How we approach it at Weeltec<\/h2>\n<p>We restructure infrastructure repositories so that state is split, locking is enforced, secrets live in a managed store, and applying is something the pipeline does and nobody does by hand. Almost always the first deliverable isn't new code \u2014 it's making the existing plan small enough that a reviewer can actually catch the change that matters.<\/p>\n<p>Permissions get the same treatment. Engineers propose through pull requests; the apply role is held by the CI system, scoped per environment, and auditable.<\/p>\n<h2>The rule<\/h2>\n<p>If your infrastructure changes don't pass through a diff that someone reads before it touches production, you don't have infrastructure as code. You have infrastructure as a habit, and habits don't survive a bad Tuesday.<\/p>\n<p><em>Weeltec builds and reviews infrastructure-as-code setups for teams that want every production change to be a decision, not an accident. If your applies go straight to production unreviewed, <a href=\"https:\/\/weeltec.com\/#contact\">get a quote<\/a> and we'll put the review back where it belongs.<\/em><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Infrastructure as code is not valuable because it automates servers. It is valuable because every production change becomes a diff someone can read, question, and reject before it runs.<\/p>\n","protected":false},"author":1,"featured_media":44,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[21,6,19,20],"class_list":["post-45","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-code-review","tag-devops","tag-infrastructure-as-code","tag-terraform"],"_links":{"self":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/45","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=45"}],"version-history":[{"count":2,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/45\/revisions"}],"predecessor-version":[{"id":80,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/45\/revisions\/80"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/media\/44"}],"wp:attachment":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=45"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=45"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=45"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}