<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mykhailo</title>
    <description>The latest articles on DEV Community by Mykhailo (@yetmike).</description>
    <link>https://dev.to/yetmike</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3901143%2F338ae527-f75c-4c2d-846c-358209ca021f.jpg</url>
      <title>DEV Community: Mykhailo</title>
      <link>https://dev.to/yetmike</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yetmike"/>
    <language>en</language>
    <item>
      <title>Cilium Certified Associate: I Scored 82, and eBPF Was Only 10% of It</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sun, 01 Nov 2026 11:54:19 +0000</pubDate>
      <link>https://dev.to/yetmike/cilium-certified-associate-i-scored-82-and-ebpf-was-only-10-of-it-2npe</link>
      <guid>https://dev.to/yetmike/cilium-certified-associate-i-scored-82-and-ebpf-was-only-10-of-it-2npe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version with the full domain breakdown and my prep notes on &lt;a href="https://yetmike.com/blog/cca-exam" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most people study for the Cilium Certified Associate like it's an eBPF exam. eBPF is 10% of it.&lt;/p&gt;

&lt;p&gt;I passed the &lt;a href="https://training.linuxfoundation.org/certification/cilium-certified-associate-cca/" rel="noopener noreferrer"&gt;CCA&lt;/a&gt; on my first attempt with 82 (passing is 75). Of the 15 CNCF certifications on my way to &lt;a href="https://www.cncf.io/training/kubestronaut/" rel="noopener noreferrer"&gt;Golden Kubestronaut&lt;/a&gt;, this is the one where my networking background helped the least.&lt;/p&gt;

&lt;h2&gt;
  
  
  Study to the weights
&lt;/h2&gt;

&lt;p&gt;60 multiple-choice questions, 90 minutes, online proctored. The official Linux Foundation weights:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Policy&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service Mesh&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Observability&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installation and Configuration&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster Mesh&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eBPF&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BGP and External Networking&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Architecture, Network Policy and Service Mesh are 54% together. eBPF is about six questions. I see people (me included, partly) pour hours into eBPF internals and BGP while under-preparing Service Mesh, which is worth nearly three times as much as BGP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity, not IP
&lt;/h2&gt;

&lt;p&gt;Cilium runs &lt;a href="https://ebpf.io/what-is-ebpf/" rel="noopener noreferrer"&gt;eBPF&lt;/a&gt; programs in the Linux kernel, with no iptables and no kube-proxy required. That's why the CKA only carries you part of the way here. Start with the fundamentals anyway, because the bigger domains assume them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;eBPF programs are checked by the verifier at load time. No infinite loops, no invalid memory access.&lt;/li&gt;
&lt;li&gt;eBPF maps are key-value stores in kernel memory, shared between programs and user space. Cilium uses them for policy enforcement and flow tracking.&lt;/li&gt;
&lt;li&gt;Every workload gets a numeric identity derived from its pod labels, and policy is enforced on that identity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If one idea decides your score, it's the third one. The wrong answer is usually some version of "the policy uses the pod's IP". Pod IPs change on every restart, and Cilium's model exists to avoid that dependency. Also know that cilium-agent runs on every node, manages the eBPF programs and maps there, and talks to the Cilium operator.&lt;/p&gt;

&lt;h2&gt;
  
  
  L7 policy needs Envoy
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://docs.cilium.io/en/stable/network/kubernetes/policy/" rel="noopener noreferrer"&gt;CiliumNetworkPolicy&lt;/a&gt; with HTTP rules looks like any other YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;fromEndpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;toPorts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt;
            &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
        &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/v1/.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in it tells you those rules silently depend on the Envoy proxy being deployed. The exam gives you a scenario where HTTP filtering doesn't work and asks why. Envoy isn't there.&lt;/p&gt;

&lt;p&gt;CiliumNetworkPolicy is namespaced. CiliumClusterwideNetworkPolicy has no namespace and covers the whole cluster. The scenario usually hints at which one it wants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Mesh is where I lost points
&lt;/h2&gt;

&lt;p&gt;I walked in thinking "network policy is network policy" and got hit by questions on &lt;a href="https://docs.cilium.io/en/stable/security/network/encryption/" rel="noopener noreferrer"&gt;transparent encryption&lt;/a&gt; modes and certificate-based mutual authentication. That's probably where most of my missing points went.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/WireGuard" rel="noopener noreferrer"&gt;WireGuard&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/IPsec" rel="noopener noreferrer"&gt;IPsec&lt;/a&gt; and mutual auth are all fair game. I'm not sure whether the exam goes deep on comparing the two encryption modes or only checks that you know both exist. I'd read both sections of the &lt;a href="https://docs.cilium.io/en/stable/network/servicemesh/" rel="noopener noreferrer"&gt;Cilium Service Mesh docs&lt;/a&gt; rather than gamble.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hubble and kube-proxy replacement
&lt;/h2&gt;

&lt;p&gt;Know the per-node Hubble server inside cilium-agent versus Hubble Relay, which aggregates flows across the cluster. Without Relay, &lt;code&gt;hubble observe&lt;/code&gt; only shows the local node, and that exact situation is a question. Drill the filters from the &lt;a href="https://docs.cilium.io/en/stable/observability/hubble/" rel="noopener noreferrer"&gt;Hubble docs&lt;/a&gt;: &lt;code&gt;--namespace&lt;/code&gt;, &lt;code&gt;--label&lt;/code&gt;, &lt;code&gt;--verdict DROPPED&lt;/code&gt;, &lt;code&gt;--type l7 --protocol DNS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/" rel="noopener noreferrer"&gt;kube-proxy replacement&lt;/a&gt;, service lookup goes from O(n) iptables chain traversal to an O(1) map lookup. Know Direct Server Return (responses bypass the load balancer) and when it helps. Skim the IPAM modes and what &lt;a href="https://docs.cilium.io/en/stable/network/clustermesh/" rel="noopener noreferrer"&gt;Cluster Mesh&lt;/a&gt; does across clusters while you're there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two weeks of prep
&lt;/h2&gt;

&lt;p&gt;About 8 hours went into the free &lt;a href="https://isovalent.com/labs/" rel="noopener noreferrer"&gt;Isovalent labs&lt;/a&gt;, which I think are the best resource for this exam: real Cilium clusters with real eBPF in the browser. Do every lab tagged CCA. After each one I wrote the main commands or YAML from memory right away. Reading lab output didn't stick for me, recall did.&lt;/p&gt;

&lt;p&gt;Reading the &lt;a href="https://docs.cilium.io/" rel="noopener noreferrer"&gt;Cilium docs&lt;/a&gt; top to bottom didn't work. I spent two hours in the BGP section before realizing it's a small part of a 6% domain. The &lt;a href="https://github.com/isovalent/CCA-Study-Guide" rel="noopener noreferrer"&gt;official CCA study guide&lt;/a&gt; maps topics to domains and is a better way to decide what to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;82, first attempt. The security side cost me points, and the eBPF and Hubble sections gave them back.&lt;/p&gt;

&lt;p&gt;I put together a &lt;a href="https://yetmike.com/practice/cca" rel="noopener noreferrer"&gt;free CCA practice test&lt;/a&gt; for my own final review: questions across all eight domains in the same multiple-choice format as the real exam. Use it early, while there's still time to do labs for your weak domains. Sets for the other CNCF exams are at &lt;a href="https://yetmike.com/tests" rel="noopener noreferrer"&gt;yetmike.com/tests&lt;/a&gt;, and my study links for the whole Kubestronaut path live in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I only started thinking of Cilium as a kernel platform after the exam. Now I wonder how many of my Service Mesh mistakes were architecture mistakes in disguise.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/cca-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cilium</category>
      <category>ebpf</category>
      <category>certification</category>
    </item>
    <item>
      <title>Passing the OTCA With 83: Half the Exam Is the SDK Pipeline</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:17:50 +0000</pubDate>
      <link>https://dev.to/yetmike/passing-the-otca-with-83-half-the-exam-is-the-sdk-pipeline-4k1i</link>
      <guid>https://dev.to/yetmike/passing-the-otca-with-83-half-the-exam-is-the-sdk-pipeline-4k1i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with everything I read and what wasted my time, on &lt;a href="https://yetmike.com/blog/otca-exam" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can run Prometheus perfectly in production and still fail the OTCA.&lt;/p&gt;

&lt;p&gt;If you've turned on auto-instrumentation or shipped telemetry to a Collector sidecar, you were inside the OpenTelemetry pipeline without ever being tested on how it works. The &lt;a href="https://training.linuxfoundation.org/certification/opentelemetry-certified-associate-otca/" rel="noopener noreferrer"&gt;OpenTelemetry Certified Associate&lt;/a&gt; tests what happens inside it. I passed with 83 on the first attempt, one of the 15 CNCF certifications I did on the way to Golden Kubestronaut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format and weights
&lt;/h2&gt;

&lt;p&gt;60 multiple-choice questions, 90 minutes, pass at 75%.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The OpenTelemetry API and SDK&lt;/td&gt;
&lt;td&gt;46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The OpenTelemetry Collector&lt;/td&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fundamentals of Observability&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintaining and Debugging Observability Pipelines&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nearly half the exam is SDK internals, the part most people never read because the library works once you add it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't study backends
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://prometheus.io/docs/introduction/overview/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;, Grafana and Jaeger are out of scope. OTCA covers the layer in front of them: how code produces a span, where it travels inside the SDK, and what the Collector does with it next. It's language-agnostic too, so no Go or Python syntax. The OTel Operator gets a mention, but I didn't see it tested in depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SDK domain
&lt;/h2&gt;

&lt;p&gt;Fundamentals (18%) is cheap points if you know what a trace is, what a metric data point looks like and why &lt;a href="https://opentelemetry.io/docs/concepts/semantic-conventions/" rel="noopener noreferrer"&gt;semantic conventions&lt;/a&gt; exist.&lt;/p&gt;

&lt;p&gt;The SDK domain is harder in a specific way. Questions ask which layer owns a behavior, and the wrong options are real OTel concepts placed one layer up or one layer down. The model I'd build first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Providers.&lt;/strong&gt; TracerProvider, MeterProvider, LoggerProvider, one entry point per signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processors&lt;/strong&gt; transform or batch data. Know BatchSpanProcessor versus SimpleSpanProcessor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exporters&lt;/strong&gt; (OTLP, Console, Zipkin) are where data leaves the SDK.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then the API versus SDK split from the &lt;a href="https://opentelemetry.io/docs/specs/otel/overview/" rel="noopener noreferrer"&gt;specification overview&lt;/a&gt;: the API defines interfaces, the SDK implements them. I had been treating them as one thing, and that costs a handful of easy questions.&lt;/p&gt;

&lt;p&gt;File-based &lt;a href="https://opentelemetry.io/docs/languages/sdk-configuration/" rel="noopener noreferrer"&gt;SDK configuration&lt;/a&gt; is tested as well. Provider contains processor contains exporter, and once you see that nesting the config questions get much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context propagation
&lt;/h2&gt;

&lt;p&gt;I think people skip this, and it shows up more than you'd expect. In-process, Baggage and SpanContext flow through the language's Context API. Across processes it's &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt;: the &lt;code&gt;traceparent&lt;/code&gt; header and what each field holds. &lt;a href="https://github.com/openzipkin/b3-propagation" rel="noopener noreferrer"&gt;B3&lt;/a&gt; is older and still appears in questions, so learn it even if you've never configured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Collector is a full pipeline
&lt;/h2&gt;

&lt;p&gt;26% of the exam, and most engineers treat the Collector as something you drop in and forget. Receivers accept data, processors transform it, exporters send it, all wired as named pipelines under &lt;code&gt;service.pipelines&lt;/code&gt;. One config can hold separate pipelines for traces, metrics and logs.&lt;/p&gt;

