<?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: Charalambos Emmanouilidis</title>
    <description>The latest articles on DEV Community by Charalambos Emmanouilidis (@charemma).</description>
    <link>https://dev.to/charemma</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%2F3835794%2F6d136d29-aa83-4c90-b50c-dc422943c3f2.jpeg</url>
      <title>DEV Community: Charalambos Emmanouilidis</title>
      <link>https://dev.to/charemma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/charemma"/>
    <language>en</language>
    <item>
      <title>When Agile Is Not Enough: Developing Software at Agent Speed</title>
      <dc:creator>Charalambos Emmanouilidis</dc:creator>
      <pubDate>Mon, 07 Sep 2026 10:41:29 +0000</pubDate>
      <link>https://dev.to/charemma/when-agile-is-not-enough-1c9o</link>
      <guid>https://dev.to/charemma/when-agile-is-not-enough-1c9o</guid>
      <description>&lt;p&gt;&lt;em&gt;Agentic Development, Part 1&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you develop software when agents write the code? Not someday. Now.
&lt;/h2&gt;

&lt;p&gt;Within a few weeks, my solo open-source project had more than 100 open issues. At the same time, an AI agent team was producing pull requests faster than I could properly review them.&lt;/p&gt;

&lt;p&gt;I had automated implementation, but not delivery. I had increased the capacity of the wrong part of the system.&lt;/p&gt;

&lt;p&gt;That was when I realized that agentic development is not simply faster Agile. It changes the bottleneck on which the whole development process has to be built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agile solved a different problem
&lt;/h2&gt;

&lt;p&gt;Waterfall-shaped development made change slow and expensive. Releases were planned months ahead. Features felt carved in stone, and feedback or bug-fix loops often came late in the release.&lt;/p&gt;

&lt;p&gt;Scrum, Kanban, and the broader Agile movement addressed exactly those pain points. They shortened planning horizons, reduced batch sizes, and made it possible to react while development was still in progress. That was a necessary step, and software development is better for it.&lt;/p&gt;

&lt;p&gt;But these methods still grew around human teams working at human speed.&lt;/p&gt;

&lt;p&gt;Coding is not the whole of software development, but implementation used to consume a large part of its time and capacity. A team could only implement so much during a sprint. Planning, implementation, review, and acceptance operated at roughly compatible speeds.&lt;/p&gt;

&lt;p&gt;Agents break that capacity model.&lt;/p&gt;

&lt;p&gt;A human can now send several agents into the same codebase, or orchestrate a specialized team that plans, implements, tests, reviews, and reworks an issue. Execution can happen in parallel and continue as long as work is available.&lt;/p&gt;

&lt;p&gt;Work that previously filled a sprint can now be completed in hours. A sprint backlog can become outdated while a human is still reviewing the first results. The cadence no longer controls execution. Completed work arrives as a continuous stream.&lt;/p&gt;

&lt;p&gt;Human attention does not scale with it.&lt;/p&gt;

&lt;p&gt;The result is not just a shorter sprint. It is a different production system. Implementation becomes cheap and elastic, while intent, judgment, review, and responsibility remain scarce.&lt;/p&gt;

&lt;p&gt;Scrum does not scale to that situation. It was not designed for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system I built
&lt;/h2&gt;

&lt;p&gt;To be clear, most of my experience so far has been solo plus AI, not a larger engineering team. It is not representative of every organization. It is, however, a real system I use to develop real software.&lt;/p&gt;

&lt;p&gt;I built a tool for orchestrating AI agents around a workflow like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Human commits an issue → agent team implements it, opens a pull request, reviews and reworks the result → human approves the merge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The individual steps run through different agents, LLMs, and models. Each role has specialized rules and prompts for its domain.&lt;/p&gt;

&lt;p&gt;The setup worked. Then the speed blew up in my face.&lt;/p&gt;

&lt;h2&gt;
  
  
  More output did not mean more delivery
&lt;/h2&gt;

&lt;p&gt;Features and issues were implemented so quickly that I could no longer keep up with reviewing the code and pull requests properly.&lt;/p&gt;

&lt;p&gt;Adding another agent did not solve this. It made the queue larger.&lt;/p&gt;

&lt;p&gt;The human became the bottleneck.&lt;/p&gt;

&lt;p&gt;More agent throughput did not produce more delivery. It produced more inventory waiting for human judgment.&lt;/p&gt;

