<?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: Alisson Rosa</title>
    <description>The latest articles on DEV Community by Alisson Rosa (@neochaotic).</description>
    <link>https://dev.to/neochaotic</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%2F3966804%2F90ddac9a-4a01-4e29-a602-652f4b934c50.jpg</url>
      <title>DEV Community: Alisson Rosa</title>
      <link>https://dev.to/neochaotic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neochaotic"/>
    <language>en</language>
    <item>
      <title>One command from your laptop to Kubernetes — no CI pipeline</title>
      <dc:creator>Alisson Rosa</dc:creator>
      <pubDate>Sun, 07 Jun 2026 03:22:29 +0000</pubDate>
      <link>https://dev.to/neochaotic/one-command-from-your-laptop-to-kubernetes-no-ci-pipeline-2d37</link>
      <guid>https://dev.to/neochaotic/one-command-from-your-laptop-to-kubernetes-no-ci-pipeline-2d37</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Leoflow &lt;code&gt;v0.0.2&lt;/code&gt; just shipped, and the headline is one verb: &lt;strong&gt;&lt;code&gt;leoflow deploy&lt;/code&gt;&lt;/strong&gt;. It takes a DAG from &lt;code&gt;dag.py&lt;/code&gt; on your laptop to a running pod on a Kubernetes control plane in a single command — compile → build → push to your registry → &lt;strong&gt;pin the image by digest&lt;/strong&gt; → register the artifact. No bespoke CI pipeline required, no hand-rolled &lt;code&gt;docker build &amp;amp;&amp;amp; docker push &amp;amp;&amp;amp; curl&lt;/code&gt;. The same command runs identically from your machine &lt;em&gt;or&lt;/em&gt; from a CI runner. Login is &lt;code&gt;leoflow auth login&lt;/code&gt; (token saved, password read hidden). GitHub: &lt;strong&gt;&lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The gap nobody talks about
&lt;/h2&gt;