&lt;p&gt;Processor order is a real topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;memory_limiter&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;memory_limiter&lt;/code&gt; goes first so the Collector can shed load before it gets OOM-killed. &lt;code&gt;batch&lt;/code&gt; goes last before export to cut outbound requests. Flip them and the config can fall over under load. I got those questions right only because I had read the &lt;a href="https://opentelemetry.io/docs/collector/configuration/" rel="noopener noreferrer"&gt;Collector configuration docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Know &lt;a href="https://opentelemetry.io/docs/collector/deployment/agent/" rel="noopener noreferrer"&gt;agent mode&lt;/a&gt; (one Collector per node, DaemonSet or sidecar) and &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/" rel="noopener noreferrer"&gt;gateway mode&lt;/a&gt; (a central cluster aggregating from agents or SDKs). Tail sampling in gateway mode needs careful routing, because related spans must land on the same instance.&lt;/p&gt;

&lt;p&gt;Debugging (10%) is small. Learn the common patterns and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked and what didn't
&lt;/h2&gt;

&lt;p&gt;I started with &lt;a href="https://training.linuxfoundation.org/training/getting-started-with-opentelemetry-lfs148/" rel="noopener noreferrer"&gt;LFS148&lt;/a&gt;, the free Linux Foundation OpenTelemetry course. That's where API versus SDK finally clicked. Then the &lt;a href="https://opentelemetry.io/docs/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; with a notepad, writing down what each component owns: about six hours over three sessions.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://opentelemetry.io/docs/demo/" rel="noopener noreferrer"&gt;OTel Demo app&lt;/a&gt; is underrated for this. Running it locally and reading its SDK configs taught me more about the Collector than any tutorial.&lt;/p&gt;

&lt;p&gt;Two things wasted my time. Language-specific SDK examples cost me two hours on an exam with no syntax in it. Third-party Collector scaling articles were the other, since some referenced patterns that changed between versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where my 83 came from
&lt;/h2&gt;

&lt;p&gt;Mostly SDK pipeline questions, where I spent the most time, plus Collector processor ordering. If you want to check where you stand before booking, the &lt;a href="https://yetmike.com/practice/otca" rel="noopener noreferrer"&gt;OTCA practice test&lt;/a&gt; I built during prep covers every topic above. Tests for other CNCF exams are at &lt;a href="https://yetmike.com/tests" rel="noopener noreferrer"&gt;yetmike.com/tests&lt;/a&gt;, and the rest of my study links are in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I still feel shaky on Collector scaling. I passed, but I'm not convinced the third-party material I read on it was current.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/otca-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>kubernetes</category>
      <category>certification</category>
    </item>
    <item>
      <title>Prometheus Certified Associate: How PromQL Decides Whether You Pass</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Fri, 30 Oct 2026 21:46:00 +0000</pubDate>
      <link>https://dev.to/yetmike/prometheus-certified-associate-how-promql-decides-whether-you-pass-130a</link>
      <guid>https://dev.to/yetmike/prometheus-certified-associate-how-promql-decides-whether-you-pass-130a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with more on each domain, on &lt;a href="https://yetmike.com/blog/pca-exam" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PromQL is 28% of the Prometheus Certified Associate exam, and it's the domain that fails people. I've seen engineers who run &lt;a href="https://prometheus.io/docs/introduction/overview/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; in production every day pick the wrong function under exam pressure, and I was almost one of them.&lt;/p&gt;

&lt;p&gt;I passed with 82 out of 100 on the first attempt. Passing is 75, and most of the points I nearly lost were on things I thought I already knew from work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format and domains
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://training.linuxfoundation.org/certification/prometheus-certified-associate/" rel="noopener noreferrer"&gt;Linux Foundation curriculum&lt;/a&gt;: around 60 multiple choice questions, 90 minutes, 75% to pass, online proctored, no hands-on part.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PromQL&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus Fundamentals&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability Concepts&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alerting and Dashboarding&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instrumentation and Exporters&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After PromQL the weights are flat. Fundamentals plus Observability is 38%, Instrumentation plus Alerting another 34%. There's no throwaway domain.&lt;/p&gt;

&lt;p&gt;Underneath it all, the PCA checks whether you understand the data model: Prometheus is a &lt;a href="https://en.wikipedia.org/wiki/Time_series_database" rel="noopener noreferrer"&gt;time series database&lt;/a&gt; where every metric is a set of labeled samples. If that feels abstract, PromQL will feel random.&lt;/p&gt;

&lt;h2&gt;
  
  
  PromQL
&lt;/h2&gt;

&lt;p&gt;The exam hands you an expression and asks for the output, or describes a scenario and asks which expression matches. You only get good at that by writing queries.&lt;/p&gt;

