<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://0xmdrza.gitlab.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://0xmdrza.gitlab.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-08-02T12:14:35-04:00</updated><id>https://0xmdrza.gitlab.io/feed.xml</id><title type="html">MMDRZA</title><subtitle>Developer portfolio and field notes. Built for people who ship.</subtitle><entry><title type="html">Terraform modules that stay readable</title><link href="https://0xmdrza.gitlab.io/posts/terraform-modules-that-stay-readable/" rel="alternate" type="text/html" title="Terraform modules that stay readable" /><published>2026-07-27T02:15:00-04:00</published><updated>2026-07-27T02:15:00-04:00</updated><id>https://0xmdrza.gitlab.io/posts/terraform-modules-that-stay-readable</id><content type="html" xml:base="https://0xmdrza.gitlab.io/posts/terraform-modules-that-stay-readable/"><![CDATA[<p>Terraform code rots faster than application code. Not because the language is bad, but because it’s easy to write infrastructure that works today and is incomprehensible in six months. A few structural habits prevent most of that.</p>

<!--more-->

<h2 id="modules-should-do-one-thing">Modules should do one thing</h2>

<p>A module that creates a VPC, a database, a cache, and a monitoring stack is not a module. It’s a novel. Split it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>modules/
  vpc/
    main.tf
    variables.tf
    outputs.tf
  database/
    main.tf
    variables.tf
    outputs.tf
  monitoring/
    main.tf
    variables.tf
    outputs.tf
</code></pre></div></div>

<p>Each module should be understandable in one read. If you need to scroll to understand what a module creates, it’s doing too much.</p>

<h2 id="variables-need-contracts">Variables need contracts</h2>

<p>Every variable should have a type, a description, and a default when a sensible one exists.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">variable</span> <span class="s2">"instance_type"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="o">=</span> <span class="nx">string</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"EC2 instance class for the app servers"</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="s2">"t3.medium"</span>

  <span class="nx">validation</span> <span class="p">{</span>
    <span class="nx">condition</span>     <span class="o">=</span> <span class="nx">can</span><span class="p">(</span><span class="nx">regex</span><span class="p">(</span><span class="s2">"^t3</span><span class="err">\\</span><span class="s2">."</span><span class="p">,</span> <span class="nx">var</span><span class="p">.</span><span class="nx">instance_type</span><span class="p">))</span>
    <span class="nx">error_message</span> <span class="o">=</span> <span class="s2">"Only t3 instance types are supported for this workload."</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">variable</span> <span class="s2">"enable_monitoring"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="o">=</span> <span class="nx">bool</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"Whether to attach CloudWatch agent and alarms"</span>
  <span class="nx">default</span>     <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The validation block is not optional. It’s the difference between a typo caught at plan time and a production outage at apply time.</p>

<h2 id="outputs-are-the-api">Outputs are the API</h2>

<p>A module’s outputs are its public interface. Export what consumers need, nothing more.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">output</span> <span class="s2">"vpc_id"</span> <span class="p">{</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"The ID of the created VPC"</span>
  <span class="nx">value</span>       <span class="o">=</span> <span class="nx">aws_vpc</span><span class="p">.</span><span class="nx">main</span><span class="p">.</span><span class="nx">id</span>
<span class="p">}</span>

