<?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>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>