&lt;p&gt;The classic trap is rate() vs irate(), placed side by side and written to look interchangeable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rate&lt;span class="o"&gt;(&lt;/span&gt;http_requests_total[5m]&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# per-second average over the full 5m window&lt;/span&gt;
irate&lt;span class="o"&gt;(&lt;/span&gt;http_requests_total[5m]&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;# only the last 2 samples&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rate() smooths short spikes, which is what you want for alerts and dashboards. irate() reacts instantly and is jumpy. A question describes a brief spike and asks which function reflects it: irate() shows the jump, rate() averages it away. I used rate() at work for years and had never checked how irate() differs until I studied.&lt;/p&gt;

&lt;p&gt;Label matchers are everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{job="node_exporter"}&lt;/code&gt; is an exact match&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{job=~"node.*"}&lt;/code&gt; is a &lt;a href="https://en.wikipedia.org/wiki/Regular_expression" rel="noopener noreferrer"&gt;regex&lt;/a&gt; match&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{job!~".*test.*"}&lt;/code&gt; excludes anything containing "test"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;sum(http_requests_total) by (job)&lt;/code&gt; keeps only the job label. &lt;code&gt;sum(http_requests_total) without (instance)&lt;/code&gt; drops instance and keeps the rest. The questions hinge on which labels survive, so sketch the label set before and after on scratch paper.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/" rel="noopener noreferrer"&gt;histogram_quantile()&lt;/a&gt; needs a &lt;code&gt;_bucket&lt;/code&gt; series with an &lt;code&gt;le&lt;/code&gt; label. I think it's the one advanced function worth running once against real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fundamentals
&lt;/h2&gt;

&lt;p&gt;Prometheus pulls. It scrapes targets on an interval, and push vs pull came up more than once. The &lt;a href="https://prometheus.io/docs/practices/pushing/" rel="noopener noreferrer"&gt;Pushgateway&lt;/a&gt; is the narrow exception for short-lived batch jobs that finish before a scrape. Using it as a general push endpoint for long-running services leaves stale metrics behind, and the exam tests that distinction.&lt;/p&gt;

&lt;p&gt;Kubernetes service discovery is on there too. Remember relabeling: it turns the raw discovery labels into the final label set on your metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric types
&lt;/h2&gt;

&lt;p&gt;The four &lt;a href="https://prometheus.io/docs/concepts/metric_types/" rel="noopener noreferrer"&gt;metric types&lt;/a&gt; were the most common non-PromQL topic I saw:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Counter&lt;/strong&gt; only goes up and resets on restart. You wrap it in rate(), which is why rate() exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gauge&lt;/strong&gt; goes up and down. Read it directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Histogram&lt;/strong&gt; exposes &lt;code&gt;_bucket&lt;/code&gt;, &lt;code&gt;_sum&lt;/code&gt; and &lt;code&gt;_count&lt;/code&gt;, and the &lt;a href="https://en.wikipedia.org/wiki/Quantile" rel="noopener noreferrer"&gt;quantile&lt;/a&gt; is calculated on the server at query time, so it aggregates across instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary&lt;/strong&gt; calculates quantiles on the client, and those can't be aggregated afterward. Expect a question on that trade-off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Naming is tested directly: &lt;code&gt;_total&lt;/code&gt; for counters, base units like &lt;code&gt;_seconds&lt;/code&gt; and &lt;code&gt;_bytes&lt;/code&gt;. Read the &lt;a href="https://prometheus.io/docs/practices/naming/" rel="noopener noreferrer"&gt;naming conventions&lt;/a&gt; once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alerting
&lt;/h2&gt;

&lt;p&gt;Three things earned me points. An alert with &lt;code&gt;for: 10m&lt;/code&gt; only fires if the condition holds for 10 continuous minutes. &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/" rel="noopener noreferrer"&gt;Recording rules&lt;/a&gt; pre-compute an expensive query into a new series. And who does what: Prometheus evaluates rules, &lt;a href="https://prometheus.io/docs/alerting/latest/alertmanager/" rel="noopener noreferrer"&gt;Alertmanager&lt;/a&gt; groups, silences and routes, Grafana draws dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I prepared
&lt;/h2&gt;

&lt;p&gt;Three days of nothing but PromQL. Local Prometheus, &lt;a href="https://prometheus.io/docs/guides/node-exporter/" rel="noopener noreferrer"&gt;node_exporter&lt;/a&gt; scraping my own laptop, real queries against real data. I kept going until I could predict the output before hitting enter. Nothing else came close.&lt;/p&gt;

&lt;p&gt;Then the &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/" rel="noopener noreferrer"&gt;configuration docs&lt;/a&gt; and the &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/" rel="noopener noreferrer"&gt;alerting rules page&lt;/a&gt;, since the exam uses their exact field names. Then metric types and naming until they were automatic. For drilling functions away from a terminal, I made free &lt;a href="https://yetmike.com/tools/promql-flashcards" rel="noopener noreferrer"&gt;PromQL flashcards&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Blog-post prep guides didn't help me much; most stay shallow and leave you stranded on PromQL. Paid courses exist, but I'd treat any of them as a supplement to writing queries yourself.&lt;/p&gt;

&lt;p&gt;The order I'd use again:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prometheus and node_exporter locally. PromQL every day.&lt;/li&gt;
&lt;li&gt;Metric types and naming.&lt;/li&gt;
&lt;li&gt;Pull model, Pushgateway, service discovery, relabeling.&lt;/li&gt;
&lt;li&gt;Alerting rules, recording rules, Alertmanager vs Grafana.&lt;/li&gt;
&lt;li&gt;Practice questions to find what's still weak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For step 5, I built a free &lt;a href="https://yetmike.com/practice/pca" rel="noopener noreferrer"&gt;PCA practice test&lt;/a&gt;. It's PromQL-heavy on purpose and covers metric types and alerting too, so you see your weak domains before exam day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking in label sets
&lt;/h2&gt;

&lt;p&gt;At some point I stopped thinking "query the database" and started thinking "transform a set of labeled time series." Now I ask what an aggregation does to the labels before I ask about the numbers, and the questions stopped feeling random.&lt;/p&gt;

&lt;p&gt;The PCA was part of my Golden Kubestronaut path, 15 of 15 CNCF certifications. The study material I collected is in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm still not sure how much of my 82 came from production experience and how much from those three days of queries. My guess is the three days mattered more than I'd like to admit.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/pca-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>certification</category>
    </item>
    <item>
      <title>Kyverno Certified Associate (KCA): where I lost points and how I'd prepare</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sun, 18 Oct 2026 12:33:11 +0000</pubDate>
      <link>https://dev.to/yetmike/kyverno-certified-associate-kca-where-i-lost-points-and-how-id-prepare-4a2n</link>
      <guid>https://dev.to/yetmike/kyverno-certified-associate-kca-where-i-lost-points-and-how-id-prepare-4a2n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with the full study order and every trap I hit, &lt;a href="https://yetmike.com/blog/kca-exam" rel="noopener noreferrer"&gt;on my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I passed the &lt;a href="https://training.linuxfoundation.org/certification/kyverno-certified-associate-kca/" rel="noopener noreferrer"&gt;Kyverno Certified Associate&lt;/a&gt; on the first attempt with 85 out of 100. The policy YAML was the easy part. Every point I dropped came from not knowing exactly what runs where, and what blocks what.&lt;/p&gt;

&lt;p&gt;It was one of the 15 CNCF certs I did on the &lt;a href="https://www.cncf.io/training/kubestronaut/" rel="noopener noreferrer"&gt;Golden Kubestronaut&lt;/a&gt; track.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Rego, no DSL
&lt;/h2&gt;

&lt;p&gt;With OPA Gatekeeper you learn Rego first, a separate query language, before you can write anything useful. &lt;a href="https://kyverno.io/docs/introduction/" rel="noopener noreferrer"&gt;Kyverno&lt;/a&gt; skips that step. A policy is a Kubernetes resource with a &lt;code&gt;spec&lt;/code&gt; block, applied with &lt;code&gt;kubectl apply&lt;/code&gt;. If you can write a Deployment, you can read a Kyverno policy on day one.&lt;/p&gt;

&lt;p&gt;So I think the KCA is a YAML exam with an architecture question hiding inside it, and prep is faster than most material suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format and domains
&lt;/h2&gt;

&lt;p&gt;60 questions, 90 minutes, multiple choice, 75 to pass.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writing Policies&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fundamentals of Kyverno&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installation, Configuration &amp;amp; Upgrades&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kyverno CLI&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applying Policies&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy Management&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Policy Management at 10% looks skippable. I gave it about an hour and still lost a question there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy types (and the bit I'm unsure about)
&lt;/h2&gt;

&lt;p&gt;Validate is the admission gate and holds most of the 32%. &lt;strong&gt;Audit&lt;/strong&gt; reports a violation, &lt;strong&gt;Enforce&lt;/strong&gt; blocks the request, and that split comes up again and again. Mutate changes resources on the way in with a strategic merge patch or JSON patch. Generate creates a resource in response to an event, like a default &lt;a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/" rel="noopener noreferrer"&gt;NetworkPolicy&lt;/a&gt; when a namespace appears. Cleanup deletes resources. Image verification checks signatures with &lt;a href="https://docs.sigstore.dev/cosign/" rel="noopener noreferrer"&gt;Cosign&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Kyverno is mid-transition. Most tutorials still use ClusterPolicy, where every type lives as a rule inside one policy. The official docs now show the &lt;a href="https://kubernetes.io/docs/reference/using-api/cel/" rel="noopener noreferrer"&gt;CEL&lt;/a&gt; based per-type resources like &lt;a href="https://kyverno.io/docs/policy-types/validating-policy/" rel="noopener noreferrer"&gt;ValidatingPolicy&lt;/a&gt;, and ClusterPolicy is marked for deprecation. I studied the new ones, but I can't promise every question I saw used the new syntax. Know both shapes well enough to recognise them.&lt;/p&gt;

&lt;p&gt;Mechanics worth drilling: &lt;code&gt;match&lt;/code&gt;/&lt;code&gt;exclude&lt;/code&gt; with &lt;code&gt;any&lt;/code&gt; (OR) and &lt;code&gt;all&lt;/code&gt; (AND), the anchors &lt;code&gt;()&lt;/code&gt;, &lt;code&gt;^()&lt;/code&gt;, &lt;code&gt;=()&lt;/code&gt; and &lt;code&gt;X()&lt;/code&gt;, ClusterPolicy vs namespaced Policy, and autogen (a Pod rule gets Deployment, DaemonSet, Job and CronJob rules generated for you).&lt;/p&gt;

&lt;h2&gt;
  
  
  Admission controller vs policy controller
&lt;/h2&gt;

&lt;p&gt;The exam keeps coming back to this.&lt;/p&gt;

&lt;p&gt;The admission controller sits on the request path as a &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/" rel="noopener noreferrer"&gt;dynamic admission webhook&lt;/a&gt;. Validate and mutate run there. Because it's inline, a webhook failure becomes a cluster problem, which is why &lt;code&gt;failurePolicy&lt;/code&gt; exists and gets asked about.&lt;/p&gt;

&lt;p&gt;The policy controller runs off the request path: background scans, generate, cleanup. Background scans produce &lt;a href="https://kyverno.io/docs/policy-reports/" rel="noopener noreferrer"&gt;PolicyReport and ClusterPolicyReport&lt;/a&gt;. If it goes down, workloads keep running and nothing gets blocked.&lt;/p&gt;

&lt;p&gt;Expect "which component handles this" and "what breaks when this is down". I'd derive both from the architecture in &lt;a href="https://kyverno.io/docs/introduction/how-kyverno-works/" rel="noopener noreferrer"&gt;how Kyverno works&lt;/a&gt; rather than memorize a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI is the cheapest 12%
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kyverno apply policy.yaml &lt;span class="nt"&gt;--resource&lt;/span&gt; resource.yaml
kyverno &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://kyverno.io/docs/kyverno-cli/usage/apply/" rel="noopener noreferrer"&gt;&lt;code&gt;kyverno apply&lt;/code&gt;&lt;/a&gt; tells you whether a resource passes or fails. &lt;a href="https://kyverno.io/docs/kyverno-cli/usage/test/" rel="noopener noreferrer"&gt;&lt;code&gt;kyverno test&lt;/code&gt;&lt;/a&gt; runs a manifest of expected outcomes across several resources. Keep both running the whole time you study.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I lost points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Audit vs Enforce. Both look right when the question asks which one blocks admission. Enforce blocks.&lt;/li&gt;
&lt;li&gt;Background scan vs admission time. The wrong option is always the other one.&lt;/li&gt;
&lt;li&gt;Old vs new syntax. Stale ClusterPolicy patterns look correct on a multiple choice screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I'd prepare
&lt;/h2&gt;

&lt;p&gt;Reading docs gives you recognition, and a timed exam with four plausible answers punishes that. What worked for me was writing a policy, running a resource through it, breaking it on purpose, fixing it. About 20 rounds with &lt;code&gt;kyverno apply&lt;/code&gt; in a loop. After that I didn't need to memorize Audit vs Enforce because I'd watched both happen. Passive video courses didn't work for me.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with the &lt;a href="https://kyverno.io/docs/introduction/" rel="noopener noreferrer"&gt;Kyverno introduction&lt;/a&gt;, then the per-type pages. The docs are good, which I don't say often.&lt;/li&gt;
&lt;li&gt;Spend most of your time on Writing Policies with the apply loop. About a week if you build real policies.&lt;/li&gt;
&lt;li&gt;Learn the controller split until "what breaks if this goes down" needs no thought.&lt;/li&gt;
&lt;li&gt;Give Policy Management its hour. Maybe more than I did.&lt;/li&gt;
&lt;li&gt;Run a practice test before booking. I put every scenario I built during prep into a free &lt;a href="https://yetmike.com/practice/kca" rel="noopener noreferrer"&gt;KCA practice test&lt;/a&gt; that shows which domain is still weak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest of my study links live in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two questions for every answer
&lt;/h2&gt;

&lt;p&gt;Does this run on the request path or off it? Does it block, or only report? Validate and mutate are on the path; generate, cleanup and background scans are off it. Once I had that, I worked answers out from where the thing sits.&lt;/p&gt;

&lt;p&gt;85 left me 10 points of headroom, and I still got caught by a Policy Management question in the domain I just told you not to skip.&lt;/p&gt;

&lt;p&gt;I don't know how long the classic ClusterPolicy questions stay in the pool. If you sit it soon, I'd like to hear which syntax most of your questions used.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/kca-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kyverno</category>
      <category>devops</category>
      <category>certification</category>
    </item>
    <item>
      <title>Certified Backstage Associate: I Passed by Five Points</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:57:53 +0000</pubDate>
      <link>https://dev.to/yetmike/certified-backstage-associate-i-passed-by-five-points-4hmm</link>
      <guid>https://dev.to/yetmike/certified-backstage-associate-i-passed-by-five-points-4hmm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with every config detail I studied and the full prep timeline, on &lt;a href="https://yetmike.com/blog/cba-exam" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I scored 80 on the &lt;a href="https://training.linuxfoundation.org/certification/certified-backstage-associate-cba/" rel="noopener noreferrer"&gt;Certified Backstage Associate&lt;/a&gt; exam. Passing is 75.&lt;/p&gt;

&lt;p&gt;No single domain sank me. I gave two or three soft answers in each of the four domains, and they stacked up until I was a couple of bad guesses away from a retake. CBA takes a point here and a point there, wherever you were pretty sure instead of certain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CNCF exam habits don't help
&lt;/h2&gt;

&lt;p&gt;CBA was one of my last exams on the Golden Kubestronaut track. Other CNCF certs test you on operating something. CBA is 60 multiple choice questions in 90 minutes about how a framework works. No kubectl, no cluster to fix.&lt;/p&gt;

&lt;p&gt;The wrong answers are written to catch people who learned Backstage by osmosis. I felt confident going in.&lt;/p&gt;

&lt;p&gt;What made it click: Backstage is a &lt;a href="https://en.wikipedia.org/wiki/TypeScript" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; framework you build on and own. Once I stopped thinking of it as a wiki with plugins and treated it as a platform for building platforms, the plugin and architecture questions started to make sense.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customizing Backstage&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backstage Development Workflow&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backstage Infrastructure&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backstage Catalog&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Customizing Backstage (32%)
&lt;/h2&gt;

&lt;p&gt;Mostly: what's a frontend plugin and what's a backend plugin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://backstage.io/docs/plugins/create-a-plugin" rel="noopener noreferrer"&gt;Frontend plugins&lt;/a&gt; are React components. They run in the browser, register routes, show up in the sidebar, and talk to backend plugins over HTTP. &lt;a href="https://backstage.io/docs/plugins/backend-plugin" rel="noopener noreferrer"&gt;Backend plugins&lt;/a&gt; are server-side, independent services with their own database connections, exposing REST or GraphQL APIs. Expect questions on where code runs, plugin registration and what &lt;code&gt;App.tsx&lt;/code&gt; does (it wires plugins together).&lt;/p&gt;

&lt;p&gt;Smaller items: &lt;a href="https://mui.com/material-ui/" rel="noopener noreferrer"&gt;Material UI&lt;/a&gt; is the UI library (not Tailwind, not shadcn), and &lt;a href="https://backstage.io/docs/backend-system/architecture/extension-points" rel="noopener noreferrer"&gt;extension points&lt;/a&gt; let modules hook into existing plugins without forking them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Workflow (24%)
&lt;/h2&gt;

&lt;p&gt;Three commands matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @backstage/create-app@latest
yarn dev
yarn build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn build-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first scaffolds a &lt;a href="https://en.wikipedia.org/wiki/Monorepo" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt; with the frontend app and backend together. &lt;code&gt;yarn dev&lt;/code&gt; runs both in watch mode. &lt;code&gt;yarn tsc&lt;/code&gt; is a separate step from building the Docker image, and the exam asks where each step fits, including where &lt;code&gt;yarn install&lt;/code&gt; sits relative to &lt;code&gt;yarn tsc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure (22%)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; is for production, SQLite for development only. The exam will offer answers that present SQLite as production-ready.&lt;/p&gt;

&lt;p&gt;The backend uses &lt;a href="https://knexjs.org/" rel="noopener noreferrer"&gt;Knex&lt;/a&gt;, and each plugin gets its own logical database. Cache backends (memory, Redis, Memcached) are in scope too.&lt;/p&gt;

&lt;p&gt;The rest is mostly &lt;a href="https://backstage.io/docs/conf/writing" rel="noopener noreferrer"&gt;app-config.yaml&lt;/a&gt;, which is layered. &lt;code&gt;app-config.yaml&lt;/code&gt; is the base, &lt;code&gt;app-config.local.yaml&lt;/code&gt; and &lt;code&gt;app-config.production.yaml&lt;/code&gt; sit on top, and later files override earlier ones. Secrets come from environment variables via &lt;code&gt;${...}&lt;/code&gt; substitution. Know the &lt;code&gt;integrations&lt;/code&gt;, &lt;code&gt;backend.database&lt;/code&gt; and &lt;code&gt;catalog.locations&lt;/code&gt; blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catalog (22%)
&lt;/h2&gt;

&lt;p&gt;Small weight, subtle distinctions. The &lt;a href="https://backstage.io/docs/features/software-catalog/descriptor-format" rel="noopener noreferrer"&gt;entity descriptor format&lt;/a&gt; is tested directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backstage.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Component&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-service&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;backstage.io/techdocs-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dir:.&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service&lt;/span&gt;
  &lt;span class="na"&gt;lifecycle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;team-a&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Know every kind: Component, API, System, Domain, Resource, Group, User, Location. Confusing Component with API is the most common way to drop catalog points. &lt;a href="https://backstage.io/docs/features/software-catalog/well-known-annotations" rel="noopener noreferrer"&gt;Well-known annotations&lt;/a&gt; carry meaning and point at external systems (&lt;code&gt;backstage.io/techdocs-ref&lt;/code&gt;, &lt;code&gt;github.com/project-slug&lt;/code&gt;), while labels are plain key-value pairs for filtering. Entities arrive through a static &lt;code&gt;catalog.locations&lt;/code&gt; entry or through discovery providers.&lt;/p&gt;

&lt;p&gt;I underestimated ingestion troubleshooting most. You register an entity and it doesn't show up: invalid YAML, wrong &lt;code&gt;apiVersion&lt;/code&gt;, missing required fields, a processing error in catalog-backend. All in scope, and "I think I know" won't get you through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I prepared, and what I'd change
&lt;/h2&gt;

&lt;p&gt;About 6 hours on the official docs first, starting with the &lt;a href="https://backstage.io/docs/overview/architecture-overview" rel="noopener noreferrer"&gt;architecture overview&lt;/a&gt; and then the catalog and plugin pages linked above. Then LFS142 from the Linux Foundation, which is free and covers the material at the right depth. I did it in one sitting, maybe 3 hours. I think it beats most third-party courses because people who work on Backstage wrote it.&lt;/p&gt;

&lt;p&gt;My Kubernetes knowledge didn't carry over; I caught myself applying k8s mental models to catalog questions.&lt;/p&gt;

&lt;p&gt;The bigger mistake was quieter. I read each domain until it felt familiar, then moved on. On exam day "I've seen this" wasn't enough, and that gap is where my soft answers came from. Next time I'd drill each domain until I could answer from memory.&lt;/p&gt;

&lt;p&gt;That's why I built a free &lt;a href="https://yetmike.com/practice/cba" rel="noopener noreferrer"&gt;CBA practice test&lt;/a&gt;. It's catalog-heavy on purpose and shows you the difference between "familiar" and "certain" before the exam does. The other &lt;a href="https://yetmike.com/tests" rel="noopener noreferrer"&gt;CNCF practice tests&lt;/a&gt; use the same approach, and the rest of my resources live in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If I sat it again, I'd bank the easy points fast (entity kinds and spec fields, the three commands, frontend vs backend scope) and spend the remaining prep on ingestion troubleshooting and annotations vs labels.&lt;/p&gt;

&lt;p&gt;I still can't settle the dev workflow question. Backstage ships so often that I'm not certain what I studied matches what you'll get. If something in that domain surprised you recently, I'd like to hear about it.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/cba-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>backstage</category>
      <category>kubernetes</category>
      <category>certification</category>
      <category>devops</category>
    </item>
    <item>
      <title>Three cargo errors that don't happen any more</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Tue, 13 Oct 2026 23:29:07 +0000</pubDate>
      <link>https://dev.to/yetmike/three-cargo-errors-that-dont-happen-any-more-edo</link>
      <guid>https://dev.to/yetmike/three-cargo-errors-that-dont-happen-any-more-edo</guid>
      <description>&lt;p&gt;Three of the cargo errors I sat down to document don't happen any more. I only found out because the build refused to publish the pages.&lt;/p&gt;

&lt;p&gt;The setup: I'm generating one page per diagnostic for a Rust learning site, and every page has to be confirmed by a real toolchain before it ships. For the cargo corpus that means an entry is a whole project, not a snippet. A build script writes the file map into a temp dir, runs actual &lt;code&gt;cargo&lt;/code&gt; in it, and compares what came back against what the page claims. If the two disagree, the entry doesn't exist.&lt;/p&gt;

&lt;p&gt;31 cargo entries survived. Here are the three that didn't, on cargo 1.96.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  A manifest with no version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"probe"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote this expecting a parse failure naming the missing &lt;code&gt;package.version&lt;/code&gt;. What I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Compiling probe v0.0.0 (/tmp/probe)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.25s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It builds. &lt;code&gt;version&lt;/code&gt; is optional now and defaults to &lt;code&gt;0.0.0&lt;/code&gt;, which cargo prints right there in the compile line if you're reading it. The error still fires for a missing &lt;code&gt;name&lt;/code&gt;, so the neighbouring entry in my corpus is fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: failed to parse manifest at `/home/you/project/Cargo.toml`

Caused by:
  missing field `package.name`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name and version used to fail the same way. They don't now, and I'd have written that they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  A misspelled key in [profile.release]
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[profile.release]&lt;/span&gt;
&lt;span class="py"&gt;opt-levle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warning: unused manifest key: profile.release.opt-levle
   Compiling probe v0.1.0 (/tmp/probe)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A warning, and a build that succeeds with the default &lt;code&gt;opt-level&lt;/code&gt;. This is the one I'm least happy about losing, because the failure mode is worse than an error would be. Your release build is not optimised the way you asked and the only evidence is one line that scrolls past. An error would at least stop you.&lt;/p&gt;

&lt;p&gt;The typo case that does still error is a required key, and the message is worth looking at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;naem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"probe"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: failed to parse manifest at `/home/you/project/Cargo.toml`

Caused by:
  missing field `package.name`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cargo tells you what's absent, not what you misspelled. &lt;code&gt;naem&lt;/code&gt; is sitting right there on line 2 and never gets mentioned.&lt;/p&gt;

&lt;h2&gt;
  
  
  A virtual manifest with one member
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[workspace]&lt;/span&gt;
&lt;span class="py"&gt;resolver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;
&lt;span class="py"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cargo run&lt;/code&gt; in that root, I was told, refuses because the root defines no package. It doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Compiling app v0.1.0 (/tmp/probe/app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/app`
hello from app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One member with a binary means one answer, so cargo picks it. Add a second member and the old error comes back, which is the version I kept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: app, tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the entry isn't wrong so much as it needed a workspace big enough to be ambiguous. That's a different page from the one I'd planned to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'd have got all three wrong
&lt;/h2&gt;

&lt;p&gt;I didn't reason my way to these. I lifted them from what I'd read, and what I'd read was written when they were true. I haven't gone back to count how many of those write-ups are still near the top of a search for the error text, so I'm not going to give you a number, but the phrasing is still out there in blog posts and answers and nothing about a stale answer looks stale.&lt;/p&gt;

&lt;p&gt;Documentation of an error is a claim about behaviour at a point in time, and cargo keeps relaxing things. Each relaxation turns a correct old page into a confident wrong one. Nobody edits the old page, because nothing tells them to.&lt;/p&gt;

&lt;p&gt;The gate is the whole value here. It's about 200 lines that shell out to cargo and diff two strings, and its only real job is telling me to delete something. Three pages I'd have sworn were correct, deleted before anyone read them, by a script with no opinions.&lt;/p&gt;

&lt;p&gt;What I'm less sure about: I now have three documented non-errors and nowhere to put them. The page that says "this used to fail and doesn't" is probably more useful than the page that says "this fails", because it's the one that contradicts what you already read. But it isn't a diagnostic, and the site is organised entirely around diagnostics. I haven't worked out where it goes: &lt;a href="https://codecrab.app/?r=devto-cargo-stale" rel="noopener noreferrer"&gt;https://codecrab.app/?r=devto-cargo-stale&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cargo</category>
      <category>testing</category>
      <category>writing</category>
    </item>
    <item>
      <title>CAPA Exam Guide: Argo CD Is Only a Third of It. I Scored 93</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Tue, 13 Oct 2026 23:20:24 +0000</pubDate>
      <link>https://dev.to/yetmike/capa-exam-guide-argo-cd-is-only-a-third-of-it-i-scored-93-4lc1</link>
      <guid>https://dev.to/yetmike/capa-exam-guide-argo-cd-is-only-a-third-of-it-i-scored-93-4lc1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with more on each Argo project and my prep timeline, on &lt;a href="https://yetmike.com/blog/capa-exam" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Daily Argo CD use covers about a third of the CAPA exam. You blast through the CD questions, and then Argo Events shows up.&lt;/p&gt;

&lt;p&gt;I passed the &lt;a href="https://training.linuxfoundation.org/certification/certified-argo-project-associate-capa/" rel="noopener noreferrer"&gt;Certified Argo Project Associate&lt;/a&gt; on the first attempt with 93 out of 100 (passing is 75). I don't run Rollouts or Events at work, so that score came from noticing early that I was preparing for the wrong exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format and weights
&lt;/h2&gt;

&lt;p&gt;60 questions, 90 minutes, all multiple choice. No terminal. If you've done CKA or CKAD, muscle memory won't help here.&lt;/p&gt;

&lt;p&gt;Argo is four projects, each with its own object model, and you can't reason from one to another. The weights when I took it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Argo Workflows&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argo CD&lt;/td&gt;
&lt;td&gt;34%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argo Rollouts&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argo Events&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Workflows is the biggest domain and the one most people underprepare. Check the &lt;a href="https://github.com/cncf/curriculum/tree/master/capa" rel="noopener noreferrer"&gt;official CNCF curriculum&lt;/a&gt; before you plan, in case the weights have moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflows (36%)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argoproj.github.io/argo-workflows/" rel="noopener noreferrer"&gt;Argo Workflows&lt;/a&gt; runs &lt;a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" rel="noopener noreferrer"&gt;DAGs&lt;/a&gt; in pods. Know &lt;code&gt;dag&lt;/code&gt; templates versus &lt;code&gt;steps&lt;/code&gt; templates cold. In a DAG template you declare dependencies explicitly: task B waits on task A because you wrote &lt;code&gt;dependencies: [build]&lt;/code&gt;, not because it's listed second. Steps run top to bottom with no dependency graph. The exam gives you a manifest and asks which style it is.&lt;/p&gt;

&lt;p&gt;Parameters pass values between tasks, artifacts pass files. Run one real workflow and most of this domain comes for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argo CD (34%)
&lt;/h2&gt;

&lt;p&gt;The one people think they already know.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;prune: true&lt;/code&gt; deletes cluster resources that were removed from Git. &lt;code&gt;selfHeal: true&lt;/code&gt; reverts manual changes made directly in the cluster. The exam asks about each separately, and if you've only ever turned on &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/" rel="noopener noreferrer"&gt;automated sync&lt;/a&gt; with both set to true, you've never had to think about which does what.&lt;/p&gt;

&lt;p&gt;Sync status and health status are separate axes. Synced vs OutOfSync says whether the cluster matches Git. Healthy, Progressing, Degraded or Missing says whether the thing works. An app can be Synced and Degraded at once, and that combination gets tested.&lt;/p&gt;

&lt;p&gt;Also recognize sync waves and hooks (PreSync, Sync, PostSync; the lowest wave applies first), &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/" rel="noopener noreferrer"&gt;ApplicationSet&lt;/a&gt; (one template plus generators makes many Applications), app-of-apps, and AppProject as the RBAC boundary. Know what each is for. You don't need to write them from memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollouts (18%)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argoproj.github.io/argo-rollouts/" rel="noopener noreferrer"&gt;Argo Rollouts&lt;/a&gt; replaces a Deployment when you need canary or blue/green.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argoproj.github.io/argo-rollouts/features/canary/" rel="noopener noreferrer"&gt;Canary&lt;/a&gt;&lt;/strong&gt; splits live traffic by percentage. Both versions serve users at the same time, and an AnalysisRun gates whether traffic keeps increasing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argoproj.github.io/argo-rollouts/features/bluegreen/" rel="noopener noreferrer"&gt;Blue/green&lt;/a&gt;&lt;/strong&gt; stands up a full second environment and swaps all traffic at once via &lt;code&gt;activeService&lt;/code&gt; and &lt;code&gt;previewService&lt;/code&gt;. Rollback is switching the Service back, because the old version is still running.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrong answer usually blurs "both versions live" with "instant full cutover".&lt;/p&gt;

&lt;h2&gt;
  
  
  Events (12%)
&lt;/h2&gt;

&lt;p&gt;Only 12%, but it's where I burned the most prep time, and most of us have never touched anything like it at work.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://argoproj.github.io/argo-events/" rel="noopener noreferrer"&gt;Argo Events&lt;/a&gt;, the EventSource listens to the outside world: a webhook, a file landing in S3, a Kafka message, a schedule. The Sensor subscribes to it and decides what to do. The Trigger is the action, like submitting a Workflow, creating a resource or calling an endpoint. Underneath, the EventBus (backed by NATS) carries events between EventSource and Sensor.&lt;/p&gt;

&lt;p&gt;EventSource listens, Sensor decides, Trigger acts. I said that chain out loud probably 20 times before it stuck, because under pressure the instinct is to assume the listener is also the decider.&lt;/p&gt;

&lt;p&gt;Even with the model down, Sensor dependencies and trigger conditions took me longest per question.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I prepared
&lt;/h2&gt;

&lt;p&gt;Two weeks: one of study, one of practice tests.&lt;/p&gt;

&lt;p&gt;For each project, before opening docs, I wrote down the problem it solves and its three main objects. That exposes the gaps that reading lets you nod past.&lt;/p&gt;

&lt;p&gt;I also deployed all four projects on one cluster. Not production grade, just enough to watch a workflow run and a rollout progress.&lt;/p&gt;

&lt;p&gt;What didn't work: going deep on Argo CD because I use it daily and skimming Events. About five days in I caught it and moved my time over.&lt;/p&gt;

&lt;p&gt;The easy points are the &lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps principles&lt;/a&gt;, the Workflows template types (container, script, dag, steps), and knowing which Argo project solves which problem.&lt;/p&gt;

&lt;p&gt;Beyond the official docs, the Linux Foundation has &lt;a href="https://training.linuxfoundation.org/certification/lfs256-capa/" rel="noopener noreferrer"&gt;LFS256, DevOps and Workflow Management with Argo&lt;/a&gt;, and everything I used for all 15 CNCF certs is in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice test
&lt;/h2&gt;

&lt;p&gt;I built a free &lt;a href="https://yetmike.com/practice/capa" rel="noopener noreferrer"&gt;CAPA practice test&lt;/a&gt; around the traps that cost the most points (EventSource/Sensor, selfHeal vs prune, canary vs blue/green, DAG vs steps). Take it a week out and you'll see which domain needs your last few evenings. Other CNCF exams are at &lt;a href="https://yetmike.com/tests" rel="noopener noreferrer"&gt;yetmike.com/tests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two weeks was enough. I'm not sure I'd pass the Events section again without a refresher, though. Sensor conditions only stick if you run them, and I don't run Events outside a practice cluster.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/capa-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>argocd</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>certification</category>
    </item>
    <item>
      <title>Every rustc error has two spans, and they mean different things</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sat, 03 Oct 2026 21:13:39 +0000</pubDate>
      <link>https://dev.to/yetmike/every-rustc-error-has-two-spans-and-they-mean-different-things-34mf</link>
      <guid>https://dev.to/yetmike/every-rustc-error-has-two-spans-and-they-mean-different-things-34mf</guid>
      <description>&lt;p&gt;The line rustc points at is usually not the line you edit. The message is doing more work than most people read.&lt;/p&gt;

&lt;p&gt;Here's the smallest example, E0384:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{x}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;E0384&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt; &lt;span class="n"&gt;twice&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;immutable&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;assignment&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;^^^^^&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt; &lt;span class="n"&gt;twice&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;immutable&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;consider&lt;/span&gt; &lt;span class="n"&gt;making&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="n"&gt;mutable&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;+++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two spans. Line 3 carries the carets, because that's where the rule broke. Line 2 carries a single dash, because that's where you made the decision that broke it. The header names the failure, the dash names the cause, and the &lt;code&gt;help:&lt;/code&gt; block edits the cause, not the failure. Read in that order the error stops being a complaint and turns into a two line story: you decided here, it went wrong there.&lt;/p&gt;

&lt;p&gt;Most people stop at the header, add &lt;code&gt;mut&lt;/code&gt;, and keep going. That works for E0384 and it teaches you nothing that survives to the next error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same shape, one step harder
&lt;/h2&gt;

&lt;p&gt;E0382, borrow of moved value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"crab"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{name}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;E0382&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;moved&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"crab"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;----&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="n"&gt;occurs&lt;/span&gt; &lt;span class="n"&gt;because&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;implement&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;Copy&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;                 &lt;span class="o"&gt;----&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;moved&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{name}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;                &lt;span class="o"&gt;^^^^&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;borrowed&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;consider&lt;/span&gt; &lt;span class="n"&gt;cloning&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;performance&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;acceptable&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;                     &lt;span class="o"&gt;++++++++&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Carets at line 4. But nothing on line 4 is wrong in isolation, printing a &lt;code&gt;String&lt;/code&gt; is fine. The dashes carry the actual content: line 2 says why the type behaves this way, line 3 says where the ownership left. The fix lands on line 3, one line above the report and two below the explanation.&lt;/p&gt;

&lt;p&gt;That middle dash on line 2 is the whole ownership model in one sentence, and it's the part people skim because it's long and doesn't have carets under it.&lt;/p&gt;

&lt;h2&gt;
  
  
  And once more, with three
&lt;/h2&gt;

&lt;p&gt;E0502:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;E0502&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;because&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;borrowed&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;immutable&lt;/span&gt;
 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;                  &lt;span class="o"&gt;-------&lt;/span&gt; &lt;span class="n"&gt;immutable&lt;/span&gt; &lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="n"&gt;occurs&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;^^^^^^^^^^^^^^^&lt;/span&gt; &lt;span class="n"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="n"&gt;occurs&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{first}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt;                &lt;span class="o"&gt;-----&lt;/span&gt; &lt;span class="n"&gt;immutable&lt;/span&gt; &lt;span class="n"&gt;borrow&lt;/span&gt; &lt;span class="n"&gt;later&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the second span split in two, one before the carets and one after. That's the borrow checker showing you a span of time, not a span of text: the borrow starts at line 3, is still alive at line 5, and line 4 sits inside it. Delete the &lt;code&gt;println!&lt;/code&gt; and the error goes away, which is a strange thing to be true about code three lines up from the complaint.&lt;/p&gt;

&lt;p&gt;So the rule generalises a little past "two spans". Carets are where the compiler stopped. Dashes are the evidence, and there can be one or several. The fix goes wherever the evidence points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think this matters more than the error codes
&lt;/h2&gt;

&lt;p&gt;I've been writing one page per rustc error code and I'm about fifty in, out of roughly six hundred codes that exist. Somewhere around the twentieth it stopped feeling like fifty separate things to memorise. The codes differ, the span layout doesn't, and the layout is the part you can actually carry into an error you've never seen. A beginner who reads dashes before carets can handle E0499 the first time they meet it. A beginner who's memorised twelve codes still can't.&lt;/p&gt;

&lt;p&gt;My unresolved doubt, and it's a real one: &lt;code&gt;rustc --explain E0384&lt;/code&gt; is already built into the toolchain, offline, with no site to visit. It explains the code in general. It does not explain your diagnostic, with your line numbers and your dashes in it, which is the thing I think people need. I'm not certain that gap is big enough to justify a page per code. I've got about fifty of them here if you want to tell me either way: &lt;a href="https://codecrab.app/?r=devto-two-spans" rel="noopener noreferrer"&gt;https://codecrab.app/?r=devto-two-spans&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's the error you kept fixing at the wrong line before you noticed the dashes?&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>compilers</category>
      <category>learning</category>
    </item>
    <item>
      <title>CGOA exam prep: why 50 clusters of Argo CD got me an 82</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sat, 03 Oct 2026 20:47:34 +0000</pubDate>
      <link>https://dev.to/yetmike/cgoa-exam-prep-why-50-clusters-of-argo-cd-got-me-an-82-58o2</link>
      <guid>https://dev.to/yetmike/cgoa-exam-prep-why-50-clusters-of-argo-cd-got-me-an-82-58o2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I wrote the longer version, with the full domain breakdown and my prep timeline, &lt;a href="https://yetmike.com/blog/cgoa-exam" rel="noopener noreferrer"&gt;on my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I run &lt;a href="https://argo-cd.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt; in production on 50 Kubernetes clusters across several public clouds. Before studying for the &lt;a href="https://training.linuxfoundation.org/certification/certified-gitops-associate-cgoa/" rel="noopener noreferrer"&gt;Certified GitOps Associate&lt;/a&gt;, I took a practice test cold and scored 82. Passing is 75, so fine on paper. The questions I missed were the ones I didn't expect to miss.&lt;/p&gt;

&lt;p&gt;The CGOA does not grade how well you run Argo CD. It grades whether you can apply the &lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps&lt;/a&gt; specification word for word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format
&lt;/h2&gt;

&lt;p&gt;60 multiple-choice questions, 90 minutes, 75 to pass. No terminal, no &lt;code&gt;kubectl&lt;/code&gt;, no YAML. Argo CD and &lt;a href="https://fluxcd.io/flux/" rel="noopener noreferrer"&gt;Flux&lt;/a&gt; appear as examples, never as the thing being graded.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitOps Principles&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitOps Terminology&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitOps Patterns&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Related Practices&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tooling&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tooling is roughly one question in seven. In my experience it gets most of people's prep time anyway, because sync options and CRDs are what we touch every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  30% of the exam is four sentences
&lt;/h2&gt;

&lt;p&gt;About 18 questions come from the four &lt;a href="https://github.com/open-gitops/documents/blob/main/PRINCIPLES.md" rel="noopener noreferrer"&gt;OpenGitOps principles&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Declarative.&lt;/strong&gt; The system is described declaratively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned and Immutable.&lt;/strong&gt; Desired state is stored in a way that enforces immutability and versioning, and retains a complete version history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pulled Automatically.&lt;/strong&gt; Software agents automatically pull the desired state declarations from the source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuously Reconciled.&lt;/strong&gt; Software agents continuously observe actual system state and attempt to apply the desired state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Memorize the exact wording. The options are written so a paraphrase lands you on the wrong one. "Pulled Automatically" is the usual slip: people read it as "applied automatically", but the agent pulls and the CI pipeline does not push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminology cost me the most
&lt;/h2&gt;

&lt;p&gt;Terminology is 20%, and the spec vocabulary sits in the question stem too, so a fuzzy term means you misread the question before you see the options.&lt;/p&gt;

&lt;p&gt;Learn desired state, actual state, state drift, state store, reconciliation, feedback loop, rollback, and "continuous" (recurring, not instantaneous). The pair people mix up most: Git is the &lt;strong&gt;state store&lt;/strong&gt;, the cluster holds the &lt;strong&gt;actual state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My 82 came from here. I'd fixed plenty of state drift. I was sloppy about the exact definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull vs push, and related practices
&lt;/h2&gt;

&lt;p&gt;In the pull model an agent inside the cluster watches Git and applies changes, so CI holds no cluster credentials. In the push model a runner calls &lt;code&gt;kubectl apply&lt;/code&gt; or &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt;. That deploys fine, and it isn't GitOps.&lt;/p&gt;

&lt;p&gt;The wrong answer on a patterns question is usually a pipeline that deploys everything correctly. A couple caught me because I was thinking about implementation. Also know that a reconciler can run in-cluster or externally as long as it pulls, that the merge is the promotion, and that reverting the commit is the rollback.&lt;/p&gt;

&lt;p&gt;Related Practices (16%) is mostly review with a DevOps background: &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code" rel="noopener noreferrer"&gt;Infrastructure as Code&lt;/a&gt;, Configuration as Code, &lt;a href="https://en.wikipedia.org/wiki/DevSecOps" rel="noopener noreferrer"&gt;DevSecOps&lt;/a&gt;, CI vs CD. In GitOps, CI builds and tests, the reconciler deploys. A few CI/CD split questions made me stop, and I'm not sure whether that section has shifted since I sat it. If you're preparing in late 2026, check the current curriculum first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'd prepare
&lt;/h2&gt;

&lt;p&gt;I spent about 8 hours over a weekend. Aimed right from the start, I think 5 or less.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the OpenGitOps principles and the &lt;a href="https://github.com/open-gitops/documents/blob/main/GLOSSARY.md" rel="noopener noreferrer"&gt;GitOps glossary&lt;/a&gt;. About 20 minutes. Say the principles out loud without looking.&lt;/li&gt;
&lt;li&gt;Take the free &lt;a href="https://training.linuxfoundation.org/training/introduction-to-gitops-lfs169/" rel="noopener noreferrer"&gt;LFS169 Introduction to GitOps&lt;/a&gt; course. Around 4 hours, and it follows the exam domains in order.&lt;/li&gt;
&lt;li&gt;Do practice questions until the wrong answers look obviously wrong. For this step I built a free &lt;a href="https://yetmike.com/practice/cgoa" rel="noopener noreferrer"&gt;CGOA practice test&lt;/a&gt; from the concepts I had to reason through after that 82, so you can see which domain is weak before exam day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wasted about 2 hours on Flux and Argo CD docs. Nobody cares about &lt;code&gt;argocd app sync&lt;/code&gt; flags here. More study links are in &lt;a href="https://github.com/yetmike/awesome-kubestronaut" rel="noopener noreferrer"&gt;awesome-kubestronaut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;GitOps clicked when I saw it as a &lt;a href="https://en.wikipedia.org/wiki/Control_loop" rel="noopener noreferrer"&gt;control loop&lt;/a&gt; rather than a deployment tool. Same pattern as a &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/" rel="noopener noreferrer"&gt;Kubernetes controller&lt;/a&gt;: watch desired state, compare to actual, reconcile. Desired state lives in Git instead of etcd.&lt;/p&gt;

&lt;p&gt;The four principles then map onto the loop: how you express desired state, how you store it, how the agent gets it, what the agent does with it.&lt;/p&gt;

&lt;p&gt;98 out of 100, first attempt. The concepts aren't hard; they need precision, which is probably why people with years of Argo CD keep landing around 80 first try.&lt;/p&gt;

&lt;p&gt;I still don't know whether those Related Practices questions were bad luck or the curriculum drifting. If you sit it after me, tell me which.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/cgoa-exam" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitops</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>certification</category>
    </item>
    <item>
      <title>The 22 Rust errors every beginner hits, in the order they hit them</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Sun, 16 Aug 2026 20:50:35 +0000</pubDate>
      <link>https://dev.to/yetmike/the-22-rust-errors-every-beginner-hits-in-the-order-they-hit-them-13gi</link>
      <guid>https://dev.to/yetmike/the-22-rust-errors-every-beginner-hits-in-the-order-they-hit-them-13gi</guid>
      <description>&lt;p&gt;Rust's compiler is the best teacher in the language and almost nobody reads it properly. Below are the 22 errors you will actually hit, roughly in the order you'll hit them, with what each one is really telling you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every diagnostic here is real.&lt;/strong&gt; It was captured from a real rustc 1.96.0 (ac68faa20 2026-05-25) compiling a real broken program. The explanations were drafted with an LLM and edited by me.&lt;/p&gt;

&lt;p&gt;Two things to know before the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most Rust errors underline two places.&lt;/strong&gt; One is where the rule was broken. The other is where the decision that broke it was made. The error is reported at the first; the fix is almost always at the second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;help:&lt;/code&gt; block is often a literal diff.&lt;/strong&gt; When you see &lt;code&gt;+++&lt;/code&gt; under a span, rustc isn't describing the fix. It's writing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. E0061: The function takes two arguments and you passed one.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0061]: this function takes 2 arguments but 1 argument was supplied
 --&amp;gt; src/main.rs:6:20
  |
6 |     println!("{}", add(1));
  |                    ^^^--- argument #2 of type `i32` is missing
  |
note: function defined here
 --&amp;gt; src/main.rs:1:4
  |
1 | fn add(a: i32, b: i32) -&amp;gt; i32 {
  |    ^^^         ------
help: provide the argument
  |
6 |     println!("{}", add(1, /* i32 */));
  |                         +++++++++++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Pass both: &lt;code&gt;add(1, 2)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rust has no default parameters and no overloading, so arity is exact. That sounds restrictive until you notice it means a function signature is a complete description of how to call it. There is never a hidden second way.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. E0277: &lt;code&gt;Vec&lt;/code&gt; has no single obvious way to print itself, so &lt;code&gt;{}&lt;/code&gt; will not take it.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0277]: `Vec&amp;lt;{integer}&amp;gt;` doesn't implement `std::fmt::Display`
 --&amp;gt; src/main.rs:3:20
  |
3 |     println!("{}", numbers);
  |               --   ^^^^^^^ `Vec&amp;lt;{integer}&amp;gt;` cannot be formatted with the default formatter
  |               |
  |               required by this formatting parameter
  |
  = help: the trait `std::fmt::Display` is not implemented for `Vec&amp;lt;{integer}&amp;gt;`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Use the debug formatter: &lt;code&gt;println!("{:?}", numbers);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the first trait error most people meet, and the message names the exact missing capability: &lt;code&gt;Vec&amp;lt;i32&amp;gt; doesn't implement std::fmt::Display&lt;/code&gt;. Read trait errors as a sentence (this type cannot do that thing) rather than as a wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. E0282: Rust cannot work out what type this collection holds, because nothing was ever put in it.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0282]: type annotations needed for `Vec&amp;lt;_&amp;gt;`
 --&amp;gt; src/main.rs:2:9
  |
2 |     let items = Vec::new();
  |         ^^^^^   ---------- type must be known at this point
  |
help: consider giving `items` an explicit type, where the type for type parameter `T` is specified
  |
2 |     let items: Vec&amp;lt;T&amp;gt; = Vec::new();
  |              ++++++++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Say it: &lt;code&gt;let items: Vec&amp;lt;i32&amp;gt; = Vec::new();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Rust infers types from &lt;em&gt;use&lt;/em&gt;, not from declaration. Push one integer and the annotation becomes unnecessary, because the inference works forward from evidence, and here you gave it none.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. E0308: You promised an &lt;code&gt;i32&lt;/code&gt; and returned nothing.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0308]: mismatched types
 --&amp;gt; src/main.rs:1:14
  |
1 | fn five() -&amp;gt; i32 {
  |    ----      ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     5;
  |      - help: remove this semicolon to return this value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Delete the semicolon after &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;()&lt;/code&gt; is Rust's word for "nothing". Whenever you see &lt;code&gt;found ()&lt;/code&gt;, look for a stray semicolon before you look at anything else. In Rust an expression without a semicolon &lt;em&gt;is&lt;/em&gt; the value, and with one it becomes a statement that evaluates to nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. E0381: The variable was declared but never given a value, and you read it.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0381]: used binding `count` isn't initialized
 --&amp;gt; src/main.rs:3:16
  |
2 |     let count: i32;
  |         ----- binding declared here but left uninitialized
3 |     println!("{count}");
  |                ^^^^^ `count` used here but it isn't initialized
  |
help: consider assigning a value
  |
2 |     let count: i32 = 42;
  |                    ++++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Give it a value: &lt;code&gt;let count: i32 = 0;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Other languages hand you a zero or a null here. Rust refuses to guess, and that refusal is the point: there is no such thing as an uninitialised read in safe Rust, so a whole category of bug cannot reach runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. E0384: &lt;code&gt;let&lt;/code&gt; bindings do not change unless you say &lt;code&gt;mut&lt;/code&gt;.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0384]: cannot assign twice to immutable variable `x`
 --&amp;gt; src/main.rs:3:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut x = 5;
  |         +++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Write &lt;code&gt;let mut x = 5;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Two spans, not one: the &lt;code&gt;^^^^^&lt;/code&gt; is where the rule broke, the &lt;code&gt;-&lt;/code&gt; marks where the decision was made. The error is reported at the assignment, the thing you change is the declaration. That pattern holds across most Rust errors, and once you see it you cannot unsee it.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. E0425: You used a name that was never declared in this scope.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0425]: cannot find value `count` in this scope
 --&amp;gt; src/main.rs:2:20
  |
2 |     println!("{}", count);
  |                    ^^^^^ not found in this scope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Declare it first: &lt;code&gt;let count = 0;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ninety percent of the time this is a typo, and rustc will often guess the name you meant in a &lt;code&gt;help:&lt;/code&gt; line. Read that before you go looking for a real bug, because the compiler has already done the search.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. E0596: You called a method that needs to modify the value, on a binding that cannot be modified.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0596]: cannot borrow `numbers` as mutable, as it is not declared as mutable
 --&amp;gt; src/main.rs:3:5
  |
3 |     numbers.push(4);
  |     ^^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut numbers = vec![1, 2, 3];
  |         +++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; &lt;code&gt;let mut numbers = vec![1, 2, 3];&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The interesting part is that &lt;code&gt;push&lt;/code&gt; never says "mutable" in your code; you have to know its signature takes &lt;code&gt;&amp;amp;mut self&lt;/code&gt;. The compiler is telling you a fact about a function you did not write, which is why the fix is on a different line from the error.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. E0599: That type has no such method.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0599]: no method named `push` found for type `{integer}` in the current scope
 --&amp;gt; src/main.rs:3:11
  |
3 |     count.push(1);
  |           ^^^^ method not found in `{integer}`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Use a type that has it, or the right method for this one.&lt;/p&gt;

&lt;p&gt;When the type is right but the method is missing, rustc often lists candidates with similar names, and sometimes tells you a trait exists but is not in scope, which is a &lt;code&gt;use&lt;/code&gt; statement away, not a redesign.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. E0601: Every Rust program starts at a function called &lt;code&gt;main&lt;/code&gt;, and there isn't one.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0601]: `main` function not found in crate `snippet`
 --&amp;gt; src/main.rs:3:2
  |
3 | }
  |  ^ consider adding a `main` function to src/main.rs`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Rename the function to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The compiler is not looking for your code, it is looking for one specific name. Rust has no "top level" that runs. The entry point is a convention with no flexibility, and this is usually the first time a beginner learns the program has a designated front door.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. E0618: You put call parentheses after something that is not a function.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0618]: expected function, found `{integer}`
 --&amp;gt; src/main.rs:3:18
  |
2 |     let total = 5;
  |         ----- `total` has type `{integer}`
3 |     let result = total();
  |                  ^^^^^--
  |                  |
  |                  call expression requires function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Drop the parentheses, or call the function you meant.&lt;/p&gt;

&lt;p&gt;Usually a shadowed name: a variable and a function with the same identifier, and the variable won. The &lt;code&gt;help:&lt;/code&gt; line will tell you what the thing actually is, which is faster than re-reading your own code.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. E0765: A string was opened with a quote and never closed.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0765]: unterminated double quote string
 --&amp;gt; src/main.rs:3:25
  |
3 |       println!("{greeting}");
  |  _________________________^
4 | | }
  | |_^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Close the quote: &lt;code&gt;let greeting = "hello";&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice the error points at where the string &lt;em&gt;started&lt;/em&gt;, not where the compiler gave up. That is the general pattern for unclosed things: the reported line is the opening, because the compiler cannot know which of the following lines you meant to close it on.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. E0004: A &lt;code&gt;match&lt;/code&gt; has to cover every possible case and yours misses one.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0004]: non-exhaustive patterns: `Direction::South` not covered
  --&amp;gt; src/main.rs:8:11
   |
 8 |     match d {
   |           ^ pattern `Direction::South` not covered
   |
note: `Direction` defined here
  --&amp;gt; src/main.rs:1:6
   |
 1 | enum Direction {
   |      ^^^^^^^^^
 2 |     North,
 3 |     South,
   |     ----- not covered
   = note: the matched value is of type `Direction`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
 9 ~         Direction::North =&amp;gt; println!("up"),
10 ~         Direction::South =&amp;gt; todo!(),
   |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Add the missing arm, or a &lt;code&gt;_ =&amp;gt; {}&lt;/code&gt; catch-all.&lt;/p&gt;

&lt;p&gt;This is the feature you will miss most in other languages. Add a variant to the enum a year later and every match that forgot to handle it fails to compile. The compiler maintains your switch statements for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. E0005: That pattern can fail, and &lt;code&gt;let&lt;/code&gt; has nowhere to go when it does.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0005]: refutable pattern in local binding
 --&amp;gt; src/main.rs:3:9
  |
3 |     let Some(value) = maybe;
  |         ^^^^^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
  = note: the matched value is of type `Option&amp;lt;i32&amp;gt;`
help: you might want to use `let...else` to handle the variant that isn't matched
  |
3 |     let Some(value) = maybe else { todo!() };
  |                             ++++++++++++++++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Use &lt;code&gt;if let Some(value) = maybe { … }&lt;/code&gt; or &lt;code&gt;let Some(value) = maybe else { … };&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let&lt;/code&gt; must always succeed, so it only accepts patterns that cannot fail. This is where &lt;code&gt;if let&lt;/code&gt; and &lt;code&gt;let … else&lt;/code&gt; come from: they are the versions with somewhere to put the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. E0063: You built a struct without all of its fields.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0063]: missing field `y` in initializer of `Point`
 --&amp;gt; src/main.rs:7:13
  |