&lt;p&gt;The same thing happened before implementation even started.&lt;/p&gt;

&lt;p&gt;I used an LLM-agent to discuss ideas, plan features, break work down, and identify follow-up tasks. While the agent team was implementing one issue, it could discover several more and file them immediately. Creating an issue was cheap, so almost every reasonable observation became one.&lt;/p&gt;

&lt;p&gt;After a few weeks, the project had more than 100 open issues.&lt;/p&gt;

&lt;p&gt;Each issue looked useful on its own. Together, they were no longer a plan I could oversee. The backlog had become exhaust from planning and implementation.&lt;/p&gt;

&lt;p&gt;An issue may be cheap to create, but it is not free to own. Someone still has to understand it, compare it with everything else, decide whether it matters, keep its context current, and eventually accept or reject the result.&lt;/p&gt;

&lt;p&gt;Agents had made both sides of the problem faster: producing software and producing demand for more software. The human capacity between them had not changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why shorter cycles are not the answer
&lt;/h2&gt;

&lt;p&gt;Scrum can reduce the amount committed to a sprint. Kanban can limit work in progress. Both are useful ideas, and I still use parts of them.&lt;/p&gt;

&lt;p&gt;But limiting a column on a board does not answer the new questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;May an agent turn a discovery into committed work?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What must be known before an agent is allowed to start?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do we prevent an agent from expanding the scope while it works?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What evidence must it produce before a human review begins?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How much autonomy can it have without also giving it authority?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which capacity controls the flow: agent execution or human validation?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a human team, many of these answers are implicit. Developers carry context between conversations. They recognize uncertainty, ask questions, and remain accountable for their decisions.&lt;/p&gt;

&lt;p&gt;An agent can turn an unclear request into convincing code without ever resolving the ambiguity behind it.&lt;/p&gt;

&lt;p&gt;The danger is not that an agent cannot finish a vague issue. The danger is that it can.&lt;/p&gt;

&lt;p&gt;If we change the roles, capacity model, commitment rules, handoffs, and acceptance process to handle that, we are no longer just making Scrum faster. We are building a different operating model.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working method for agentic development
&lt;/h2&gt;

&lt;p&gt;I started with one simple correction: remembering work and committing to work must be separate actions.&lt;/p&gt;

&lt;p&gt;From there, the following rules emerged.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Anyone may capture. Only a human may commit.
&lt;/h3&gt;

&lt;p&gt;An idea, defect, risk, or improvement may be captured at any time. It does not matter whether a human or an agent found it.&lt;/p&gt;

&lt;p&gt;A capture records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;what was observed,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;where it was found,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;why it might matter,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and enough context to evaluate it later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not yet an implementation issue. It has no milestone, estimate, or detailed specification. It may later be merged with another capture, rewritten, discarded, or simply expire.&lt;/p&gt;

&lt;p&gt;This matters because an agent must be allowed to notice things without gaining the authority to expand the product.&lt;/p&gt;

&lt;p&gt;Commitment is different. A human explicitly pulls a capture into committed work. By doing that, the human is not only asking an agent to implement something. The human is allocating the attention required to specify, review, and accept it.&lt;/p&gt;

&lt;p&gt;Agents may propose work. They may not commit it themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Committed work needs an executable specification.
&lt;/h3&gt;

&lt;p&gt;Once work is committed, it needs enough information for an agent to finish it and for a human to review the result against it.&lt;/p&gt;

&lt;p&gt;The specification should contain at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the intended outcome and why it matters,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the relevant context,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scope and explicit non-goals,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;technical and product constraints,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;acceptance criteria,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the required tests or other evidence,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and any known risks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An LLM can help create that specification. It can ask questions, find missing cases, and propose acceptance criteria. The human still decides when the specification reflects the intended work.&lt;/p&gt;

&lt;p&gt;“Improve error handling” is not executable. It does not say which failure, which behavior should change, or how anyone can prove that the issue is complete.&lt;/p&gt;

&lt;p&gt;Agents are fast enough that unresolved ambiguity becomes code almost immediately. Preparation therefore matters more, not less.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Execution is limited by validation capacity.
&lt;/h3&gt;

&lt;p&gt;The important question is no longer how many issues the agents can execute in parallel. The question is how many completed changes a human can understand and evaluate without losing control of the system.&lt;/p&gt;