&lt;p&gt;Last time we &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;rewrote Airflow's control plane in Go&lt;/a&gt; and the time before we &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;ran real provider hooks without installing Airflow&lt;/a&gt;. Both were about the &lt;em&gt;runtime&lt;/em&gt;. This one is about the part that quietly eats a week of every Airflow-adjacent project: &lt;strong&gt;getting a DAG you wrote into the thing that runs it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the old world, the inner loop on your laptop is delightful and the path to production is a cliff. You have a DAG that works. Now to ship it you need to: write a &lt;code&gt;Dockerfile&lt;/code&gt;, build the image for the &lt;em&gt;cluster's&lt;/em&gt; architecture (not your Mac's), authenticate to a registry, push, find the resulting digest, write a manifest or a &lt;code&gt;values.yaml&lt;/code&gt;, authenticate to the control plane, and register it — usually all wired into a CI pipeline you have to build and babysit first.&lt;/p&gt;

&lt;p&gt;That cliff is why "it works locally" and "it's in production" are two different teams in most shops. Leoflow &lt;code&gt;v0.0.2&lt;/code&gt; collapses the cliff into one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  What &lt;code&gt;leoflow deploy&lt;/code&gt; actually does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;leoflow auth login &lt;span class="nt"&gt;--server&lt;/span&gt; https://pro.example.com
&lt;span class="go"&gt;Username: admin
Password:
Logged in to https://pro.example.com (token saved to ~/.leoflow/config.yaml)

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;leoflow deploy &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;span class="gp"&gt;Compiled dag.py -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dag.json
&lt;span class="go"&gt;Building image (linux/amd64) …
Pushed registry.example.com/etl_sales:9f3a2c1
&lt;/span&gt;&lt;span class="gp"&gt;Deployed etl_sales -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://pro.example.com
&lt;span class="go"&gt;  image registry.example.com/etl_sales@sha256:3c90019f2ba4fd8a51bf35fdc9ff65cc0ba0f1f0e2ada1f38d186554ff5b88d6
  registered version 9f3a2c1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one &lt;code&gt;deploy&lt;/code&gt; ran the entire boundary-crossing sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A[dag.py + leoflow.yaml] --&amp;gt; B[compile → dag.json]
  B --&amp;gt; C[build DAG image&amp;lt;br/&amp;gt;FROM leoflow-runtime]
  C --&amp;gt; D[push image → your registry]
  D --&amp;gt; E[re-pin by digest&amp;lt;br/&amp;gt;+ register → control plane]
  E --&amp;gt; F[runs in a pod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compile&lt;/strong&gt; — parse &lt;code&gt;dag.py&lt;/code&gt;, overlay &lt;code&gt;leoflow.yaml&lt;/code&gt;, run the guardrails (unknown &lt;code&gt;task_id&lt;/code&gt;, unsupported operator, duplicate keys), emit &lt;code&gt;dag.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; — build the DAG image, &lt;strong&gt;cross-built for the cluster by default&lt;/strong&gt; (&lt;code&gt;linux/amd64&lt;/code&gt;), so building on an arm64 Mac doesn't produce an exec-format-error pod.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push&lt;/strong&gt; — push to &lt;em&gt;your&lt;/em&gt; registry (Docker Hub, GHCR, ECR, Artifact Registry, ACR, a private one — anywhere the cluster can pull from).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin by digest&lt;/strong&gt; — capture the registry digest and rewrite &lt;code&gt;dag.json&lt;/code&gt; to reference the image by &lt;code&gt;@sha256:…&lt;/code&gt;, not by a mutable tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register&lt;/strong&gt; — register the artifact with the control plane. Add &lt;code&gt;--trigger&lt;/code&gt; to fire a run immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing is documented in &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0041-leoflow-deploy-pipelineless.md" rel="noopener noreferrer"&gt;ADR 0041&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why digest pinning is the whole point
&lt;/h2&gt;

&lt;p&gt;Leoflow has one non-negotiable principle baked in since &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0003-dag-as-image.md" rel="noopener noreferrer"&gt;ADR 0003&lt;/a&gt;: &lt;strong&gt;a DAG is an immutable artifact&lt;/strong&gt; — a &lt;code&gt;dag.json&lt;/code&gt; plus a container image, versioned together, never mutated after compilation.&lt;/p&gt;

&lt;p&gt;A tag like &lt;code&gt;:latest&lt;/code&gt; or even &lt;code&gt;:9f3a2c1&lt;/code&gt; is a &lt;em&gt;pointer&lt;/em&gt;. Pointers move. The same tag can resolve to a different image tomorrow, and now your "reproducible" run isn't. So &lt;code&gt;deploy&lt;/code&gt; doesn't register the tag it pushed — it asks the registry for the &lt;strong&gt;content digest&lt;/strong&gt; of what landed and pins &lt;code&gt;dag.json&lt;/code&gt; to &lt;code&gt;image@sha256:…&lt;/code&gt;. The control plane stores that. Every pod the scheduler launches for that DAG version pulls &lt;em&gt;exactly those bytes&lt;/em&gt;, forever. Re-tag, overwrite, garbage-collect the tag — the deployed version is unaffected.&lt;/p&gt;

&lt;p&gt;This is the difference between "we deploy DAGs" and "we deploy &lt;em&gt;artifacts&lt;/em&gt;." &lt;code&gt;deploy&lt;/code&gt; makes the second one the default with zero extra effort from you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The same command in CI — because the CLI is runtime-independent
&lt;/h2&gt;

&lt;p&gt;Here's a design decision we're proud of: &lt;strong&gt;the &lt;code&gt;leoflow&lt;/code&gt; CLI doesn't need Lite.&lt;/strong&gt; &lt;code&gt;compile&lt;/code&gt;, &lt;code&gt;auth login&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, and &lt;code&gt;deploy&lt;/code&gt; all run standalone. So the laptop one-liner and the CI step are &lt;em&gt;literally the same command&lt;/em&gt;:&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="c1"&gt;# .github/workflows/deploy-dag.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;leoflow deploy ./dags/etl_sales --yes \&lt;/span&gt;
      &lt;span class="s"&gt;--server "$LEOFLOW_SERVER" --token "$LEOFLOW_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No "CI edition," no second code path that drifts from what you tested by hand. Token comes from a flag, &lt;code&gt;LEOFLOW_TOKEN&lt;/code&gt;, or the saved config — in that order. Registry auth is your builder's own &lt;code&gt;docker login&lt;/code&gt; (separate from the control-plane login on purpose: pushing an image and registering an artifact are two different trust boundaries).&lt;/p&gt;

&lt;p&gt;Scope it however you like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;leoflow deploy                 &lt;span class="c"&gt;# the project in the current directory&lt;/span&gt;
leoflow deploy ./dags/etl      &lt;span class="c"&gt;# a specific path&lt;/span&gt;
leoflow deploy etl_sales       &lt;span class="c"&gt;# by dag_id, from the workspace&lt;/span&gt;
leoflow deploy &lt;span class="nt"&gt;--all&lt;/span&gt;           &lt;span class="c"&gt;# every DAG in the workspace (best-effort)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What it deliberately does &lt;em&gt;not&lt;/em&gt; do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not ship your secrets.&lt;/strong&gt; The artifact carries no connections and no variables. If a task needs &lt;code&gt;sales_db&lt;/code&gt;, &lt;code&gt;deploy&lt;/code&gt; &lt;em&gt;surfaces&lt;/em&gt; that the connection is required — it never seeds a credential into an image or into the registry. Secrets live in the control plane, delivered to the pod at runtime via the standard &lt;code&gt;AIRFLOW_CONN_*&lt;/code&gt; seam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not link the Docker Go SDK.&lt;/strong&gt; &lt;code&gt;deploy&lt;/code&gt; &lt;em&gt;shells out&lt;/em&gt; to your builder (&lt;code&gt;docker&lt;/code&gt;, or &lt;code&gt;--builder podman&lt;/code&gt;/&lt;code&gt;nerdctl&lt;/code&gt;). That's an &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0015-kubernetes-only-execution.md" rel="noopener noreferrer"&gt;ADR 0015&lt;/a&gt; consequence: no 100-package Docker client tree welded into the binary, smaller supply-chain surface, &lt;code&gt;govulncheck&lt;/code&gt; stays clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It requires a registry, loudly.&lt;/strong&gt; A &lt;code&gt;deploy&lt;/code&gt; with no &lt;code&gt;registry:&lt;/code&gt; configured fails with the exact line to add, not a silent local-only build that surprises you when the cluster can't pull. (Lite needs no registry — this is a Pro concern.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Dockerfile-free build isn't here &lt;em&gt;yet&lt;/em&gt;.&lt;/strong&gt; Today the simple path uses a four-line &lt;code&gt;Dockerfile&lt;/code&gt; that layers your code &lt;code&gt;FROM ghcr.io/neochaotic/leoflow-runtime:py3.11&lt;/code&gt; — the published, signed task base. The richer flow where the build is &lt;em&gt;synthesized&lt;/em&gt; from &lt;code&gt;leoflow.yaml&lt;/code&gt; (&lt;code&gt;connectors:&lt;/code&gt; and all) lands with the connectors release (&lt;code&gt;v0.1.0&lt;/code&gt;). The &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/first-pro-dag.md" rel="noopener noreferrer"&gt;first-Pro-DAG walkthrough&lt;/a&gt; shows both, so you can see where it's going.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  We didn't just unit-test it
&lt;/h2&gt;

&lt;p&gt;The interesting part of &lt;code&gt;deploy&lt;/code&gt; is the part unit tests can't reach: does the cluster actually pull the digest-pinned image and run it? So there's an end-to-end test that stands up a real &lt;strong&gt;k3d&lt;/strong&gt; cluster with a real registry, runs &lt;code&gt;leoflow auth login&lt;/code&gt;, then a single &lt;code&gt;leoflow deploy&lt;/code&gt; that compiles → builds for the cluster arch → pushes → captures the digest → re-pins → registers, then triggers a run and &lt;strong&gt;asserts the task reaches &lt;code&gt;success&lt;/code&gt;&lt;/strong&gt; — i.e. the cluster pulled the digest-pinned image from the registry and ran it. It runs in CI on every release (&lt;code&gt;E2E deploy (k3d + registry)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Before cutting &lt;code&gt;v0.0.2&lt;/code&gt; final, I re-ran that same e2e against the &lt;strong&gt;published &lt;code&gt;v0.0.2-rc.2&lt;/code&gt; binaries&lt;/strong&gt; (downloaded from the release, checksum-verified, not a local build). Green: built, pushed, pinned by digest, and the cluster ran it. The only delta between rc.2 and the final tag was documentation — so what the e2e exercised is exactly what shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  What else is in &lt;code&gt;v0.0.2&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;v0.0.2&lt;/code&gt; is the "Pro-enablement" release — the smallest cut that makes the laptop→cluster path real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;leoflow deploy&lt;/code&gt;&lt;/strong&gt; + &lt;strong&gt;&lt;code&gt;leoflow auth login&lt;/code&gt;&lt;/strong&gt; (the headline).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Published, signed task base images&lt;/strong&gt; — &lt;code&gt;ghcr.io/neochaotic/leoflow-runtime:py3.10&lt;/code&gt; / &lt;code&gt;py3.11&lt;/code&gt; / &lt;code&gt;py3.12&lt;/code&gt;, multi-arch. Your DAG &lt;code&gt;Dockerfile&lt;/code&gt; is &lt;code&gt;FROM&lt;/code&gt; one of these; CI pulls a signed base instead of building one.&lt;/li&gt;
&lt;li&gt;The usual release discipline: binaries &lt;strong&gt;signed with cosign&lt;/strong&gt;, &lt;strong&gt;SBOMs&lt;/strong&gt; per platform, an &lt;strong&gt;install smoke across eight Linux distros&lt;/strong&gt;, and a &lt;strong&gt;previous→&lt;code&gt;v0.0.2&lt;/code&gt; upgrade smoke&lt;/strong&gt; — all green before the tag was promoted from &lt;code&gt;rc&lt;/code&gt; to final, per &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0037-release-version-scheme.md" rel="noopener noreferrer"&gt;ADR 0037&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install or upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/neochaotic/leoflow/main/install.sh | sh
leoflow version          &lt;span class="c"&gt;# leoflow 0.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The fastest honest test is the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/first-pro-dag.md" rel="noopener noreferrer"&gt;&lt;strong&gt;first Pro DAG walkthrough&lt;/strong&gt;&lt;/a&gt; — one DAG, one &lt;code&gt;deploy&lt;/code&gt;, a running pod, about ten minutes. You'll need a builder (&lt;code&gt;docker&lt;/code&gt;/&lt;code&gt;podman&lt;/code&gt;/&lt;code&gt;nerdctl&lt;/code&gt;), a registry the cluster can pull from, and a reachable control plane (a throwaway one comes from the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/helm-chart.md" rel="noopener noreferrer"&gt;Helm chart&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you just want to feel the inner loop with zero infrastructure, &lt;code&gt;leoflow lite&lt;/code&gt; still needs none of the above — no cluster, no registry, no deploy at all. Write a DAG, watch it hot-reload, then reach for &lt;code&gt;deploy&lt;/code&gt; the day you outgrow the laptop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Help shape the next milestone
&lt;/h2&gt;

&lt;p&gt;We keep the surface small on purpose — boring and reliable beats broad and flaky for a thing that has to run at 5 AM. The connectors + operators epic (&lt;code&gt;v0.1.0&lt;/code&gt;) is what unlocks the Dockerfile-free &lt;code&gt;deploy&lt;/code&gt; and the full provider catalog; it's in flight.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Star the repo&lt;/strong&gt; — &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;leoflow deploy&lt;/code&gt;&lt;/strong&gt; against a real cluster and tell us where it bit you. Pre-1.0 is &lt;em&gt;the&lt;/em&gt; time to shape this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open an issue&lt;/strong&gt; with the Airflow deploy ceremony you most want gone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt; — &lt;code&gt;v0.0.2&lt;/code&gt; release notes at &lt;a href="https://github.com/neochaotic/leoflow/releases/tag/v0.0.2" rel="noopener noreferrer"&gt;/releases/tag/v0.0.2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apache 2.0. Thanks for reading — tell us what hurts.&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>go</category>
      <category>kubernetes</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>We rewrote Apache Airflow's control plane in Go (and kept the UI)</title>
      <dc:creator>Alisson Rosa</dc:creator>
      <pubDate>Wed, 03 Jun 2026 18:23:45 +0000</pubDate>
      <link>https://dev.to/neochaotic/we-rewrote-apache-airflows-control-plane-in-go-and-kept-the-ui-42l6</link>
      <guid>https://dev.to/neochaotic/we-rewrote-apache-airflows-control-plane-in-go-and-kept-the-ui-42l6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Leoflow &lt;code&gt;v0.0.1&lt;/code&gt; just shipped. It speaks the Airflow API, runs the Airflow 3.2.x UI &lt;strong&gt;unmodified&lt;/strong&gt;, but replaces the Python control plane with Go. Pod-per-task is the only execution mode. Each DAG is its own container image. Fan-in (map-reduce) is a Python list comprehension. Install: &lt;code&gt;curl -fsSL https://raw.githubusercontent.com/neochaotic/leoflow/main/install.sh | sh&lt;/code&gt;. GitHub: &lt;strong&gt;&lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt; — stars and issues warmly accepted.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The 3 AM pager
&lt;/h2&gt;

&lt;p&gt;You know the one. The scheduler stalled again. Or the triggerer suffocated under 500 sensors. Or a worker leaked file descriptors until Kubernetes OOMKilled it mid-run. Or someone bumped &lt;code&gt;pandas&lt;/code&gt; for the new DAG and broke six legacy ones because they all share the same image.&lt;/p&gt;

&lt;p&gt;Apache Airflow is the most widely deployed workflow orchestrator on earth. It is also the one that bleeds the most in production. None of those wounds are bugs — they are &lt;strong&gt;structural consequences of running orchestration through a Python control plane&lt;/strong&gt;. You cannot patch the GIL. You cannot make &lt;code&gt;DagBag&lt;/code&gt; reparse cheap. You cannot make Celery workers ephemeral without rewriting them.&lt;/p&gt;

&lt;p&gt;So we did the only thing left: we kept everything Airflow got right and replaced everything that bleeds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "kept" means
&lt;/h2&gt;

&lt;p&gt;We did not invent a new model. Airflow's &lt;code&gt;KubernetesExecutor&lt;/code&gt; proved years ago that &lt;strong&gt;pod-per-task is correct&lt;/strong&gt;: each task gets its own container, its own resources, its own lifecycle. You can't leak a process that exits.&lt;/p&gt;

&lt;p&gt;We also did not invent a new UI. The Airflow 3.2.x React SPA ships embedded inside the Leoflow server binary. Your team's muscle memory survives the migration.&lt;/p&gt;

&lt;p&gt;What we kept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pod-per-task execution model&lt;/li&gt;
&lt;li&gt;The Airflow 3.2.x UI (literally the same React build, served from &lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The HTTP API shape (&lt;code&gt;/api/v2/dags/...&lt;/code&gt;, &lt;code&gt;/api/v2/dagRuns/...&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;The vocabulary: DAG, TaskInstance, DagRun, XCom, Trigger Rules&lt;/li&gt;
&lt;li&gt;The DAG-authoring dialect — &lt;code&gt;from airflow.sdk import DAG, task&lt;/code&gt;, TaskFlow, classic operators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we threw out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Python scheduler. It's Go now.&lt;/li&gt;
&lt;li&gt;The Python triggerer. Sensors are 2 KB goroutines.&lt;/li&gt;
&lt;li&gt;The shared &lt;code&gt;/dags&lt;/code&gt; folder. Each DAG is its own immutable container image.&lt;/li&gt;
&lt;li&gt;The "long-lived Celery worker" model. Every task is an ephemeral pod.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What it looks like to write a DAG
&lt;/h2&gt;

&lt;p&gt;Two files. That's the whole project.&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="c1"&gt;# leoflow.yaml — your deploy concerns&lt;/span&gt;
&lt;span class="na"&gt;dag_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;etl_sales&lt;/span&gt;
&lt;span class="na"&gt;python_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pandas==2.1.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;requests==2.31.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 python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dag.py — your DAG, in real Airflow SDK 3.2.x
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;etl_sales&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 5 * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catchup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&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 shell"&gt;&lt;code&gt;leoflow compile &lt;span class="nb"&gt;.&lt;/span&gt;              &lt;span class="c"&gt;# generates Dockerfile, builds image, emits dag.json&lt;/span&gt;
leoflow push ./dag.json        &lt;span class="c"&gt;# registers a new versioned DAG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Dockerfile&lt;/code&gt;. No &lt;code&gt;requirements.txt&lt;/code&gt;. No &lt;code&gt;Helm values.yaml&lt;/code&gt; for this DAG. No &lt;code&gt;pyproject.toml&lt;/code&gt;. The compiler reads &lt;code&gt;leoflow.yaml&lt;/code&gt;, generates a Dockerfile against the official base image (&lt;code&gt;leoflow/python-runtime:3.11&lt;/code&gt;), builds, pushes to your registry, and registers a versioned &lt;code&gt;dag.json&lt;/code&gt; with the control plane. That's the whole inner loop.&lt;/p&gt;

&lt;p&gt;For local development, &lt;code&gt;leoflow lite&lt;/code&gt; provisions a managed Postgres, hot-reloads on every save, gives each DAG its own per-DAG virtualenv at &lt;code&gt;~/.leoflow/dev/venvs/&amp;lt;dag_id&amp;gt;/&lt;/code&gt;, and &lt;strong&gt;auto-detects &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt; on &lt;code&gt;PATH&lt;/code&gt;&lt;/strong&gt; for 5–10× faster cold installs. Two DAGs that pin conflicting versions of the same package coexist without interference. This is the bit that made me file the issue against Airflow for the first time, ten years ago. We finally have it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Map-reduce, as a Python list comprehension
&lt;/h2&gt;

&lt;p&gt;Hyperparameter search. K-fold cross-validation. Ensemble training. Monte Carlo. Every parallel ML workload is &lt;strong&gt;map-reduce&lt;/strong&gt;. Most orchestrators make you build it: an operator per fan-out, a broker for the intermediate values, shared storage for the artifacts, a custom reducer that knows how to find them all.&lt;/p&gt;

&lt;p&gt;Leoflow expresses the whole pattern in &lt;strong&gt;two lines of Python&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;train_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                            &lt;span class="c1"&gt;# map
&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;select_best&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;    &lt;span class="c1"&gt;# reduce
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hparam_search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;select_best&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;trial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;[trial(lr) for lr in …]&lt;/code&gt; is the whole map. &lt;code&gt;trials: list[dict]&lt;/code&gt; is the whole reduce. &lt;strong&gt;No XCom plumbing, no broker, no shared filesystem, no special operator.&lt;/strong&gt; The parser captures the list shape at compile time; the runtime assembles upstream XComs in declaration order and delivers them as a real Python list. Per-trial isolation (own pod, own process, own venv if you want). Per-trial retry. Deterministic ordering. A 256 KB cap per upstream value. A &lt;code&gt;null&lt;/code&gt; slot for any upstream that legitimately produced no result.&lt;/p&gt;

&lt;p&gt;If you have ever written a Celery chord by hand, take a moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                          Author / CI                             │
│  leoflow.yaml  +  dag.py  +  (auto-generated) Dockerfile         │
└───────────────────────────────┬─────────────────────────────────┘
                                │  leoflow compile / push
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Control plane — Go                           │
│ ┌───────────────────────────────────────────────────────────┐   │
│ │ HTTP API  /api/v2  ·  JWT · RBAC · multi-tenant           │   │
│ ├───────────────────────────────────────────────────────────┤   │
│ │ Scheduler   ·  state machine · cron · catchup             │   │
│ │             ·  PG-advisory-lock leader election           │   │
│ │             ·  retries with backoff                       │   │
│ ├───────────────────────────────────────────────────────────┤   │
│ │ Agent gRPC service  ·  task spec · state · XCom · logs     │   │
│ └───────────────────────────────────────────────────────────┘   │
│       │                                  │                       │
│       │ Postgres (metadata)              │ Redis (XCom + log)    │
└───────┼──────────────────────────────────┼──────────────────────┘
        │                                  │
        │     dispatch: one pod per task   │
        ▼                                  │
┌───────────────────────────────────────┐ │
│              Kubernetes               │ │
│  ┌─────────────────────────────────┐  │ │
│  │  Worker pod = your DAG image    │  │ │
│  │  leoflow-agent (15 MB Go bin)   │  │ │
│  │     ⇅ gRPC                      │  │ │
│  │  your Python / Bash code        │──┼─┘
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-lived &lt;code&gt;http_api&lt;/code&gt; tasks skip the pod and run inline as goroutines (capped). Everything else runs &lt;strong&gt;pod-per-task&lt;/strong&gt;, every time. Concurrency is goroutines and pods — no Celery, no triggerer process, no shared worker pool.&lt;/p&gt;

&lt;p&gt;A few specifics worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leader election&lt;/strong&gt; is a Postgres advisory lock. No external coordinator. No ZooKeeper, no etcd, no Raft library. It is the kind of decision you can explain to a new hire in 30 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XCom&lt;/strong&gt; lives in &lt;strong&gt;Postgres on Lite&lt;/strong&gt; (small, no Redis required for laptop dev) and &lt;strong&gt;Redis on Pro&lt;/strong&gt;. 256 KB cap, optional schema validation, last-write-wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connections&lt;/strong&gt; are encrypted at rest with AES-256-GCM and delivered to tasks via Airflow's standard &lt;code&gt;AIRFLOW_CONN_&amp;lt;ID&amp;gt;&lt;/code&gt; env var. Postgres / MySQL / SQLite / MSSQL / Redis / HTTP / GCS connectors ship with chain-of-custody-tested integration tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent&lt;/strong&gt; is a static Go binary, ~15 MB. PID 1 of the task pod. Talks gRPC back to the control plane. Forks one process per task. Does not buffer Python output (&lt;code&gt;-u&lt;/code&gt; plus &lt;code&gt;PYTHONUNBUFFERED=1&lt;/code&gt;), because watching a SIGKILL race steal half of the user's &lt;code&gt;print()&lt;/code&gt; output is its own kind of torment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The numbers (the only honest part of any orchestration README)
&lt;/h2&gt;

&lt;p&gt;We are not going to claim "1000× faster" because nobody who has run real pipelines believes you. Here is what falls out of replacing the control plane:&lt;/p&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;Airflow today&lt;/th&gt;
&lt;th&gt;Leoflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler decision latency&lt;/td&gt;
&lt;td&gt;3–10 s per task&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;lt;200 ms&lt;/strong&gt; — native Go, no GIL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensor concurrency&lt;/td&gt;
&lt;td&gt;~500 (asyncio Triggerer)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100,000+&lt;/strong&gt; — each sensor is a 2 KB goroutine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAG parsing cost&lt;/td&gt;
&lt;td&gt;Re-parsed every scheduler loop&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zero&lt;/strong&gt; — &lt;code&gt;dag.json&lt;/code&gt; is precompiled, immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker lifecycle&lt;/td&gt;
&lt;td&gt;Long-lived, leak-prone&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Ephemeral pod per task&lt;/strong&gt; — spawn, run, die&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker image size&lt;/td&gt;
&lt;td&gt;1.5 GB+ Airflow base&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~200 MB typical&lt;/strong&gt; — each DAG is its own slim image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency isolation&lt;/td&gt;
&lt;td&gt;Workaround via &lt;code&gt;KubernetesPodOperator&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Native&lt;/strong&gt; — every DAG is a container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;15–45 s&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2–5 s target&lt;/strong&gt; — agent is a 15 MB static binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Retrofitted with effort&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Native&lt;/strong&gt; — Prometheus + OpenTelemetry + structured logs from commit one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the structural wins. The marketing-grade "X× faster" depends on your DAG. The scheduler latency drop is universal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it is not (because we have all read those launch posts)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not v1.0.&lt;/strong&gt; Per &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0037-release-version-scheme.md" rel="noopener noreferrer"&gt;ADR 0037&lt;/a&gt;, &lt;code&gt;v0.0.1&lt;/code&gt; ends the pre-alpha series; every release after is &lt;code&gt;vX.Y.Z-rc.N → vX.Y.Z&lt;/code&gt;. The HTTP API, CLI surface, and Helm values may change between minor versions until &lt;code&gt;v1.0.0&lt;/code&gt; locks them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI is still Airflow 3.2.x.&lt;/strong&gt; It is a tactical choice (your team's muscle memory). A purpose-built Leoflow UI is on the roadmap (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0018-airflow-ui-as-mvp.md" rel="noopener noreferrer"&gt;ADR 0018&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro is Kubernetes-only.&lt;/strong&gt; Lite runs anywhere. Pro means a real cluster, external Postgres + Redis, the Helm chart. There is deliberately no Docker-Compose "Pro" path; we explained why in &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0015-kubernetes-only-execution.md" rel="noopener noreferrer"&gt;ADR 0015&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a drop-in for every Airflow plugin.&lt;/strong&gt; The Airflow operator catalog has 30+ years of accreted Python; we ship a closed set (&lt;code&gt;python&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;http_api&lt;/code&gt;) plus first-party connectors. ADR 0036 defines a runtime shim for &lt;code&gt;from airflow.providers.&amp;lt;X&amp;gt;.hooks.&amp;lt;Y&amp;gt; import &amp;lt;Z&amp;gt;Hook&lt;/code&gt; so the common cases keep working — but if your DAG depends on three obscure providers we have not vendored, you will hit a wall today. File an issue; we are gating the next batch by demand.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lite: the zero-deploy path
&lt;/h2&gt;

&lt;p&gt;Here is the part that surprises people. To run Leoflow on your laptop you do &lt;strong&gt;not&lt;/strong&gt; need a Kubernetes cluster. You do not need Docker. You do not need a container registry, a &lt;code&gt;compile&lt;/code&gt;, a &lt;code&gt;push&lt;/code&gt;, or a single line of deploy YAML. You need one shell command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/neochaotic/leoflow/main/install.sh | sh
leoflow lite                &lt;span class="c"&gt;# → http://localhost:8088 (LITE badge, top-center)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer is a single shell script: three static Go binaries into &lt;code&gt;~/.leoflow/bin&lt;/code&gt;, then &lt;code&gt;leoflow setup&lt;/code&gt; provisions a &lt;strong&gt;managed CPython&lt;/strong&gt;, the parser, and a &lt;strong&gt;managed local Postgres&lt;/strong&gt; — nothing touches your system Python or your global packages. Then &lt;code&gt;leoflow lite&lt;/code&gt; boots a full control plane (scheduler, API, UI) against that managed Postgres. No system services, no Compose file, no cluster. Close the terminal and it's gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  There is no "dags/" folder — there is &lt;em&gt;your&lt;/em&gt; folder
&lt;/h3&gt;

&lt;p&gt;This trips up everyone coming from Airflow, so let's be explicit. Leoflow has &lt;strong&gt;no magic &lt;code&gt;dags/&lt;/code&gt; directory&lt;/strong&gt;. During &lt;code&gt;leoflow setup&lt;/code&gt; you pick a &lt;strong&gt;workspace folder&lt;/strong&gt; (default &lt;code&gt;~/leoflow&lt;/code&gt;) — that folder &lt;em&gt;is&lt;/em&gt; the runtime. Every subdirectory that contains a &lt;code&gt;leoflow.yaml&lt;/code&gt; is a DAG project; the watcher scans them and hot-reloads on save. Your tree looks like what you'd actually keep in git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/leoflow/                     ← the workspace you chose at install
├── etl_sales/
│   ├── leoflow.yaml           ← makes this folder a DAG
│   └── dag.py
└── hparam_search/
    ├── leoflow.yaml
    └── dag.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No central registry file, no &lt;code&gt;dag_folder&lt;/code&gt; setting to fight, no "why isn't my DAG showing up." A folder with a &lt;code&gt;leoflow.yaml&lt;/code&gt; is a DAG. That's the whole rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit DAGs from the browser — and get examples in one click
&lt;/h3&gt;

&lt;p&gt;Lite ships a small &lt;strong&gt;embedded web editor&lt;/strong&gt; so you can go from install to a running DAG without leaving the browser. Click the &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt; &lt;strong&gt;IDE&lt;/strong&gt; button (bottom-right of the UI) and you get a real &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt; editor — the engine behind VS Code — scoped to your workspace:&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.amazonaws.com%2Fuploads%2Farticles%2Fe9861fdl44pnim3dp154.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.amazonaws.com%2Fuploads%2Farticles%2Fe9861fdl44pnim3dp154.png" alt="The Leoflow Lite web editor: a file tree on the left with leoflow.yaml and a dag.py open, Python syntax highlighting, and Download examples / New file / Save buttons" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python + YAML syntax highlighting&lt;/strong&gt;, a workspace file tree, open/save (&lt;strong&gt;⌘S&lt;/strong&gt;), create/rename/delete with a "create target" chip that always tells you where a new file will land, collapse/expand carets that remember their state, and a recursive folder delete that says so out loud before it nukes a tree.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;"Download examples"&lt;/strong&gt; button in the header. Click it and Leoflow materializes the bundled example DAGs straight into your workspace — fan-out/aggregate, Monte Carlo π, an HTTP-load DAG, a daily-sales ETL — so you have real, runnable DAGs in the UI in seconds instead of staring at an empty home screen.&lt;/li&gt;
&lt;li&gt;Every save hits disk, the watcher picks it up, and the DAG &lt;strong&gt;hot-reloads&lt;/strong&gt;. (One gotcha: the Airflow tab doesn't auto-refresh DAG &lt;em&gt;structure&lt;/em&gt; — reload it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is deliberately &lt;em&gt;not&lt;/em&gt; a full IDE — no extensions, no terminal, no debugger. For those, point your own editor at the same workspace folder; it's just files on disk. The editor is a Lite convenience and is never registered in Pro.&lt;/p&gt;

&lt;p&gt;Recover the admin password any time with &lt;code&gt;leoflow lite reset-password&lt;/code&gt;. The &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/lite-web-editor.md" rel="noopener noreferrer"&gt;Lite web-editor guide&lt;/a&gt; and the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/dev-workflow.md" rel="noopener noreferrer"&gt;Lite cookbook&lt;/a&gt; cover the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pro: when you outgrow the laptop
&lt;/h2&gt;

&lt;p&gt;For production, the Helm chart deploys against external Postgres 13+ and Redis 6+ (Cloud SQL / RDS / Memorystore / ElastiCache / Azure Cache all work):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace leoflow
helm &lt;span class="nb"&gt;install &lt;/span&gt;lf oci://ghcr.io/neochaotic/leoflow &lt;span class="nt"&gt;-n&lt;/span&gt; leoflow &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; v0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; database.url&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'postgres://USER:PASS@HOST:5432/leoflow?sslmode=verify-full'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; redis.url&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'rediss://HOST:6380/0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; auth.jwtSecret&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 64&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;secretKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/helm-chart.md" rel="noopener noreferrer"&gt;chart docs&lt;/a&gt; for every value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering discipline (the stuff you would actually want to know)
&lt;/h2&gt;

&lt;p&gt;If you are evaluating Leoflow as a load-bearing piece of your data platform, the answers are in the repo, but the short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict TDD.&lt;/strong&gt; Every line of production code is preceded by a failing test (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0011-tdd-strict.md" rel="noopener noreferrer"&gt;ADR 0011&lt;/a&gt;). Per-package coverage floors enforced in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go Report Card A+&lt;/strong&gt; as the quality floor (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0012-code-quality-standards.md" rel="noopener noreferrer"&gt;ADR 0012&lt;/a&gt;). &lt;code&gt;gocyclo ≤ 15&lt;/code&gt; per function. GoDocs on every exported identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain from commit one.&lt;/strong&gt; &lt;code&gt;govulncheck&lt;/code&gt;, &lt;code&gt;gosec&lt;/code&gt;, &lt;code&gt;Trivy&lt;/code&gt;, &lt;code&gt;CodeQL&lt;/code&gt;, &lt;code&gt;gitleaks&lt;/code&gt;, OpenSSF Scorecard, OpenSSF Best Practices badge. Releases signed with &lt;code&gt;cosign&lt;/code&gt;. SBOMs published per platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every architectural decision is an ADR.&lt;/strong&gt; 37 of them at the time of writing. They are the single best place to start if you want to understand the &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo also runs an end-to-end install smoke against &lt;strong&gt;seven Linux distros&lt;/strong&gt; (&lt;code&gt;ubuntu:24.04&lt;/code&gt;, &lt;code&gt;debian:12&lt;/code&gt;, &lt;code&gt;fedora:41&lt;/code&gt;, &lt;code&gt;alpine:3.20&lt;/code&gt;, &lt;code&gt;opensuse/leap:15&lt;/code&gt;, &lt;code&gt;archlinux:latest&lt;/code&gt;, &lt;code&gt;rockylinux:9&lt;/code&gt;) on every release, plus a &lt;code&gt;prealpha.N → v0.0.1&lt;/code&gt; upgrade smoke. The &lt;code&gt;v0.0.1&lt;/code&gt; release got the green light because every one of those passed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Help us ship the next milestone
&lt;/h2&gt;

&lt;p&gt;We are at &lt;code&gt;v0.0.1&lt;/code&gt;. The next steps are visible — pick any:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Star the repo&lt;/strong&gt; at &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;. It is the cheapest way to tell us this matters to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open an issue&lt;/strong&gt; with a chronic Airflow pain we have not closed yet. Pre-1.0 is &lt;em&gt;the&lt;/em&gt; time to shape the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File a PR.&lt;/strong&gt; Strict TDD applies. The &lt;a href="https://github.com/neochaotic/leoflow/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING guide&lt;/a&gt; is the entry point. We review fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the Lite quick start&lt;/strong&gt; (60 seconds, above). If something does not work, that is an issue worth filing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the Helm chart&lt;/strong&gt; against a real cluster and tell us what bit you. Pro alpha needs real-world miles before it gets a Pro &lt;code&gt;v1.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What is on the post-&lt;code&gt;v0.0.1&lt;/code&gt; table
&lt;/h2&gt;

&lt;p&gt;Visible work toward &lt;code&gt;v0.1.0&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimized backfill (parallel execution with throttling)&lt;/li&gt;
&lt;li&gt;UI scaling for 10,000+ DAGs (caching, server-side pagination)&lt;/li&gt;
&lt;li&gt;Dynamic task mapping&lt;/li&gt;
&lt;li&gt;OIDC authentication (Google, Azure AD, Keycloak, Okta)&lt;/li&gt;
&lt;li&gt;Deferrable tasks (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0016-deferrable-tasks.md" rel="noopener noreferrer"&gt;ADR 0016&lt;/a&gt;) — efficient dispatch + long-poll pattern, native Go, &lt;strong&gt;no separate Triggerer process&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A purpose-built Leoflow UI (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0018-airflow-ui-as-mvp.md" rel="noopener noreferrer"&gt;ADR 0018&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are deliberately keeping the surface small until it is &lt;em&gt;boring and reliable&lt;/em&gt;. Workflow orchestrators have to be boring to be useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  License &amp;amp; credits
&lt;/h2&gt;

&lt;p&gt;Apache 2.0. We stand on the shoulders of Airflow — the team behind it defined the vocabulary, proved the architecture, and built the UI we reuse without modification today. We also studied Argo Workflows, Prefect, and Dagster carefully; each made decisions worth borrowing, and we did.&lt;/p&gt;

&lt;p&gt;If you have ever waited five seconds for an Airflow task to start, you know why we built this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt; — the v0.0.1 release notes are at &lt;a href="https://github.com/neochaotic/leoflow/releases/tag/v0.0.1" rel="noopener noreferrer"&gt;/releases/tag/v0.0.1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>go</category>
      <category>kubernetes</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>We rewrote Apache Airflow's control plane in Go (and kept the UI)</title>
      <dc:creator>Alisson Rosa</dc:creator>
      <pubDate>Wed, 03 Jun 2026 16:22:27 +0000</pubDate>
      <link>https://dev.to/neochaotic/we-rewrote-apache-airflows-control-plane-in-go-and-kept-the-ui-1fh5</link>
      <guid>https://dev.to/neochaotic/we-rewrote-apache-airflows-control-plane-in-go-and-kept-the-ui-1fh5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Leoflow &lt;code&gt;v0.0.1&lt;/code&gt; just shipped. It speaks the Airflow API, runs the Airflow 3.2.x UI &lt;strong&gt;unmodified&lt;/strong&gt;, but replaces the Python control plane with Go. Pod-per-task is the only execution mode. Each DAG is its own container image. Fan-in (map-reduce) is a Python list comprehension. Install: &lt;code&gt;curl -fsSL https://raw.githubusercontent.com/neochaotic/leoflow/main/install.sh | sh&lt;/code&gt;. GitHub: &lt;strong&gt;&lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt; — stars and issues warmly accepted.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The 3 AM pager
&lt;/h2&gt;

&lt;p&gt;You know the one. The scheduler stalled again. Or the triggerer suffocated under 500 sensors. Or a worker leaked file descriptors until Kubernetes OOMKilled it mid-run. Or someone bumped &lt;code&gt;pandas&lt;/code&gt; for the new DAG and broke six legacy ones because they all share the same image.&lt;/p&gt;

&lt;p&gt;Apache Airflow is the most widely deployed workflow orchestrator on earth. It is also the one that bleeds the most in production. None of those wounds are bugs — they are &lt;strong&gt;structural consequences of running orchestration through a Python control plane&lt;/strong&gt;. You cannot patch the GIL. You cannot make &lt;code&gt;DagBag&lt;/code&gt; reparse cheap. You cannot make Celery workers ephemeral without rewriting them.&lt;/p&gt;

&lt;p&gt;So we did the only thing left: we kept everything Airflow got right and replaced everything that bleeds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "kept" means
&lt;/h2&gt;

&lt;p&gt;We did not invent a new model. Airflow's &lt;code&gt;KubernetesExecutor&lt;/code&gt; proved years ago that &lt;strong&gt;pod-per-task is correct&lt;/strong&gt;: each task gets its own container, its own resources, its own lifecycle. You can't leak a process that exits.&lt;/p&gt;

&lt;p&gt;We also did not invent a new UI. The Airflow 3.2.x React SPA ships embedded inside the Leoflow server binary. Your team's muscle memory survives the migration.&lt;/p&gt;

&lt;p&gt;What we kept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pod-per-task execution model&lt;/li&gt;
&lt;li&gt;The Airflow 3.2.x UI (literally the same React build, served from &lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The HTTP API shape (&lt;code&gt;/api/v2/dags/...&lt;/code&gt;, &lt;code&gt;/api/v2/dagRuns/...&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;The vocabulary: DAG, TaskInstance, DagRun, XCom, Trigger Rules&lt;/li&gt;
&lt;li&gt;The DAG-authoring dialect — &lt;code&gt;from airflow.sdk import DAG, task&lt;/code&gt;, TaskFlow, classic operators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we threw out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Python scheduler. It's Go now.&lt;/li&gt;
&lt;li&gt;The Python triggerer. Sensors are 2 KB goroutines.&lt;/li&gt;
&lt;li&gt;The shared &lt;code&gt;/dags&lt;/code&gt; folder. Each DAG is its own immutable container image.&lt;/li&gt;
&lt;li&gt;The "long-lived Celery worker" model. Every task is an ephemeral pod.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What it looks like to write a DAG
&lt;/h2&gt;

&lt;p&gt;Two files. That's the whole project.&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="c1"&gt;# leoflow.yaml — your deploy concerns&lt;/span&gt;
&lt;span class="na"&gt;dag_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;etl_sales&lt;/span&gt;
&lt;span class="na"&gt;python_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pandas==2.1.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;requests==2.31.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 python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dag.py — your DAG, in real Airflow SDK 3.2.x
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;etl_sales&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 5 * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;catchup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&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 shell"&gt;&lt;code&gt;leoflow compile &lt;span class="nb"&gt;.&lt;/span&gt;              &lt;span class="c"&gt;# generates Dockerfile, builds image, emits dag.json&lt;/span&gt;
leoflow push ./dag.json        &lt;span class="c"&gt;# registers a new versioned DAG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Dockerfile&lt;/code&gt;. No &lt;code&gt;requirements.txt&lt;/code&gt;. No &lt;code&gt;Helm values.yaml&lt;/code&gt; for this DAG. No &lt;code&gt;pyproject.toml&lt;/code&gt;. The compiler reads &lt;code&gt;leoflow.yaml&lt;/code&gt;, generates a Dockerfile against the official base image (&lt;code&gt;leoflow/python-runtime:3.11&lt;/code&gt;), builds, pushes to your registry, and registers a versioned &lt;code&gt;dag.json&lt;/code&gt; with the control plane. That's the whole inner loop.&lt;/p&gt;

&lt;p&gt;For local development, &lt;code&gt;leoflow lite&lt;/code&gt; provisions a managed Postgres, hot-reloads on every save, gives each DAG its own per-DAG virtualenv at &lt;code&gt;~/.leoflow/dev/venvs/&amp;lt;dag_id&amp;gt;/&lt;/code&gt;, and &lt;strong&gt;auto-detects &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt; on &lt;code&gt;PATH&lt;/code&gt;&lt;/strong&gt; for 5–10× faster cold installs. Two DAGs that pin conflicting versions of the same package coexist without interference. This is the bit that made me file the issue against Airflow for the first time, ten years ago. We finally have it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Map-reduce, as a Python list comprehension
&lt;/h2&gt;

&lt;p&gt;Hyperparameter search. K-fold cross-validation. Ensemble training. Monte Carlo. Every parallel ML workload is &lt;strong&gt;map-reduce&lt;/strong&gt;. Most orchestrators make you build it: an operator per fan-out, a broker for the intermediate values, shared storage for the artifacts, a custom reducer that knows how to find them all.&lt;/p&gt;

&lt;p&gt;Leoflow expresses the whole pattern in &lt;strong&gt;two lines of Python&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;train_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                            &lt;span class="c1"&gt;# map
&lt;/span&gt;
&lt;span class="nd"&gt;@task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;select_best&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;    &lt;span class="c1"&gt;# reduce
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hparam_search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;select_best&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;trial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;[trial(lr) for lr in …]&lt;/code&gt; is the whole map. &lt;code&gt;trials: list[dict]&lt;/code&gt; is the whole reduce. &lt;strong&gt;No XCom plumbing, no broker, no shared filesystem, no special operator.&lt;/strong&gt; The parser captures the list shape at compile time; the runtime assembles upstream XComs in declaration order and delivers them as a real Python list. Per-trial isolation (own pod, own process, own venv if you want). Per-trial retry. Deterministic ordering. A 256 KB cap per upstream value. A &lt;code&gt;null&lt;/code&gt; slot for any upstream that legitimately produced no result.&lt;/p&gt;

&lt;p&gt;If you have ever written a Celery chord by hand, take a moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                          Author / CI                             │
│  leoflow.yaml  +  dag.py  +  (auto-generated) Dockerfile         │
└───────────────────────────────┬─────────────────────────────────┘
                                │  leoflow compile / push
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Control plane — Go                           │
│ ┌───────────────────────────────────────────────────────────┐   │
│ │ HTTP API  /api/v2  ·  JWT · RBAC · multi-tenant           │   │
│ ├───────────────────────────────────────────────────────────┤   │
│ │ Scheduler   ·  state machine · cron · catchup             │   │
│ │             ·  PG-advisory-lock leader election           │   │
│ │             ·  retries with backoff                       │   │
│ ├───────────────────────────────────────────────────────────┤   │
│ │ Agent gRPC service  ·  task spec · state · XCom · logs     │   │
│ └───────────────────────────────────────────────────────────┘   │
│       │                                  │                       │
│       │ Postgres (metadata)              │ Redis (XCom + log)    │
└───────┼──────────────────────────────────┼──────────────────────┘
        │                                  │
        │     dispatch: one pod per task   │
        ▼                                  │
┌───────────────────────────────────────┐ │
│              Kubernetes               │ │
│  ┌─────────────────────────────────┐  │ │
│  │  Worker pod = your DAG image    │  │ │
│  │  leoflow-agent (15 MB Go bin)   │  │ │
│  │     ⇅ gRPC                      │  │ │
│  │  your Python / Bash code        │──┼─┘
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-lived &lt;code&gt;http_api&lt;/code&gt; tasks skip the pod and run inline as goroutines (capped). Everything else runs &lt;strong&gt;pod-per-task&lt;/strong&gt;, every time. Concurrency is goroutines and pods — no Celery, no triggerer process, no shared worker pool.&lt;/p&gt;

&lt;p&gt;A few specifics worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leader election&lt;/strong&gt; is a Postgres advisory lock. No external coordinator. No ZooKeeper, no etcd, no Raft library. It is the kind of decision you can explain to a new hire in 30 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XCom&lt;/strong&gt; lives in &lt;strong&gt;Postgres on Lite&lt;/strong&gt; (small, no Redis required for laptop dev) and &lt;strong&gt;Redis on Pro&lt;/strong&gt;. 256 KB cap, optional schema validation, last-write-wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connections&lt;/strong&gt; are encrypted at rest with AES-256-GCM and delivered to tasks via Airflow's standard &lt;code&gt;AIRFLOW_CONN_&amp;lt;ID&amp;gt;&lt;/code&gt; env var. Postgres / MySQL / SQLite / MSSQL / Redis / HTTP / GCS connectors ship with chain-of-custody-tested integration tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent&lt;/strong&gt; is a static Go binary, ~15 MB. PID 1 of the task pod. Talks gRPC back to the control plane. Forks one process per task. Does not buffer Python output (&lt;code&gt;-u&lt;/code&gt; plus &lt;code&gt;PYTHONUNBUFFERED=1&lt;/code&gt;), because watching a SIGKILL race steal half of the user's &lt;code&gt;print()&lt;/code&gt; output is its own kind of torment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The numbers (the only honest part of any orchestration README)
&lt;/h2&gt;

&lt;p&gt;We are not going to claim "1000× faster" because nobody who has run real pipelines believes you. Here is what falls out of replacing the control plane:&lt;/p&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;Airflow today&lt;/th&gt;
&lt;th&gt;Leoflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler decision latency&lt;/td&gt;
&lt;td&gt;3–10 s per task&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;lt;200 ms&lt;/strong&gt; — native Go, no GIL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensor concurrency&lt;/td&gt;
&lt;td&gt;~500 (asyncio Triggerer)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100,000+&lt;/strong&gt; — each sensor is a 2 KB goroutine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DAG parsing cost&lt;/td&gt;
&lt;td&gt;Re-parsed every scheduler loop&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zero&lt;/strong&gt; — &lt;code&gt;dag.json&lt;/code&gt; is precompiled, immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker lifecycle&lt;/td&gt;
&lt;td&gt;Long-lived, leak-prone&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Ephemeral pod per task&lt;/strong&gt; — spawn, run, die&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker image size&lt;/td&gt;
&lt;td&gt;1.5 GB+ Airflow base&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~200 MB typical&lt;/strong&gt; — each DAG is its own slim image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency isolation&lt;/td&gt;
&lt;td&gt;Workaround via &lt;code&gt;KubernetesPodOperator&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Native&lt;/strong&gt; — every DAG is a container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;15–45 s&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2–5 s target&lt;/strong&gt; — agent is a 15 MB static binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Retrofitted with effort&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Native&lt;/strong&gt; — Prometheus + OpenTelemetry + structured logs from commit one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the structural wins. The marketing-grade "X× faster" depends on your DAG. The scheduler latency drop is universal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it is not (because we have all read those launch posts)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not v1.0.&lt;/strong&gt; Per &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0037-release-version-scheme.md" rel="noopener noreferrer"&gt;ADR 0037&lt;/a&gt;, &lt;code&gt;v0.0.1&lt;/code&gt; ends the pre-alpha series; every release after is &lt;code&gt;vX.Y.Z-rc.N → vX.Y.Z&lt;/code&gt;. The HTTP API, CLI surface, and Helm values may change between minor versions until &lt;code&gt;v1.0.0&lt;/code&gt; locks them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI is still Airflow 3.2.x.&lt;/strong&gt; It is a tactical choice (your team's muscle memory). A purpose-built Leoflow UI is on the roadmap (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0018-airflow-ui-as-mvp.md" rel="noopener noreferrer"&gt;ADR 0018&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro is Kubernetes-only.&lt;/strong&gt; Lite runs anywhere. Pro means a real cluster, external Postgres + Redis, the Helm chart. There is deliberately no Docker-Compose "Pro" path; we explained why in &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0015-kubernetes-only-execution.md" rel="noopener noreferrer"&gt;ADR 0015&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a drop-in for every Airflow plugin.&lt;/strong&gt; The Airflow operator catalog has 30+ years of accreted Python; we ship a closed set (&lt;code&gt;python&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;http_api&lt;/code&gt;) plus first-party connectors. ADR 0036 defines a runtime shim for &lt;code&gt;from airflow.providers.&amp;lt;X&amp;gt;.hooks.&amp;lt;Y&amp;gt; import &amp;lt;Z&amp;gt;Hook&lt;/code&gt; so the common cases keep working — but if your DAG depends on three obscure providers we have not vendored, you will hit a wall today. File an issue; we are gating the next batch by demand.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lite: the zero-deploy path
&lt;/h2&gt;

&lt;p&gt;Here is the part that surprises people. To run Leoflow on your laptop you do &lt;strong&gt;not&lt;/strong&gt; need a Kubernetes cluster. You do not need Docker. You do not need a container registry, a &lt;code&gt;compile&lt;/code&gt;, a &lt;code&gt;push&lt;/code&gt;, or a single line of deploy YAML. You need one shell command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/neochaotic/leoflow/main/install.sh | sh
leoflow lite                &lt;span class="c"&gt;# → http://localhost:8088 (LITE badge, top-center)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer is a single shell script: three static Go binaries into &lt;code&gt;~/.leoflow/bin&lt;/code&gt;, then &lt;code&gt;leoflow setup&lt;/code&gt; provisions a &lt;strong&gt;managed CPython&lt;/strong&gt;, the parser, and a &lt;strong&gt;managed local Postgres&lt;/strong&gt; — nothing touches your system Python or your global packages. Then &lt;code&gt;leoflow lite&lt;/code&gt; boots a full control plane (scheduler, API, UI) against that managed Postgres. No system services, no Compose file, no cluster. Close the terminal and it's gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  There is no "dags/" folder — there is &lt;em&gt;your&lt;/em&gt; folder
&lt;/h3&gt;

&lt;p&gt;This trips up everyone coming from Airflow, so let's be explicit. Leoflow has &lt;strong&gt;no magic &lt;code&gt;dags/&lt;/code&gt; directory&lt;/strong&gt;. During &lt;code&gt;leoflow setup&lt;/code&gt; you pick a &lt;strong&gt;workspace folder&lt;/strong&gt; (default &lt;code&gt;~/leoflow&lt;/code&gt;) — that folder &lt;em&gt;is&lt;/em&gt; the runtime. Every subdirectory that contains a &lt;code&gt;leoflow.yaml&lt;/code&gt; is a DAG project; the watcher scans them and hot-reloads on save. Your tree looks like what you'd actually keep in git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/leoflow/                     ← the workspace you chose at install
├── etl_sales/
│   ├── leoflow.yaml           ← makes this folder a DAG
│   └── dag.py
└── hparam_search/
    ├── leoflow.yaml
    └── dag.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No central registry file, no &lt;code&gt;dag_folder&lt;/code&gt; setting to fight, no "why isn't my DAG showing up." A folder with a &lt;code&gt;leoflow.yaml&lt;/code&gt; is a DAG. That's the whole rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit DAGs from the browser — and get examples in one click
&lt;/h3&gt;

&lt;p&gt;Lite ships a small &lt;strong&gt;embedded web editor&lt;/strong&gt; so you can go from install to a running DAG without leaving the browser. Click the &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt; &lt;strong&gt;IDE&lt;/strong&gt; button (bottom-right of the UI) and you get a real &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco&lt;/a&gt; editor — the engine behind VS Code — scoped to your workspace:&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.amazonaws.com%2Fuploads%2Farticles%2Fe9861fdl44pnim3dp154.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.amazonaws.com%2Fuploads%2Farticles%2Fe9861fdl44pnim3dp154.png" alt="The Leoflow Lite web editor: a file tree on the left with leoflow.yaml and a dag.py open, Python syntax highlighting, and Download examples / New file / Save buttons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python + YAML syntax highlighting&lt;/strong&gt;, a workspace file tree, open/save (&lt;strong&gt;⌘S&lt;/strong&gt;), create/rename/delete with a "create target" chip that always tells you where a new file will land, collapse/expand carets that remember their state, and a recursive folder delete that says so out loud before it nukes a tree.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;"Download examples"&lt;/strong&gt; button in the header. Click it and Leoflow materializes the bundled example DAGs straight into your workspace — fan-out/aggregate, Monte Carlo π, an HTTP-load DAG, a daily-sales ETL — so you have real, runnable DAGs in the UI in seconds instead of staring at an empty home screen.&lt;/li&gt;
&lt;li&gt;Every save hits disk, the watcher picks it up, and the DAG &lt;strong&gt;hot-reloads&lt;/strong&gt;. (One gotcha: the Airflow tab doesn't auto-refresh DAG &lt;em&gt;structure&lt;/em&gt; — reload it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is deliberately &lt;em&gt;not&lt;/em&gt; a full IDE — no extensions, no terminal, no debugger. For those, point your own editor at the same workspace folder; it's just files on disk. The editor is a Lite convenience and is never registered in Pro.&lt;/p&gt;

&lt;p&gt;Recover the admin password any time with &lt;code&gt;leoflow lite reset-password&lt;/code&gt;. The &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/lite-web-editor.md" rel="noopener noreferrer"&gt;Lite web-editor guide&lt;/a&gt; and the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/dev-workflow.md" rel="noopener noreferrer"&gt;Lite cookbook&lt;/a&gt; cover the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pro: when you outgrow the laptop
&lt;/h2&gt;

&lt;p&gt;For production, the Helm chart deploys against external Postgres 13+ and Redis 6+ (Cloud SQL / RDS / Memorystore / ElastiCache / Azure Cache all work):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace leoflow
helm &lt;span class="nb"&gt;install &lt;/span&gt;lf oci://ghcr.io/neochaotic/leoflow &lt;span class="nt"&gt;-n&lt;/span&gt; leoflow &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; v0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; database.url&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'postgres://USER:PASS@HOST:5432/leoflow?sslmode=verify-full'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; redis.url&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'rediss://HOST:6380/0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; auth.jwtSecret&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 64&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;secretKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/helm-chart.md" rel="noopener noreferrer"&gt;chart docs&lt;/a&gt; for every value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Engineering discipline (the stuff you would actually want to know)
&lt;/h2&gt;

&lt;p&gt;If you are evaluating Leoflow as a load-bearing piece of your data platform, the answers are in the repo, but the short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict TDD.&lt;/strong&gt; Every line of production code is preceded by a failing test (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0011-tdd-strict.md" rel="noopener noreferrer"&gt;ADR 0011&lt;/a&gt;). Per-package coverage floors enforced in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go Report Card A+&lt;/strong&gt; as the quality floor (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0012-code-quality-standards.md" rel="noopener noreferrer"&gt;ADR 0012&lt;/a&gt;). &lt;code&gt;gocyclo ≤ 15&lt;/code&gt; per function. GoDocs on every exported identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain from commit one.&lt;/strong&gt; &lt;code&gt;govulncheck&lt;/code&gt;, &lt;code&gt;gosec&lt;/code&gt;, &lt;code&gt;Trivy&lt;/code&gt;, &lt;code&gt;CodeQL&lt;/code&gt;, &lt;code&gt;gitleaks&lt;/code&gt;, OpenSSF Scorecard, OpenSSF Best Practices badge. Releases signed with &lt;code&gt;cosign&lt;/code&gt;. SBOMs published per platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every architectural decision is an ADR.&lt;/strong&gt; 37 of them at the time of writing. They are the single best place to start if you want to understand the &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo also runs an end-to-end install smoke against &lt;strong&gt;seven Linux distros&lt;/strong&gt; (&lt;code&gt;ubuntu:24.04&lt;/code&gt;, &lt;code&gt;debian:12&lt;/code&gt;, &lt;code&gt;fedora:41&lt;/code&gt;, &lt;code&gt;alpine:3.20&lt;/code&gt;, &lt;code&gt;opensuse/leap:15&lt;/code&gt;, &lt;code&gt;archlinux:latest&lt;/code&gt;, &lt;code&gt;rockylinux:9&lt;/code&gt;) on every release, plus a &lt;code&gt;prealpha.N → v0.0.1&lt;/code&gt; upgrade smoke. The &lt;code&gt;v0.0.1&lt;/code&gt; release got the green light because every one of those passed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Help us ship the next milestone
&lt;/h2&gt;

&lt;p&gt;We are at &lt;code&gt;v0.0.1&lt;/code&gt;. The next steps are visible — pick any:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Star the repo&lt;/strong&gt; at &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;. It is the cheapest way to tell us this matters to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open an issue&lt;/strong&gt; with a chronic Airflow pain we have not closed yet. Pre-1.0 is &lt;em&gt;the&lt;/em&gt; time to shape the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File a PR.&lt;/strong&gt; Strict TDD applies. The &lt;a href="https://github.com/neochaotic/leoflow/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING guide&lt;/a&gt; is the entry point. We review fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the Lite quick start&lt;/strong&gt; (60 seconds, above). If something does not work, that is an issue worth filing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the Helm chart&lt;/strong&gt; against a real cluster and tell us what bit you. Pro alpha needs real-world miles before it gets a Pro &lt;code&gt;v1.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What is on the post-&lt;code&gt;v0.0.1&lt;/code&gt; table
&lt;/h2&gt;

&lt;p&gt;Visible work toward &lt;code&gt;v0.1.0&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimized backfill (parallel execution with throttling)&lt;/li&gt;
&lt;li&gt;UI scaling for 10,000+ DAGs (caching, server-side pagination)&lt;/li&gt;
&lt;li&gt;Dynamic task mapping&lt;/li&gt;
&lt;li&gt;OIDC authentication (Google, Azure AD, Keycloak, Okta)&lt;/li&gt;
&lt;li&gt;Deferrable tasks (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0016-deferrable-tasks.md" rel="noopener noreferrer"&gt;ADR 0016&lt;/a&gt;) — efficient dispatch + long-poll pattern, native Go, &lt;strong&gt;no separate Triggerer process&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A purpose-built Leoflow UI (&lt;a href="https://github.com/neochaotic/leoflow/blob/main/docs/adr/0018-airflow-ui-as-mvp.md" rel="noopener noreferrer"&gt;ADR 0018&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are deliberately keeping the surface small until it is &lt;em&gt;boring and reliable&lt;/em&gt;. Workflow orchestrators have to be boring to be useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  License &amp;amp; credits
&lt;/h2&gt;

&lt;p&gt;Apache 2.0. We stand on the shoulders of Airflow — the team behind it defined the vocabulary, proved the architecture, and built the UI we reuse without modification today. We also studied Argo Workflows, Prefect, and Dagster carefully; each made decisions worth borrowing, and we did.&lt;/p&gt;

&lt;p&gt;If you have ever waited five seconds for an Airflow task to start, you know why we built this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/neochaotic/leoflow" rel="noopener noreferrer"&gt;github.com/neochaotic/leoflow&lt;/a&gt;&lt;/strong&gt; — the v0.0.1 release notes are at &lt;a href="https://github.com/neochaotic/leoflow/releases/tag/v0.0.1" rel="noopener noreferrer"&gt;/releases/tag/v0.0.1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>go</category>
      <category>kubernetes</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