7 |     let p = Point { x: 1 };
  |             ^^^^^ missing `y`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Supply &lt;code&gt;y&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;There is no partially-built struct in Rust. Every field is set at construction or the value does not exist, which is why you never have to check whether a field was initialised.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. E0070: The left side of &lt;code&gt;=&lt;/code&gt; has to be something that can be assigned to.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0070]: invalid left-hand side of assignment
 --&amp;gt; src/main.rs:2:7
  |
2 |     5 = 6;
  |     - ^
  |     |
  |     cannot assign to this expression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Assign to a variable, or use &lt;code&gt;==&lt;/code&gt; if you meant to compare.&lt;/p&gt;

&lt;p&gt;Almost always a &lt;code&gt;==&lt;/code&gt; typed as &lt;code&gt;=&lt;/code&gt;. In C this class of typo silently compiles inside an &lt;code&gt;if&lt;/code&gt;; Rust rejects it because a condition must be a &lt;code&gt;bool&lt;/code&gt; and an assignment is not one.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. E0382: The value moved to a new owner, and you used the old name afterwards.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0382]: borrow of moved value: `name`
 --&amp;gt; src/main.rs:4:16
  |
2 |     let name = String::from("crab");
  |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
3 |     let other = name;
  |                 ---- value moved here
4 |     println!("{name}");
  |                ^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let other = name.clone();
  |                     ++++++++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Clone it, or borrow with &lt;code&gt;&amp;amp;name&lt;/code&gt;, or use &lt;code&gt;other&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Read the &lt;code&gt;note:&lt;/code&gt; rather than the error: &lt;em&gt;move occurs because &lt;code&gt;String&lt;/code&gt; does not implement the &lt;code&gt;Copy&lt;/code&gt; trait&lt;/em&gt;. That single line is the whole ownership model. An &lt;code&gt;i32&lt;/code&gt; in the same code would have been fine, because copying eight bytes is free and copying a heap allocation is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. E0423: You used a struct's name where a value was expected.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0423]: expected value, found struct `Config`
 --&amp;gt; src/main.rs:6:13
  |