&lt;p&gt;I therefore limit committed work according to downstream review capacity. An agent should not start another issue merely because an execution slot is available. It should start when there is also a realistic path through review and acceptance.&lt;/p&gt;

&lt;p&gt;This can leave agents idle while technically more work could be produced. That is intentional.&lt;/p&gt;

&lt;p&gt;An idle agent is cheaper than a pull request that nobody can properly review.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Discoveries do not expand active scope.
&lt;/h3&gt;

&lt;p&gt;Agents regularly find adjacent problems while working: missing validation, a weak abstraction, outdated documentation, another feature that would fit well.&lt;/p&gt;

&lt;p&gt;These discoveries return to capture. They do not automatically become issues, and they do not extend the current task unless they block its acceptance criteria or require an explicit human decision.&lt;/p&gt;

&lt;p&gt;This keeps one useful implementation from turning into five unplanned ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The result must arrive with evidence.
&lt;/h3&gt;

&lt;p&gt;A pull request is not ready for human review just because the code compiles and another agent has approved it.&lt;/p&gt;

&lt;p&gt;The handoff should explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;what changed and why,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how the result satisfies each acceptance criterion,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which tests and checks were run,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which relevant decisions the agents made,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;what risks or uncertainties remain,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and where human judgment is specifically required.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent reviews are useful quality gates. They can find mistakes, compare the implementation with repository rules, and request rework before a human sees the result. But an agent reviewing another agent does not create human accountability.&lt;/p&gt;

&lt;p&gt;At least in my current workflow, final merge authority remains with the human.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal is not to keep up with the agents
&lt;/h2&gt;

&lt;p&gt;My first instinct was to make reviewing faster so I could keep the agents busy. That was the wrong goal.&lt;/p&gt;

&lt;p&gt;The method has to prevent the system from producing more committed change than the responsible humans can absorb. Otherwise, the queue grows, context is lost, reviews become superficial, and “approved” slowly stops meaning understood.&lt;/p&gt;

&lt;p&gt;The scarce resource is no longer typing code. It is making clear decisions and validating their consequences.&lt;/p&gt;

&lt;p&gt;I have been using this approach for only a few weeks. On this project, it works better than the Scrum-shaped workflow I used before. That is all I will claim. It is a field report, not proof of a universal method.&lt;/p&gt;

&lt;p&gt;The team case will also be different. Review capacity can be distributed across several people. Product, architecture, security, and domain decisions may have different owners. But adding agents will create the same underlying imbalance whenever execution capacity grows faster than human judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Waterfall to Agile to Agentic
&lt;/h2&gt;

&lt;p&gt;Waterfall organized software development around phases and a long-term plan.&lt;/p&gt;

&lt;p&gt;Agile organized human teams around short feedback loops and their capacity to implement change.&lt;/p&gt;

&lt;p&gt;The next step has to organize autonomous execution around human intent, bounded authority, and verifiable outcomes.&lt;/p&gt;

&lt;p&gt;I call that broader step &lt;strong&gt;Agentic Development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some Agile principles will survive inside it, just as planning survived the move away from Waterfall. But Agile will no longer be the operating model. It will become part of the inheritance.&lt;/p&gt;

&lt;p&gt;Agentic Development is not Agile with a faster coder added to the team. It starts from a different constraint: agents can produce almost continuously, while humans remain responsible for deciding what should exist and whether the result is acceptable.&lt;/p&gt;

&lt;p&gt;That requires a new method.&lt;/p&gt;

&lt;p&gt;This is my current attempt.&lt;/p&gt;

&lt;p&gt;If your team used Scrum or Kanban before coding agents became part of the delivery process, I would like to know what happened. Which bottleneck appeared first? What did you keep, and what did you have to replace?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agile</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Docker works; until it doesn't. Why I started using Nix for dev environments</title>
      <dc:creator>Charalambos Emmanouilidis</dc:creator>
      <pubDate>Fri, 20 Mar 2026 18:03:51 +0000</pubDate>
      <link>https://dev.to/charemma/docker-works-until-it-doesnt-why-i-started-using-nix-for-dev-environments-2280</link>
      <guid>https://dev.to/charemma/docker-works-until-it-doesnt-why-i-started-using-nix-for-dev-environments-2280</guid>
      <description>&lt;p&gt;It's Monday morning. A new engineer joins the team. By Thursday, they're still fighting their dev environment. The Dockerfile the previous engineer wrote pulls from &lt;code&gt;ubuntu:22.04&lt;/code&gt;, runs &lt;code&gt;apt-get install&lt;/code&gt; without pinned versions, and "works on the CI runner." On the new engineer's M2 Mac it builds fine but the binary crashes at runtime because they're running a different glibc. You've seen this. We all have.&lt;/p&gt;