<span class="nx">output</span> <span class="s2">"private_subnet_ids"</span> <span class="p">{</span>
  <span class="nx">description</span> <span class="o">=</span> <span class="s2">"IDs of the private subnets, for use by app modules"</span>
  <span class="nx">value</span>       <span class="o">=</span> <span class="nx">aws_subnet</span><span class="p">.</span><span class="nx">private</span><span class="p">[*].</span><span class="nx">id</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Don’t export every attribute of every resource. That couples consumers to implementation details you might want to change later.</p>

<h2 id="state-is-sacred">State is sacred</h2>

<p>Remote state, state locking, and a clear separation between environments. Non-negotiable.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">backend</span> <span class="s2">"s3"</span> <span class="p">{</span>
    <span class="nx">bucket</span>         <span class="o">=</span> <span class="s2">"terraform-state-prod"</span>
    <span class="nx">key</span>            <span class="o">=</span> <span class="s2">"app/terraform.tfstate"</span>
    <span class="nx">region</span>         <span class="o">=</span> <span class="s2">"us-east-1"</span>
    <span class="nx">dynamodb_table</span> <span class="o">=</span> <span class="s2">"terraform-locks"</span>
    <span class="nx">encrypt</span>        <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>One state file per environment per component. Never share state between dev and prod. Never run <code class="language-plaintext highlighter-rouge">terraform apply</code> from a laptop against production state.</p>

<h2 id="the-test-that-matters">The test that matters</h2>

<p>The best Terraform test is <code class="language-plaintext highlighter-rouge">terraform plan</code> in CI on every pull request. If the plan output is clean, the change is probably safe. If it’s a wall of red, something is wrong.</p>

<p>Readable infrastructure is a team sport. Write it for the person who has to change it at 2am, because eventually that person is you.</p>]]></content><author><name></name></author><category term="Infrastructure" /><category term="terraform" /><category term="iac" /><category term="aws" /><summary type="html"><![CDATA[How to structure Terraform so the next person doesn't curse your name.]]></summary></entry><entry><title type="html">Rust CLI tools that feel native</title><link href="https://0xmdrza.gitlab.io/posts/rust-cli-tools-that-feel-native/" rel="alternate" type="text/html" title="Rust CLI tools that feel native" /><published>2026-07-19T07:30:00-04:00</published><updated>2026-07-19T07:30:00-04:00</updated><id>https://0xmdrza.gitlab.io/posts/rust-cli-tools-that-feel-native</id><content type="html" xml:base="https://0xmdrza.gitlab.io/posts/rust-cli-tools-that-feel-native/"><![CDATA[<p>A CLI tool can be fast, correct, and still feel wrong. The difference between a tool people tolerate and a tool people reach for comes down to a handful of small decisions that most developers skip.</p>

<!--more-->

<h2 id="output-is-the-interface">Output is the interface</h2>

<p>Your CLI’s output is its UI. Treat it that way.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::{</span><span class="k">self</span><span class="p">,</span> <span class="n">Write</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Wrong: dumping raw data</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"{:?}"</span><span class="p">,</span> <span class="n">results</span><span class="p">);</span>

    <span class="c1">// Right: structured, scannable output</span>
    <span class="k">for</span> <span class="n">item</span> <span class="k">in</span> <span class="o">&amp;</span><span class="n">results</span> <span class="p">{</span>
        <span class="nd">println!</span><span class="p">(</span><span class="s">"  {}  {:&lt;24}  {}"</span><span class="p">,</span> <span class="n">item</span><span class="nf">.status_icon</span><span class="p">(),</span> <span class="n">item</span><span class="py">.name</span><span class="p">,</span> <span class="n">item</span><span class="py">.detail</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Always flush before exit on interactive output</span>
    <span class="nn">io</span><span class="p">::</span><span class="nf">stdout</span><span class="p">()</span><span class="nf">.flush</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Align columns. Use color to communicate state, not to decorate. Red means failure, green means success, yellow means attention. Never use color as the only signal, and always respect <code class="language-plaintext highlighter-rouge">NO_COLOR</code>.</p>

<h2 id="errors-should-teach">Errors should teach</h2>

<p>A good error message tells the user what went wrong and what to do next.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Wrong</span>
<span class="nd">eprintln!</span><span class="p">(</span><span class="s">"Error: connection failed"</span><span class="p">);</span>

<span class="c1">// Right</span>
<span class="nd">eprintln!</span><span class="p">(</span><span class="s">"error: could not reach registry.example.com:443"</span><span class="p">);</span>
<span class="nd">eprintln!</span><span class="p">(</span><span class="s">"  hint: check your network connection or VPN status"</span><span class="p">);</span>
<span class="nd">eprintln!</span><span class="p">(</span><span class="s">"  hint: run with --offline to use the local cache"</span><span class="p">);</span>
</code></pre></div></div>

<p>Exit codes matter too. Zero for success, non-zero for failure, and different codes for different failure classes so scripts can branch on them.</p>

<h2 id="flags-should-feel-inevitable">Flags should feel inevitable</h2>

<p>Follow the conventions users already know:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">-h</code> / <code class="language-plaintext highlighter-rouge">--help</code> for help</li>
  <li><code class="language-plaintext highlighter-rouge">-v</code> / <code class="language-plaintext highlighter-rouge">--verbose</code> for more output</li>
  <li><code class="language-plaintext highlighter-rouge">-q</code> / <code class="language-plaintext highlighter-rouge">--quiet</code> for less output</li>
  <li><code class="language-plaintext highlighter-rouge">--version</code> for version info</li>
  <li><code class="language-plaintext highlighter-rouge">--dry-run</code> when the tool changes state</li>
</ul>

<p>Use <code class="language-plaintext highlighter-rouge">clap</code> with derive macros. The help output it generates is better than anything you’ll write by hand.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">clap</span><span class="p">::</span><span class="n">Parser</span><span class="p">;</span>

<span class="nd">#[derive(Parser)]</span>
<span class="nd">#[command(name</span> <span class="nd">=</span> <span class="s">"wiretap"</span><span class="nd">,</span> <span class="nd">about</span> <span class="nd">=</span> <span class="s">"Inspect HTTP traffic from the terminal"</span><span class="nd">)]</span>
<span class="k">struct</span> <span class="n">Args</span> <span class="p">{</span>
    <span class="cd">/// Port to listen on</span>
    <span class="nd">#[arg(short,</span> <span class="nd">long,</span> <span class="nd">default_value_t</span> <span class="nd">=</span> <span class="mi">8080</span><span class="nd">)]</span>
    <span class="n">port</span><span class="p">:</span> <span class="nb">u16</span><span class="p">,</span>

    <span class="cd">/// Filter by route pattern</span>
    <span class="nd">#[arg(short,</span> <span class="nd">long)]</span>
    <span class="n">filter</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span>

    <span class="cd">/// Output raw JSON instead of formatted text</span>
    <span class="nd">#[arg(long)]</span>
    <span class="n">json</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="speed-is-a-feature">Speed is a feature</h2>

<p>Users notice startup time. If your tool takes 400ms to print help, it feels broken. Keep the binary small, avoid lazy network calls at startup, and profile the cold path.</p>

<p>The best CLI tools feel instant. That’s not an accident. It’s a design decision.</p>]]></content><author><name></name></author><category term="Tooling" /><category term="rust" /><category term="cli" /><category term="developer-experience" /><summary type="html"><![CDATA[The small design decisions that make a terminal tool feel like it belongs.]]></summary></entry><entry><title type="html">Deploy pipelines that don’t wake you up</title><link href="https://0xmdrza.gitlab.io/posts/deploy-pipelines-that-dont-wake-you-up/" rel="alternate" type="text/html" title="Deploy pipelines that don’t wake you up" /><published>2026-07-08T03:00:00-04:00</published><updated>2026-07-08T03:00:00-04:00</updated><id>https://0xmdrza.gitlab.io/posts/deploy-pipelines-that-dont-wake-you-up</id><content type="html" xml:base="https://0xmdrza.gitlab.io/posts/deploy-pipelines-that-dont-wake-you-up/"><![CDATA[<p>The best deploy pipeline is the one you never think about. Code merges, tests run, artifacts build, production updates, and nobody gets paged. That’s the goal. Everything else is a compromise.</p>

<!--more-->

<h2 id="the-failure-mode-that-matters">The failure mode that matters</h2>

<p>Most pipelines fail in the same way: a change passes CI, lands in production, and breaks something that CI never tested. The fix is not more tests. The fix is a pipeline that assumes failure and routes around it.</p>

<h2 id="three-rules">Three rules</h2>

<p><strong>1. Every deploy is reversible in one step.</strong> If rolling back requires a meeting, your pipeline is broken. Rollback should be a button, not a procedure.</p>

<p><strong>2. Health checks gate the rollout.</strong> A deploy that reports success while the app is crash-looping is a liar. The pipeline should verify the service is actually healthy before it calls the job done.</p>

<p><strong>3. Small batches beat big bangs.</strong> Ten small deploys a day is safer than one large deploy a week. The blast radius of each change stays small, and the cause of any failure is obvious.</p>

<h2 id="a-minimal-gitlab-pipeline">A minimal GitLab pipeline</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">stages</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">test</span>
  <span class="pi">-</span> <span class="s">build</span>
  <span class="pi">-</span> <span class="s">deploy</span>
  <span class="pi">-</span> <span class="s">verify</span>

<span class="na">test</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">bundle exec rspec</span>

<span class="na">build</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">docker build -t app:$CI_COMMIT_SHORT_SHA .</span>
    <span class="pi">-</span> <span class="s">docker push registry.example.com/app:$CI_COMMIT_SHORT_SHA</span>

<span class="na">deploy</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./scripts/deploy.sh $CI_COMMIT_SHORT_SHA</span>
  <span class="na">environment</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">production</span>

<span class="na">verify</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">verify</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">./scripts/healthcheck.sh || ./scripts/rollback.sh</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">verify</code> stage is the part most teams skip. It’s also the part that matters most. If the health check fails, the pipeline rolls back automatically and alerts the team. No human required.</p>

<h2 id="what-healthy-means">What “healthy” means</h2>

<p>A health check is not a 200 response from <code class="language-plaintext highlighter-rouge">/</code>. It’s a check that the service can actually do its job: connect to the database, reach its dependencies, and respond within a latency budget.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># healthcheck.sh</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>1..12<span class="o">}</span><span class="p">;</span> <span class="k">do
  if </span>curl <span class="nt">-sf</span> <span class="nt">--max-time</span> 5 https://app.example.com/health/deep <span class="o">&gt;</span> /dev/null<span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"healthy"</span>
    <span class="nb">exit </span>0
  <span class="k">fi
  </span><span class="nb">sleep </span>10
<span class="k">done

</span><span class="nb">echo</span> <span class="s2">"unhealthy after 2 minutes"</span>
<span class="nb">exit </span>1
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">/health/deep</code> endpoint checks the database connection, the cache, and any critical downstream services. A shallow ping tells you the process is alive. A deep check tells you the service is working.</p>

<h2 id="the-real-lesson">The real lesson</h2>

<p>Pipelines are not about automation. They’re about trust. When the team trusts the pipeline, they deploy often, they take smaller risks, and they sleep through the night. When they don’t, they batch changes, delay releases, and get woken up anyway.</p>

<p>Build the pipeline you’d trust at 3am. Then never get woken up by it.</p>]]></content><author><name></name></author><category term="Infrastructure" /><category term="ci-cd" /><category term="deploy" /><category term="reliability" /><summary type="html"><![CDATA[How I design CI/CD so a bad deploy is a non-event, not a 3am incident.]]></summary></entry></feed>