1 | / struct Config {
2 | |     debug: bool,
3 | | }
  | |_- `Config` defined here
...
6 |       let c = Config;
  |               ^^^^^^ help: use struct literal syntax instead: `Config { debug: val }`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Construct it: &lt;code&gt;Config { debug: true }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A struct name is a type, not a value, unless it is a unit struct, in which case the name &lt;em&gt;is&lt;/em&gt; the value. That inconsistency is real and worth knowing, and the error message tells you which case you are in.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. E0428: Two things with the same name in the same scope.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0428]: the name `total` is defined multiple times
 --&amp;gt; src/main.rs:5:1
  |
1 | fn total() -&amp;gt; i32 {
  | ----------------- previous definition of the value `total` here
...
5 | fn total() -&amp;gt; i32 {
  | ^^^^^^^^^^^^^^^^^ `total` redefined here
  |
  = note: `total` must be defined only once in the value namespace of this module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Rename one of them.&lt;/p&gt;

&lt;p&gt;No overloading in Rust: one name, one item, per scope. This is the language deciding that "which one did I call?" should never be a question you have to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  20. E0432: The path in your &lt;code&gt;use&lt;/code&gt; statement does not exist.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0432]: unresolved import `std::collections::HashMapp`
 --&amp;gt; src/main.rs:1:5
  |
1 | use std::collections::HashMapp;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ no `HashMapp` in `collections`
  |
help: a similar name exists in the module
  |
1 - use std::collections::HashMapp;
1 + use std::collections::HashMap;
  |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Fix the spelling: &lt;code&gt;HashMap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Import errors surface before type errors, so a single typo in a &lt;code&gt;use&lt;/code&gt; line can produce a cascade of unrelated-looking failures below it. Always fix the topmost error first and recompile. Most of the rest often vanish.&lt;/p&gt;

&lt;h2&gt;
  
  
  21. E0433: The type exists in the standard library but has not been brought into scope.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0433]: cannot find type `HashMap` in this scope
 --&amp;gt; src/main.rs:2:18
  |