&lt;p&gt;The thing is: Docker was supposed to fix this. It didn't. It just moved the problem. Not in production -- in development environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Dev Environments
&lt;/h2&gt;

&lt;p&gt;Dev environments fail in predictable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependency drift&lt;/strong&gt;: your local toolchain diverges from CI over weeks. Nobody notices until a build breaks in production but passes locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version manager sprawl&lt;/strong&gt;: &lt;code&gt;nvm&lt;/code&gt;, &lt;code&gt;pyenv&lt;/code&gt;, &lt;code&gt;rbenv&lt;/code&gt;, &lt;code&gt;sdkman&lt;/code&gt; -- one per language, none of them talk to each other, all of them break on OS upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform gaps&lt;/strong&gt;: macOS developers running Linux containers via a VM, dealing with volume mount performance issues and file permission mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding friction&lt;/strong&gt;: a &lt;code&gt;README.md&lt;/code&gt; with fifteen manual steps that's six months out of date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying issue is that most tooling in this space solves &lt;em&gt;isolation&lt;/em&gt; but not &lt;em&gt;reproducibility&lt;/em&gt;. Those are different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Docker Actually Solves Well
&lt;/h2&gt;

&lt;p&gt;Docker is genuinely good at a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime isolation&lt;/strong&gt;: your service runs in a predictable Linux environment regardless of the host OS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution&lt;/strong&gt;: package a runtime with its dependencies, ship it anywhere. This is Docker's strongest use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized CI/CD&lt;/strong&gt;: every major CI platform speaks Docker natively. The ecosystem around it is massive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service dependencies&lt;/strong&gt;: spinning up Postgres, Redis, and Kafka locally with &lt;code&gt;docker compose up&lt;/code&gt; is a legitimately good workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;em&gt;running services&lt;/em&gt;, Docker is the right tool. It's the &lt;em&gt;dev environment&lt;/em&gt; use case where things get complicated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Docker Hits Its Limits
&lt;/h2&gt;

&lt;p&gt;The Dockerfile model is procedural, not declarative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    cmake &lt;span class="se"&gt;\
&lt;/span&gt;    ninja-build &lt;span class="se"&gt;\
&lt;/span&gt;    gcc &lt;span class="se"&gt;\
&lt;/span&gt;    python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fetches whatever happens to be current at build time. You're not building a stable environment. You're replaying a moving target -- without even knowing what changed -- and taking a dependency on the state of the internet that day. Build the same Dockerfile six months later: different packages, different behavior, zero indication anything changed.&lt;/p&gt;

&lt;p&gt;The usual response is to pin the image digest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04@sha256:58b87898c82e...&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; cmake ninja-build gcc python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better -- but the packages inside still drift. You've pinned the base layer, not the build. And now you own the update cycle manually. So even "best practices" don't fully solve the problem.&lt;/p&gt;

&lt;p&gt;More practically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS and Windows performance&lt;/strong&gt;: Docker on macOS runs inside a Linux VM. On Windows, you're going through WSL2 -- and while WSL2 is functional, cross-filesystem I/O (your code lives on the Windows side, the build runs on the Linux side) kills performance. Large C++ or Go builds inside a container on either platform are noticeably slower than native Linux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevContainers + Kubernetes&lt;/strong&gt;: if your team runs DevContainers against a remote Kubernetes cluster, you've already hit the wall. VSCode remote development, build performance, port forwarding, image rebuild cycles -- it adds friction at every layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build caching is fragile&lt;/strong&gt;: Docker layer cache depends on instruction order and content. It breaks in non-obvious ways and is hard to share across machines without a registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DinD is a mess&lt;/strong&gt;: if your CI or dev workflow needs Docker inside Docker, you're fighting containerd, socket mounts, and privilege escalation from day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Snapshot vs. Derivation
&lt;/h2&gt;

&lt;p&gt;This is the core difference.&lt;/p&gt;

&lt;p&gt;A Docker image is a snapshot. A Nix build is a derivation.&lt;/p&gt;