2 |     let scores = HashMap::new();
  |                  ^^^^^^^ use of undeclared type `HashMap`
  |
help: consider importing this struct
  |
1 + use std::collections::HashMap;
  |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; &lt;code&gt;use std::collections::HashMap;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Rust's prelude is deliberately tiny: only the handful of items nearly every program needs. Everything else you import by hand, and rustc will usually print the exact &lt;code&gt;use&lt;/code&gt; line, which you can paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  22. E0609: That struct has no field by that name.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[E0609]: no field `z` on type `Point`
 --&amp;gt; src/main.rs:8:22
  |
8 |     println!("{}", p.z);
  |                      ^ unknown field
  |
help: a field with a similar name exists
  |
8 -     println!("{}", p.z);
8 +     println!("{}", p.x);
  |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix.&lt;/strong&gt; Use a field that exists, or add it to the struct.&lt;/p&gt;

&lt;p&gt;rustc lists the fields that do exist, which makes this one of the errors you can fix without leaving the terminal. Worth noticing: the available-fields list is the compiler volunteering information you did not ask for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern behind all of them
&lt;/h2&gt;

&lt;p&gt;Once you've read a few hundred of these, they stop being 22 separate errors and become four questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Who owns this value, and did I give it away?&lt;/strong&gt; Ownership errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long does this reference need to live?&lt;/strong&gt; Lifetime errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does this type do the thing I'm asking of it?&lt;/strong&gt; Trait errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did I say what I meant?&lt;/strong&gt; Type, scope and mutability errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the language's whole difficulty curve, and the compiler tells you which of the four you're in every single time.&lt;/p&gt;




&lt;p&gt;If you want to practise these against a real compiler without installing anything, that's what I built codecrab for: actual &lt;code&gt;rustc&lt;/code&gt; output, the failing span underlined in your own source, one hint at a time. 94 exercises. Exercises derive from rustlings (MIT).&lt;/p&gt;

&lt;p&gt;Compiling costs real money and I'd like this to still exist in 2028. The 94 exercises above stay free - the Pro subscription Pass raises the daily compile limit and opens the courses built from the error corpus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codecrab.app/" rel="noopener noreferrer"&gt;https://codecrab.app/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Forward Deployed Engineer is outsourcing with a better logo</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Fri, 14 Aug 2026 00:26:41 +0000</pubDate>
      <link>https://dev.to/yetmike/forward-deployed-engineer-is-outsourcing-with-a-better-logo-3o55</link>
      <guid>https://dev.to/yetmike/forward-deployed-engineer-is-outsourcing-with-a-better-logo-3o55</guid>
      <description>&lt;p&gt;I spent more than five years in outsourcing companies. That is where I started my career.&lt;/p&gt;

&lt;p&gt;The job looked like this: get assigned to a client, learn their infrastructure, fight their legacy systems, sit in calls with people who could not describe what they wanted, ship something that worked, move to the next client. Sometimes I was on their VPN more than my own employer's. Sometimes I knew their systems better than their own staff did.&lt;/p&gt;

&lt;p&gt;Now the same job has a new name and a salary that can be twice as high. It is called Forward Deployed Engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the name came from
&lt;/h2&gt;

&lt;p&gt;The term belongs to &lt;a href="https://en.wikipedia.org/wiki/Palantir_Technologies" rel="noopener noreferrer"&gt;Palantir&lt;/a&gt;. The earliest published use I could find is a &lt;a href="https://techcrunch.com/2010/06/25/palantir-the-next-billion-dollar-company-raises-90-million/" rel="noopener noreferrer"&gt;TechCrunch piece from June 2010&lt;/a&gt; about a funding round, which identifies one of their employees by the title forward deployed engineer.&lt;/p&gt;

&lt;p&gt;The name is borrowed from the military, where a &lt;a href="https://en.wikipedia.org/wiki/Forward_operating_base" rel="noopener noreferrer"&gt;forward deployed&lt;/a&gt; unit sits in the field instead of at home base. That is the whole idea. Palantir was selling &lt;a href="https://en.wikipedia.org/wiki/Data_integration" rel="noopener noreferrer"&gt;data integration&lt;/a&gt; software to intelligence and defense customers who could not describe what they needed, partly because the requirements were classified and partly because nobody had written them down. Shipping the software did not work. So they shipped engineers, cleared and embedded on site, writing production code inside the customer's environment for months at a time.&lt;/p&gt;

&lt;p&gt;Internally Palantir called them Deltas, a name they explain in their own post on &lt;a href="https://blog.palantir.com/dev-versus-delta-demystifying-engineering-roles-at-palantir-ad44c2a6e87" rel="noopener noreferrer"&gt;Dev versus Delta&lt;/a&gt;. According to &lt;a href="https://newsletter.pragmaticengineer.com/p/forward-deployed-engineers" rel="noopener noreferrer"&gt;The Pragmatic Engineer&lt;/a&gt;, the company had more FDEs than regular software engineers until roughly 2016. That is a striking ratio for a company that sells a product. There is also &lt;a href="https://blog.palantir.com/a-day-in-the-life-of-a-palantir-forward-deployed-software-engineer-45ef2de257b1" rel="noopener noreferrer"&gt;a day in the life of an FDE&lt;/a&gt; from 2022 if you want their version of the job.&lt;/p&gt;