&lt;p&gt;A snapshot tells you &lt;em&gt;what existed&lt;/em&gt;. A derivation tells you &lt;em&gt;how to get there&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That difference matters when things break -- because with a snapshot you can restore, but you can't reason. With a derivation you can reproduce, audit, and understand exactly what changed and why.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Nix Approaches the Problem
&lt;/h2&gt;

&lt;p&gt;Nix takes a different angle. Instead of "here's a script that builds an environment," it asks: given a precise description of &lt;em&gt;inputs&lt;/em&gt;, what are the &lt;em&gt;outputs&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Every package in Nix is identified by a cryptographic hash of its inputs -- source code, dependencies, build flags, compiler version. If the inputs are identical, the output is identical. Always. This is what &lt;em&gt;reproducible builds&lt;/em&gt; actually means. And because the entire build graph is explicit, cross-compilation becomes a first-class concern: you can target ARM from an x86 machine without custom Docker images or fragile toolchain scripts.&lt;/p&gt;

&lt;p&gt;For dev environments, &lt;code&gt;nix develop&lt;/code&gt; gives you a shell with an exact toolchain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="c"&gt;# flake.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:NixOS/nixpkgs/nixos-24.05"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt;
      &lt;span class="nv"&gt;forAllSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;genAttrs&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"x86_64-linux"&lt;/span&gt;
        &lt;span class="s2"&gt;"aarch64-linux"&lt;/span&gt;
        &lt;span class="s2"&gt;"aarch64-darwin"&lt;/span&gt;  &lt;span class="c"&gt;# M1/M2 Mac&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;devShells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;forAllSystems&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;legacyPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkShell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cmake&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ninja&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gcc13&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;python311&lt;/span&gt;
            &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              echo "toolchain: $(cmake --version | head -1)"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&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;p&gt;&lt;code&gt;flake.lock&lt;/code&gt; pins the exact nixpkgs revision. A colleague on macOS, a CI runner on Linux, and you on your laptop all get the exact same &lt;code&gt;cmake&lt;/code&gt; binary -- same source, same build flags, same hash. Not "the same version" -- the same build. That includes &lt;code&gt;clang-format&lt;/code&gt;: it ships with &lt;code&gt;clang-tools&lt;/code&gt; and is pinned alongside everything else. No separate install, no "which version did you format with?" conversations.&lt;/p&gt;

&lt;p&gt;No global installs. No version managers. &lt;code&gt;nix develop&lt;/code&gt; drops you into the environment, &lt;code&gt;exit&lt;/code&gt; leaves it. Your system stays clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Combine with direnv and it gets even better.&lt;/strong&gt; Add a &lt;code&gt;.envrc&lt;/code&gt; with a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;use flake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;cd&lt;/code&gt; into the project directory and the environment activates automatically. Leave the directory, it's gone. No manual &lt;code&gt;nix develop&lt;/code&gt;, no global state, no version managers fighting each other. Your shell just has the right tools when you need them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same principle applies to your C++ library dependencies.&lt;/strong&gt; Anything you'd normally pull in via CMake's &lt;code&gt;FetchContent&lt;/code&gt; can go into the devShell instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The FetchContent approach: version string lives in CMakeLists.txt,&lt;/span&gt;
&lt;span class="c1"&gt;# downloads happen at configure time, requires internet access on every fresh build&lt;/span&gt;
&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;FetchContent&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;FetchContent_Declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;fmt
  GIT_REPOSITORY https://github.com/fmtlib/fmt
  GIT_TAG        10.2.1
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;FetchContent_MakeAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;fmt&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Nix, you add the library to &lt;code&gt;packages&lt;/code&gt; like any other tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cmake&lt;/span&gt;
  &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ninja&lt;/span&gt;
  &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;gcc13&lt;/span&gt;
  &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fmt&lt;/span&gt;  &lt;span class="c"&gt;# fmt -- same pin, same hash, same binary everywhere&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in CMakeLists.txt you use standard system package discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;fmt REQUIRED&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;target_link_libraries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;myapp PRIVATE fmt::fmt&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library is already in the Nix store when CMake runs -- no build-time downloads, no internet access required. The version is pinned in &lt;code&gt;flake.lock&lt;/code&gt; alongside gcc and cmake. One lock file. One place to update. No version strings scattered across &lt;code&gt;CMakeLists.txt&lt;/code&gt; files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full example (including fmt dependency): &lt;a href="https://github.com/charemma/blog/tree/main/nix/devshell-cmake" rel="noopener noreferrer"&gt;github.com/charemma/blog/tree/main/nix/devshell-cmake&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Docker&lt;/th&gt;
&lt;th&gt;Nix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reproducibility&lt;/td&gt;
&lt;td&gt;Image digest is reproducible; Dockerfile build is not&lt;/td&gt;
&lt;td&gt;Content-addressed; build graph is fully deterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev environment&lt;/td&gt;
&lt;td&gt;Container with volume mounts&lt;/td&gt;
&lt;td&gt;Native shell, no overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS / Windows&lt;/td&gt;
&lt;td&gt;VM overhead, slow volume mounts, WSL2 I/O issues&lt;/td&gt;
&lt;td&gt;Native, no VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI integration&lt;/td&gt;
&lt;td&gt;Native, widely supported&lt;/td&gt;
&lt;td&gt;Requires setup; but identical to local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Low-medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial adoption&lt;/td&gt;
&lt;td&gt;All-or-nothing per container&lt;/td&gt;
&lt;td&gt;Per-project devShell, gradual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary caching&lt;/td&gt;
&lt;td&gt;Registry-based&lt;/td&gt;
&lt;td&gt;Nix substituters (cachix, self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package availability&lt;/td&gt;
&lt;td&gt;Anything you can &lt;code&gt;apt-get&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;nixpkgs is large but has gaps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Where Nix is Stronger
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Toolchain-heavy projects&lt;/strong&gt;: C++, embedded, cross-compilation. When your build depends on a specific GCC version, exact sysroot, and a custom linker script, Nix handles this cleanly. Cross-compilation is where Nix really shines. You declare host and target -- Nix handles the rest. Docker can technically do it too, but cross-compilation setups in containers are notoriously fragile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-platform consistency&lt;/strong&gt;: the same &lt;code&gt;devShell&lt;/code&gt; works on macOS and Linux. No "but it works in the container" conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI parity&lt;/strong&gt;: the gap between local and CI closes because they run the same command against the same environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# locally&lt;/span&gt;
nix develop
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# GitHub Actions&lt;/span&gt;
- name: Build
  run: nix develop &lt;span class="nt"&gt;--command&lt;/span&gt; cmake &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No "install dependencies" step in CI that diverges from what you ran locally. Same flake, same lock file, same result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental adoption&lt;/strong&gt;: you don't need to Nix-ify your entire infrastructure (though once you've been running NixOS for a while, you'll want to). A &lt;code&gt;flake.nix&lt;/code&gt; in a repo gives everyone a consistent dev environment without touching your deployment pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Nix Falls Short
&lt;/h2&gt;

&lt;p&gt;This section matters more than the strengths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The learning curve is real.&lt;/strong&gt; Nix the language is functional, lazy, and unlike anything most engineers have written. Error messages are often cryptic. The mental model takes weeks to build, not hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation is fragmented.&lt;/strong&gt; The official docs are improving but still incomplete in places. A lot of knowledge lives in GitHub issues, forum posts, and other people's flakes. You will spend time reading source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flakes are "experimental."&lt;/strong&gt; Pragmatically, everyone uses them and they're stable enough. But the official experimental label is real friction in corporate environments where "experimental" means "not approved."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold builds are slow.&lt;/strong&gt; On a cache miss, Nix builds from source. For packages with heavy build times (LLVM, Qt, anything Rust-based) this can be painful. Cachix or a self-hosted binary cache solves this, but it's infrastructure you need to set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some tooling doesn't play well.&lt;/strong&gt; JetBrains IDEs, some commercial SDKs, proprietary build tools -- Nix support is incomplete or requires workarounds. If your stack is heavily JetBrains-based, expect friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard to justify organizationally.&lt;/strong&gt; "We're switching to a functional package manager written in a language nobody knows" is a tough pitch to a team already stretched thin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The reproducibility story is weaker for JavaScript and Python projects.&lt;/strong&gt; Nix pins the interpreter -- Node 20, Python 3.11 -- but your actual application dependencies still live in &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;poetry.lock&lt;/code&gt;. Those lock files already solve the version pinning problem for the packages that matter most. If you're already committing them, Nix adds little beyond what &lt;code&gt;.nvmrc&lt;/code&gt; or &lt;code&gt;pyenv&lt;/code&gt; already gives you. The full reproducibility story that makes Nix compelling for C++ and embedded -- where the entire relevant stack lives in nixpkgs -- doesn't translate cleanly to ecosystems with their own mature package managers.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reach for Docker when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need runtime isolation for services (this is Docker's home turf)&lt;/li&gt;
&lt;li&gt;Your CI/CD is Kubernetes-native and you're building images anyway&lt;/li&gt;
&lt;li&gt;The team has no appetite for learning new tooling&lt;/li&gt;
&lt;li&gt;You need to distribute a pre-built environment to non-engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reach for Nix when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a complex toolchain where exact versions matter (C++, embedded, cross-compilation -- and if you're building an embedded OS, Nix can replace Yocto entirely. But that's a story for another post.)&lt;/li&gt;
&lt;li&gt;You've been burned enough times by local/CI divergence&lt;/li&gt;
&lt;li&gt;macOS or Windows developers on the team are tired of Docker Desktop / WSL2 performance&lt;/li&gt;
&lt;li&gt;You want onboarding to be &lt;code&gt;nix develop&lt;/code&gt; and nothing else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The common pattern that actually works:&lt;/strong&gt;&lt;br&gt;
Use Nix for the &lt;em&gt;dev environment&lt;/em&gt; (the toolchain, the build tools, the local shell) and Docker for &lt;em&gt;runtime&lt;/em&gt; (building and running the actual service images). They're not competing -- they solve different layers of the same problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Docker solved distribution. It didn't solve reproducibility -- it just moved the non-determinism from your local machine to a Dockerfile that nobody updates.&lt;/p&gt;

&lt;p&gt;Nix solves reproducibility. It doesn't replace Docker for what Docker is actually good at.&lt;/p&gt;

&lt;p&gt;The question isn't "Docker or Nix." It's: where is your actual pain? If your team is fighting with environment drift, onboarding friction, or local/CI divergence -- that's a reproducibility problem and Nix addresses it directly. If you need consistent runtime packaging for services running in Kubernetes, that's Docker's job and it's good at it.&lt;/p&gt;

&lt;p&gt;Use boring tools for boring problems. Reach for Nix when the boring tools have stopped working.&lt;/p&gt;




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

&lt;p&gt;Docker teaches you to think in environments.&lt;/p&gt;

&lt;p&gt;Nix forces you to think in inputs and outputs.&lt;/p&gt;

&lt;p&gt;Once you see that difference, it's hard to unsee it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're dealing with similar problems, I'd be curious how you're solving them -- drop a comment below.&lt;/em&gt;&lt;br&gt;
It's Monday morning. A new engineer joins the team. By Thursday, they're still fighting their dev environment. The Dockerfile the previous engineer wrote pulls from ￼￼ubuntu:22.04￼￼, runs ￼￼apt-get install￼￼ without pinned versions, and "works on the CI runner." On the new engineer's M2 Mac it builds fine but the binary crashes at runtime because they're running a different glibc. You've seen this. We all have.&lt;/p&gt;

&lt;p&gt;The thing is: Docker was supposed to fix this. It didn't. It just moved the problem. Not in production -- in development environments.&lt;/p&gt;

&lt;p&gt;￼￼￼The Problem with Dev Environments&lt;/p&gt;

&lt;p&gt;Dev environments fail in predictable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;￼￼Dependency drift￼￼: your local toolchain diverges from CI over weeks. Nobody notices until a build breaks in production but passes locally.&lt;/li&gt;
&lt;li&gt;￼￼Version manager sprawl￼￼: ￼￼nvm￼￼, ￼￼pyenv￼￼, ￼￼rbenv￼￼, ￼￼sdkman￼￼ -- one per language, none of them talk to each other, all of them break on OS upgrades.&lt;/li&gt;
&lt;li&gt;￼￼Platform gaps￼￼: macOS developers running Linux containers via a VM, dealing with volume mount performance issues and file permission mismatches.&lt;/li&gt;
&lt;li&gt;￼￼Onboarding friction￼￼: a ￼￼README.md￼￼ with fifteen manual steps that's six months out of date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying issue is that most tooling in this space solves ￼￼isolation￼￼ but not ￼￼reproducibility￼￼. Those are different problems.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>nix</category>
      <category>docker</category>
      <category>reproducibility</category>
    </item>
  </channel>
</rss>