&lt;p&gt;So the name is about fifteen years old, and the current wave of AI companies hiring FDEs did not invent anything. They picked up a label that already had a decade of history and a good story attached to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The job description is identical
&lt;/h2&gt;

&lt;p&gt;Read what an FDE is supposed to do. Integrate a vendor's product with the client's messy data. Build custom pipelines and workflows on top of it. Sit with non-technical stakeholders and translate business pain into technical requirements. Feed real-world edge cases back to the product team.&lt;/p&gt;

&lt;p&gt;Now read what I did in outsourcing. Integrate whatever the client bought with whatever the client already had. Build custom pipelines on top of it. Sit with non-technical stakeholders and translate business pain into technical requirements. File bugs and feature requests upstream when the vendor's product could not do the thing the client paid for.&lt;/p&gt;

&lt;p&gt;I cannot find the daily difference. Same messy enterprise APIs. Same undocumented internal system that one person understands and that person is on vacation. Same client stakeholder who changes the requirement after the sprint started. Same travel, same context switching, same burnout curve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbdy28k56an4zxk89qhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbdy28k56an4zxk89qhm.png" alt="Scooby-Doo unmasking meme: the masked figure is labelled Forward Deployed Engineer, and underneath the mask is an outsource engineer on twice the pay" width="600" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually differs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuxws8qdq33778w13m1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpuxws8qdq33778w13m1j.png" alt="Outsourcing agency sells hours and locks the client into people; a product vendor sells licenses and locks the client into the platform. Same engineer, same messy APIs, up to twice the pay" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two business models, one job description.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An outsourcing agency sells hours. The unit of revenue is a person occupying a seat for a month. If a client cuts ten engineers, the agency loses ten engineers of revenue. Every engineer is a line item, and line items get negotiated down. That is why the rate is what it is, and it is why the agency's incentive is to keep bodies billable rather than to make any single engineer irreplaceable.&lt;/p&gt;

&lt;p&gt;A product vendor sells licenses. The unit of revenue is an annual contract with a margin an agency will never see. The engineer they send into the client is not the product. The engineer is what makes the contract renew. One person who unblocks a stuck rollout can be the reason a seven-figure renewal happens instead of a churn. That is a very different number to justify a salary against.&lt;/p&gt;

&lt;p&gt;If you want the numbers, &lt;a href="https://www.levels.fyi/" rel="noopener noreferrer"&gt;levels.fyi&lt;/a&gt; has them. Palantir's forward deployed software engineer sits around $211K median. An &lt;a href="https://www.levels.fyi/companies/epam-systems/salaries/software-engineer/locations/united-states" rel="noopener noreferrer"&gt;EPAM engineer in the US&lt;/a&gt; is $165K. &lt;a href="https://www.levels.fyi/companies/accenture/salaries/software-engineer/locations/united-states" rel="noopener noreferrer"&gt;Accenture&lt;/a&gt; is closer to $109K. The frontier labs pay far more than any of them, but they pay everyone far more, so that gap is the company and not the title.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lock-in argument does not hold up
&lt;/h2&gt;

&lt;p&gt;The usual defense is that outsourcing and FDE work create different kinds of dependency. Outsourcing locks the client into people who understand the custom code. FDE work locks the client into the vendor's platform.&lt;/p&gt;

&lt;p&gt;Both are &lt;a href="https://en.wikipedia.org/wiki/Vendor_lock-in" rel="noopener noreferrer"&gt;lock-in&lt;/a&gt;. I have watched both happen. I have written the pipeline that nobody else could maintain, and I have also been the one who inherited someone else's version of it three years later and needed two weeks just to draw the diagram.&lt;/p&gt;

&lt;p&gt;The distinction people draw is real but small. It describes who captures the value, not what the engineer does with their day. An engineer wiring a client's legacy database into a vendor's domain model is doing the same work whether the invoice says &lt;a href="https://en.wikipedia.org/wiki/Time_and_materials" rel="noopener noreferrer"&gt;time and materials&lt;/a&gt; or annual license.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would actually tell someone trying to make the jump
&lt;/h2&gt;

&lt;p&gt;If you are in outsourcing and you want the FDE title and the FDE compensation, the honest advice is to stop optimizing your engineering and start optimizing three other things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn one product deeply instead of ten stacks shallowly.&lt;/strong&gt; Outsourcing rewards breadth. You get thrown at whatever the next contract needs, and after five years you have touched a dozen stacks and mastered none of them. Product companies hire for the opposite. They want someone who knows their platform's data model well enough to argue with the core team about it. Pick a platform, go deep, get certified if the vendor offers it, build something real on it in public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get comfortable being wrong in front of executives.&lt;/strong&gt; In outsourcing my escalation path was almost always internal. There was an account manager and a delivery lead between me and anyone who owned a budget. FDE work removes those layers. You are in the room where the customer decides whether to keep paying, and you have to say "that integration will take three weeks and here is why" to someone who wanted to hear one week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go find out what they are actually hiring for.&lt;/strong&gt; Change your LinkedIn title to Forward Deployed Engineer and start reading the job posts. Scrape them if you want, there is nothing hidden here. A few dozen descriptions will tell you what the requirements really are, and once you line them up against your own CV the gaps are obvious. Then build a plan to close them.&lt;/p&gt;

&lt;p&gt;What you will notice is that almost every one of these roles is at a product company. So you need a product mindset. Not just "solve the client's problem", but understand why the company put you at this particular client, what that account means to them, and what happens if it goes badly.&lt;/p&gt;

&lt;p&gt;None of this is about writing better code. If you are already the person the client asks for by name, you have the engineering part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I am not sure about
&lt;/h2&gt;

&lt;p&gt;I do not know how durable the FDE label is. Roles that exist to make a specific product land tend to get squeezed when the product gets easier to deploy, or when the vendor decides the work belongs to a partner network. That is more or less what happened to a lot of specialist consulting niches over the last decade.&lt;/p&gt;

&lt;p&gt;I also do not know whether the pay gap survives the title becoming common. Right now it is scarce enough that the compensation reflects the revenue it protects. In five years there may be a lot of people with FDE on their profile, and the salary may look a lot more like a senior engineer's, and the same people who chased the title will be looking for the next one.&lt;/p&gt;

&lt;p&gt;I did this job for five years before it had the good name. Nobody called it strategic then, and I still cannot tell whether they were wrong about the work or just early on the pricing.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://yetmike.com/blog/forward-deployed-engineer-vs-outsourcing?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=forward-deployed-engineer-vs-outsourcing" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>engineering</category>
      <category>consulting</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Podman Lost to Docker. I Stopped Fighting It.</title>
      <dc:creator>Mykhailo</dc:creator>
      <pubDate>Mon, 27 Apr 2026 20:18:32 +0000</pubDate>
      <link>https://dev.to/yetmike/podman-lost-to-docker-i-stopped-fighting-it-21hi</link>
      <guid>https://dev.to/yetmike/podman-lost-to-docker-i-stopped-fighting-it-21hi</guid>
      <description>&lt;p&gt;Most "Podman vs Docker" articles treat it as a technical comparison. It isn't. It's a migration cost problem.&lt;/p&gt;

&lt;p&gt;The technical case for Podman is real: rootless by default (not opt-in like &lt;a href="https://docs.docker.com/engine/security/rootless/" rel="noopener noreferrer"&gt;Docker's 20.10 mode&lt;/a&gt;), no &lt;code&gt;dockerd&lt;/code&gt; running as root, no &lt;a href="https://www.docker.com/pricing/" rel="noopener noreferrer"&gt;$9–15/user/month&lt;/a&gt; for Docker Desktop, and &lt;a href="https://www.redhat.com/en/blog/quadlet-podman" rel="noopener noreferrer"&gt;Quadlet&lt;/a&gt; (5.0, 2025) for native &lt;a href="https://systemd.io/" rel="noopener noreferrer"&gt;systemd&lt;/a&gt; integration. These are genuine architectural wins.&lt;/p&gt;

&lt;p&gt;But &lt;a href="https://survey.stackoverflow.co/2025/technology" rel="noopener noreferrer"&gt;Stack Overflow's 2025 survey&lt;/a&gt; shows Docker at 71.1% adoption — the largest single-year jump of any technology. Podman at 11.1%. &lt;a href="https://www.docker.com/blog/docker-stack-overflow-survey-thank-you-2024/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt;: 318 billion pulls. &lt;a href="https://podman-desktop.io/blog/podman-desktop-2025-journey" rel="noopener noreferrer"&gt;Podman Desktop&lt;/a&gt;: 3 million total downloads since launch.&lt;/p&gt;

&lt;p&gt;That's not a competitor. That's a niche.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that actually matters
&lt;/h2&gt;

&lt;p&gt;Not "which is better?" but "what context are you in?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting fresh — use Podman if it fits.&lt;/strong&gt; On &lt;a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux" rel="noopener noreferrer"&gt;RHEL&lt;/a&gt;, it's the obvious choice: &lt;a href="https://developers.redhat.com/articles/2023/08/03/podman-next-generation-container-management-tool" rel="noopener noreferrer"&gt;Red Hat ships it by default&lt;/a&gt;, Quadlet integrates directly with systemd, rootless-by-default matters when someone audits your runtime. &lt;a href="https://www.cncf.io/projects/podman/" rel="noopener noreferrer"&gt;The CNCF accepted it into sandbox in January 2025&lt;/a&gt;. Greenfield project, no legacy tooling — go for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating an existing Docker setup — think twice.&lt;/strong&gt; This is where the calculation breaks down. The spreadsheet shows Docker Desktop at $9–15/user/month. It doesn't show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migration day&lt;/li&gt;
&lt;li&gt;Debugging socket path differences (&lt;a href="https://podman.io/docs/installation" rel="noopener noreferrer"&gt;Podman uses a different socket location&lt;/a&gt; than Docker)&lt;/li&gt;
&lt;li&gt;Updating CI pipelines built around Docker socket compatibility&lt;/li&gt;
&lt;li&gt;Fixing &lt;a href="https://containers.dev/" rel="noopener noreferrer"&gt;Dev Containers&lt;/a&gt; when VS Code stops finding the right runtime&lt;/li&gt;
&lt;li&gt;The 30-minute Podman explanation for every new hire who's never heard of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are real hours from real people. None of them ship anything a user will ever see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Docker won anyway
&lt;/h2&gt;

&lt;p&gt;Not on merit. On surface area.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; assumes Docker. &lt;a href="https://rancherdesktop.io/" rel="noopener noreferrer"&gt;Rancher Desktop&lt;/a&gt; defaults to Docker. &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP server configs&lt;/a&gt; reference Docker. Every Stack Overflow answer assumes Docker. Every internal platform template your company has written assumes Docker.&lt;/p&gt;

&lt;p&gt;When you switch, you're not just swapping a binary. You're swimming against the accumulated inertia of every tool your team uses daily. Podman's remaining real advantages — daemonless architecture, rootless-by-default, Quadlet — are genuinely better. They're also genuinely invisible to 90% of the people making the decision.&lt;/p&gt;

&lt;p&gt;I wrote about the full trade-off breakdown, including where each argument actually landed and what Docker did and didn't fix, &lt;a href="https://yetmike.com/blog/podman-lost-to-docker/?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=podman-lost-to-docker" rel="noopener noreferrer"&gt;in the original post on yetmike.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The short version: run the numbers with migration costs included. Then decide.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://yetmike.com/blog/podman-lost-to-docker/?utm_source=devto&amp;amp;utm_medium=cross-post&amp;amp;utm_campaign=podman-lost-to-docker" rel="noopener noreferrer"&gt;yetmike.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>podman</category>
      <category>devops</category>
      <category>containers</category>
    </item>
  </channel>
</rss>
