This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 9f874c3a6f6 Add 'breeze k8s dev' command for hot-reloading DAGs and 
core sources (#40005) (#59747)
9f874c3a6f6 is described below

commit 9f874c3a6f65efdbf84deaefe18c62a4851eeb56
Author: Aaron Chen <[email protected]>
AuthorDate: Fri Dec 26 07:16:18 2025 +0800

    Add 'breeze k8s dev' command for hot-reloading DAGs and core sources 
(#40005) (#59747)
    
    * Add 'breeze k8s dev' command for hot-reloading DAGs and core sources 
(#40005)
    
    * fix CI test error
    
    * Add Kubernetes development output files and update documentation
    
    - Updated output_k8s.txt with new hash value.
    - Added output_k8s_dev.svg for visual representation of the 'k8s dev' 
command usage.
    - Created output_k8s_dev.txt with corresponding hash for the new SVG output.
    
    * Update documentation for Breeze test commands
    
    * update output files and add log level parameter to Kubernetes commands
---
 contributing-docs/testing/k8s_tests.rst            |  20 ++
 dev/breeze/doc/05_test_commands.rst                |  27 +++
 dev/breeze/doc/images/output_k8s.svg               |  40 ++--
 dev/breeze/doc/images/output_k8s.txt               |   2 +-
 dev/breeze/doc/images/output_k8s_dev.svg           | 188 ++++++++++++++++
 dev/breeze/doc/images/output_k8s_dev.txt           |   1 +
 .../output_setup_check-all-params-in-groups.svg    |   4 +-
 .../output_setup_check-all-params-in-groups.txt    |   2 +-
 .../output_setup_regenerate-command-images.svg     |   4 +-
 .../output_setup_regenerate-command-images.txt     |   2 +-
 .../airflow_breeze/commands/kubernetes_commands.py | 242 ++++++++++++++++++++-
 .../commands/kubernetes_commands_config.py         |  17 ++
 dev/breeze/src/airflow_breeze/global_constants.py  |   3 +
 .../src/airflow_breeze/utils/kubernetes_utils.py   |  20 ++
 14 files changed, 548 insertions(+), 24 deletions(-)

diff --git a/contributing-docs/testing/k8s_tests.rst 
b/contributing-docs/testing/k8s_tests.rst
index 4a16bae3249..bfb823cf828 100644
--- a/contributing-docs/testing/k8s_tests.rst
+++ b/contributing-docs/testing/k8s_tests.rst
@@ -121,6 +121,26 @@ you need to deploy Airflow with the ``--executor 
KubernetesExecutor`` flag.
     breeze k8s deploy-airflow --executor KubernetesExecutor
 
 
+Hot-reloading DAGs and core sources
+-----------------------------------
+
+If you want to iterate on DAGs or core sources without rebuilding images, run 
``breeze k8s dev`` after the
+cluster is deployed. It starts a skaffold loop that syncs local changes under 
``dags/`` and
+``airflow-core/src/airflow`` into the running pods. Scheduler, triggerer, and 
dag-processor run in dev
+hot-reload mode; the API server and webserver UI are not hot-reloaded by 
default.
+
+.. code-block:: bash
+
+    breeze k8s dev
+
+If skaffold cannot find the running pods (for example if the release was 
deployed outside skaffold),
+rerun with ``--deploy`` so it upgrades the Helm release and manages the 
resources:
+
+.. code-block:: bash
+
+    breeze k8s dev --deploy
+
+
 Running tests with Kubernetes Cluster
 -------------------------------------
 
diff --git a/dev/breeze/doc/05_test_commands.rst 
b/dev/breeze/doc/05_test_commands.rst
index 9b062a1b2d2..5ae0372978f 100644
--- a/dev/breeze/doc/05_test_commands.rst
+++ b/dev/breeze/doc/05_test_commands.rst
@@ -424,6 +424,7 @@ You can:
 * Manage KinD Kubernetes cluster and upload image and deploy Airflow to KinD 
cluster via
   ``breeze k8s create-cluster``, ``breeze k8s configure-cluster``, ``breeze 
k8s deploy-airflow``, ``breeze k8s status``,
   ``breeze k8s upload-k8s-image``, ``breeze k8s delete-cluster`` commands
+* Hot-reload DAGs and core sources (scheduler/triggerer/dag-processor) with 
``breeze k8s dev`` (skaffold sync)
 * Run Kubernetes tests  specified with ``breeze k8s tests`` command
 * Run complete test run with ``breeze k8s run-complete-tests`` - performing 
the full cycle of creating
   cluster, uploading the image, deploying airflow, running tests and deleting 
the cluster
@@ -547,6 +548,32 @@ All parameters of the command are here:
   :width: 100%
   :alt: Breeze k8s deploy-airflow
 
+Hot-reloading DAGs and core sources
+...................................
+
+After deploying Airflow you can run ``breeze k8s dev`` to sync local ``dags/`` 
and
+``airflow-core/src/airflow`` changes into running pods without rebuilding 
images. Scheduler,
+triggerer, and dag-processor run in dev hot-reload mode; the API server and 
webserver UI are not
+hot-reloaded by default.
+
+.. code-block:: bash
+
+    breeze k8s dev
+
+If skaffold cannot find the pods (for example if the release was deployed 
outside skaffold), rerun with
+``--deploy`` so it upgrades the Helm release and manages the resources:
+
+.. code-block:: bash
+
+    breeze k8s dev --deploy
+
+All parameters of the command are here:
+
+.. image:: ./images/output_k8s_dev.svg
+  :target: 
https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/images/output_k8s_dev.svg
+  :width: 100%
+  :alt: Breeze k8s dev
+
 Checking status of the K8S cluster
 ..................................
 
diff --git a/dev/breeze/doc/images/output_k8s.svg 
b/dev/breeze/doc/images/output_k8s.svg
index 38d7f5afc95..27ac395e5d4 100644
--- a/dev/breeze/doc/images/output_k8s.svg
+++ b/dev/breeze/doc/images/output_k8s.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 782.0" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 830.8" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-k8s-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="731.0" />
+      <rect x="0" y="0" width="1463.0" height="779.8" />
     </clipPath>
     <clipPath id="breeze-k8s-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -132,9 +132,15 @@
 <clipPath id="breeze-k8s-line-28">
     <rect x="0" y="684.7" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-k8s-line-29">
+    <rect x="0" y="709.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-line-30">
+    <rect x="0" y="733.5" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="780" rx="8"/><text class="breeze-k8s-title" 
fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;k8s</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="828.8" rx="8"/><text class="breeze-k8s-title" 
fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;k8s</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -161,19 +167,21 @@
 </text><text class="breeze-k8s-r5" x="0" y="361.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-14)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="361.6" textLength="207.4" 
clip-path="url(#breeze-k8s-line-14)">build-k8s-image&#160;&#160;</text><text 
class="breeze-k8s-r1" x="256.2" y="361.6" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-14)">Build&#160;k8s-ready&#160;airflow&#160;image&#160;(optionally&#160;all&#160;images&#160;in&#160;parallel).&#160;&#160;&#160;&#160;&#160;
 [...]
 </text><text class="breeze-k8s-r5" x="0" y="386" textLength="12.2" 
clip-path="url(#breeze-k8s-line-15)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="386" textLength="207.4" 
clip-path="url(#breeze-k8s-line-15)">upload-k8s-image&#160;</text><text 
class="breeze-k8s-r1" x="256.2" y="386" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-15)">Upload&#160;k8s-ready&#160;airflow&#160;image&#160;to&#160;the&#160;KinD&#160;cluster&#160;(optionally&#160;to&#160;all&#160;clusters&#160;in&#160;
 [...]
 </text><text class="breeze-k8s-r5" x="0" y="410.4" textLength="12.2" 
clip-path="url(#breeze-k8s-line-16)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="410.4" textLength="207.4" 
clip-path="url(#breeze-k8s-line-16)">deploy-airflow&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="256.2" y="410.4" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-16)">Deploy&#160;airflow&#160;image&#160;to&#160;the&#160;current&#160;KinD&#160;cluster&#160;(or&#160;all&#160;clusters).&#160;&#160;&#
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="434.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-17)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="434.8" textLength="207.4" 
clip-path="url(#breeze-k8s-line-17)">delete-cluster&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="256.2" y="434.8" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-17)">Delete&#160;the&#160;current&#160;KinD&#160;Cluster&#160;(optionally&#160;all&#160;clusters).&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="459.2" textLength="1464" 
clip-path="url(#breeze-k8s-line-18)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="459.2" textLength="12.2" 
clip-path="url(#breeze-k8s-line-18)">
-</text><text class="breeze-k8s-r5" x="0" y="483.6" textLength="24.4" 
clip-path="url(#breeze-k8s-line-19)">╭─</text><text class="breeze-k8s-r5" 
x="24.4" y="483.6" textLength="305" 
clip-path="url(#breeze-k8s-line-19)">&#160;K8S&#160;inspection&#160;commands&#160;</text><text
 class="breeze-k8s-r5" x="329.4" y="483.6" textLength="1110.2" 
clip-path="url(#breeze-k8s-line-19)">───────────────────────────────────────────────────────────────────────────────────────────</text><text
 class="breeze-k [...]
-</text><text class="breeze-k8s-r5" x="0" y="508" textLength="12.2" 
clip-path="url(#breeze-k8s-line-20)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="508" textLength="97.6" 
clip-path="url(#breeze-k8s-line-20)">status&#160;&#160;</text><text 
class="breeze-k8s-r1" x="146.4" y="508" textLength="1293.2" 
clip-path="url(#breeze-k8s-line-20)">Check&#160;status&#160;of&#160;the&#160;current&#160;cluster&#160;and&#160;airflow&#160;deployed&#160;to&#160;it&#160;(optionally&#160;all&#160;clusters
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="532.4" textLength="12.2" 
clip-path="url(#breeze-k8s-line-21)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="532.4" textLength="97.6" 
clip-path="url(#breeze-k8s-line-21)">logs&#160;&#160;&#160;&#160;</text><text 
class="breeze-k8s-r1" x="146.4" y="532.4" textLength="463.6" 
clip-path="url(#breeze-k8s-line-21)">Dump&#160;k8s&#160;logs&#160;to&#160;${TMP_DIR}/kind_logs_</text><text
 class="breeze-k8s-r7" x="610" y="532.4" textLength="170.8" clip-pa [...]
-</text><text class="breeze-k8s-r5" x="0" y="556.8" textLength="1464" 
clip-path="url(#breeze-k8s-line-22)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="556.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-22)">
-</text><text class="breeze-k8s-r5" x="0" y="581.2" textLength="24.4" 
clip-path="url(#breeze-k8s-line-23)">╭─</text><text class="breeze-k8s-r5" 
x="24.4" y="581.2" textLength="268.4" 
clip-path="url(#breeze-k8s-line-23)">&#160;K8S&#160;testing&#160;commands&#160;</text><text
 class="breeze-k8s-r5" x="292.8" y="581.2" textLength="1146.8" 
clip-path="url(#breeze-k8s-line-23)">──────────────────────────────────────────────────────────────────────────────────────────────</text><text
 class="breeze [...]
-</text><text class="breeze-k8s-r5" x="0" y="605.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-24)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="605.6" textLength="219.6" 
clip-path="url(#breeze-k8s-line-24)">tests&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="605.6" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-24)">Run&#160;tests&#160;against&#160;the&#160;current&#160;KinD&#160;cluster&#160;(o
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="630" textLength="12.2" 
clip-path="url(#breeze-k8s-line-25)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="630" textLength="219.6" 
clip-path="url(#breeze-k8s-line-25)">run-complete-tests</text><text 
class="breeze-k8s-r1" x="268.4" y="630" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-25)">Run&#160;complete&#160;k8s&#160;tests&#160;consisting&#160;of:&#160;creating&#160;cluster,&#160;building&#160;and&#160;uploading&#160;image,&#160;deplo
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="654.4" textLength="12.2" 
clip-path="url(#breeze-k8s-line-26)">│</text><text class="breeze-k8s-r1" 
x="268.4" y="654.4" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-26)">airflow,&#160;running&#160;tests&#160;and&#160;deleting&#160;clusters&#160;(optionally&#160;for&#160;all&#160;clusters&#160;in&#160;parallel).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r5" x="1451.8" y="654.4" textLength="12.2" cli [...]
-</text><text class="breeze-k8s-r5" x="0" y="678.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-27)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="678.8" textLength="219.6" 
clip-path="url(#breeze-k8s-line-27)">shell&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="678.8" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-27)">Run&#160;shell&#160;environment&#160;for&#160;the&#160;current&#160;KinD&#160;cl
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="703.2" textLength="12.2" 
clip-path="url(#breeze-k8s-line-28)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="703.2" textLength="219.6" 
clip-path="url(#breeze-k8s-line-28)">k9s&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="703.2" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-28)">Run&#160;k9s&#160;tool.&#160;You&#160;can&#160;pass&#160;any&#160;k9s&
 [...]
-</text><text class="breeze-k8s-r5" x="0" y="727.6" textLength="1464" 
clip-path="url(#breeze-k8s-line-29)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="727.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-29)">
+</text><text class="breeze-k8s-r5" x="0" y="434.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-17)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="434.8" textLength="207.4" 
clip-path="url(#breeze-k8s-line-17)">dev&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="256.2" y="434.8" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-17)">Run&#160;skaffold&#160;dev&#160;loop&#160;to&#160;sync&#160;dags&#160;and&#1
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="459.2" textLength="12.2" 
clip-path="url(#breeze-k8s-line-18)">│</text><text class="breeze-k8s-r1" 
x="256.2" y="459.2" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-18)">(scheduler/triggerer/dag-processor&#160;hot-reload;&#160;API&#160;server/webserver&#160;UI&#160;not&#160;by&#160;default).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#b 
[...]
+</text><text class="breeze-k8s-r5" x="0" y="483.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-19)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="483.6" textLength="207.4" 
clip-path="url(#breeze-k8s-line-19)">delete-cluster&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="256.2" y="483.6" textLength="1183.4" 
clip-path="url(#breeze-k8s-line-19)">Delete&#160;the&#160;current&#160;KinD&#160;Cluster&#160;(optionally&#160;all&#160;clusters).&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="508" textLength="1464" 
clip-path="url(#breeze-k8s-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="508" textLength="12.2" 
clip-path="url(#breeze-k8s-line-20)">
+</text><text class="breeze-k8s-r5" x="0" y="532.4" textLength="24.4" 
clip-path="url(#breeze-k8s-line-21)">╭─</text><text class="breeze-k8s-r5" 
x="24.4" y="532.4" textLength="305" 
clip-path="url(#breeze-k8s-line-21)">&#160;K8S&#160;inspection&#160;commands&#160;</text><text
 class="breeze-k8s-r5" x="329.4" y="532.4" textLength="1110.2" 
clip-path="url(#breeze-k8s-line-21)">───────────────────────────────────────────────────────────────────────────────────────────</text><text
 class="breeze-k [...]
+</text><text class="breeze-k8s-r5" x="0" y="556.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-22)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="556.8" textLength="97.6" 
clip-path="url(#breeze-k8s-line-22)">status&#160;&#160;</text><text 
class="breeze-k8s-r1" x="146.4" y="556.8" textLength="1293.2" 
clip-path="url(#breeze-k8s-line-22)">Check&#160;status&#160;of&#160;the&#160;current&#160;cluster&#160;and&#160;airflow&#160;deployed&#160;to&#160;it&#160;(optionally&#160;all&#160;cl
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="581.2" textLength="12.2" 
clip-path="url(#breeze-k8s-line-23)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="581.2" textLength="97.6" 
clip-path="url(#breeze-k8s-line-23)">logs&#160;&#160;&#160;&#160;</text><text 
class="breeze-k8s-r1" x="146.4" y="581.2" textLength="463.6" 
clip-path="url(#breeze-k8s-line-23)">Dump&#160;k8s&#160;logs&#160;to&#160;${TMP_DIR}/kind_logs_</text><text
 class="breeze-k8s-r7" x="610" y="581.2" textLength="170.8" clip-pa [...]
+</text><text class="breeze-k8s-r5" x="0" y="605.6" textLength="1464" 
clip-path="url(#breeze-k8s-line-24)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="605.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-24)">
+</text><text class="breeze-k8s-r5" x="0" y="630" textLength="24.4" 
clip-path="url(#breeze-k8s-line-25)">╭─</text><text class="breeze-k8s-r5" 
x="24.4" y="630" textLength="268.4" 
clip-path="url(#breeze-k8s-line-25)">&#160;K8S&#160;testing&#160;commands&#160;</text><text
 class="breeze-k8s-r5" x="292.8" y="630" textLength="1146.8" 
clip-path="url(#breeze-k8s-line-25)">──────────────────────────────────────────────────────────────────────────────────────────────</text><text
 class="breeze-k8s-r [...]
+</text><text class="breeze-k8s-r5" x="0" y="654.4" textLength="12.2" 
clip-path="url(#breeze-k8s-line-26)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="654.4" textLength="219.6" 
clip-path="url(#breeze-k8s-line-26)">tests&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="654.4" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-26)">Run&#160;tests&#160;against&#160;the&#160;current&#160;KinD&#160;cluster&#160;(o
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="678.8" textLength="12.2" 
clip-path="url(#breeze-k8s-line-27)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="678.8" textLength="219.6" 
clip-path="url(#breeze-k8s-line-27)">run-complete-tests</text><text 
class="breeze-k8s-r1" x="268.4" y="678.8" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-27)">Run&#160;complete&#160;k8s&#160;tests&#160;consisting&#160;of:&#160;creating&#160;cluster,&#160;building&#160;and&#160;uploading&#160;image,&#160
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="703.2" textLength="12.2" 
clip-path="url(#breeze-k8s-line-28)">│</text><text class="breeze-k8s-r1" 
x="268.4" y="703.2" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-28)">airflow,&#160;running&#160;tests&#160;and&#160;deleting&#160;clusters&#160;(optionally&#160;for&#160;all&#160;clusters&#160;in&#160;parallel).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r5" x="1451.8" y="703.2" textLength="12.2" cli [...]
+</text><text class="breeze-k8s-r5" x="0" y="727.6" textLength="12.2" 
clip-path="url(#breeze-k8s-line-29)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="727.6" textLength="219.6" 
clip-path="url(#breeze-k8s-line-29)">shell&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="727.6" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-29)">Run&#160;shell&#160;environment&#160;for&#160;the&#160;current&#160;KinD&#160;cl
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="752" textLength="12.2" 
clip-path="url(#breeze-k8s-line-30)">│</text><text class="breeze-k8s-r4" 
x="24.4" y="752" textLength="219.6" 
clip-path="url(#breeze-k8s-line-30)">k9s&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-r1" x="268.4" y="752" textLength="1171.2" 
clip-path="url(#breeze-k8s-line-30)">Run&#160;k9s&#160;tool.&#160;You&#160;can&#160;pass&#160;any&#160;k9s&#160;a
 [...]
+</text><text class="breeze-k8s-r5" x="0" y="776.4" textLength="1464" 
clip-path="url(#breeze-k8s-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-r1" x="1464" y="776.4" textLength="12.2" 
clip-path="url(#breeze-k8s-line-31)">
 </text>
     </g>
     </g>
diff --git a/dev/breeze/doc/images/output_k8s.txt 
b/dev/breeze/doc/images/output_k8s.txt
index 2014dc199a3..7967bb9bd0d 100644
--- a/dev/breeze/doc/images/output_k8s.txt
+++ b/dev/breeze/doc/images/output_k8s.txt
@@ -1 +1 @@
-753a8c2dae15310166438602584a4775
+8f44d97d272fb33b061059e6b4239497
diff --git a/dev/breeze/doc/images/output_k8s_dev.svg 
b/dev/breeze/doc/images/output_k8s_dev.svg
new file mode 100644
index 00000000000..bd43ff2b65d
--- /dev/null
+++ b/dev/breeze/doc/images/output_k8s_dev.svg
@@ -0,0 +1,188 @@
+<svg class="rich-terminal" viewBox="0 0 1482 830.8" 
xmlns="http://www.w3.org/2000/svg";>
+    <!-- Generated with Rich https://www.textualize.io -->
+    <style>
+
+    @font-face {
+        font-family: "Fira Code";
+        src: local("FiraCode-Regular"),
+                
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2";)
 format("woff2"),
+                
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff";)
 format("woff");
+        font-style: normal;
+        font-weight: 400;
+    }
+    @font-face {
+        font-family: "Fira Code";
+        src: local("FiraCode-Bold"),
+                
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2";)
 format("woff2"),
+                
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff";)
 format("woff");
+        font-style: bold;
+        font-weight: 700;
+    }
+
+    .breeze-k8s-dev-matrix {
+        font-family: Fira Code, monospace;
+        font-size: 20px;
+        line-height: 24.4px;
+        font-variant-east-asian: full-width;
+    }
+
+    .breeze-k8s-dev-title {
+        font-size: 18px;
+        font-weight: bold;
+        font-family: arial;
+    }
+
+    .breeze-k8s-dev-r1 { fill: #c5c8c6 }
+.breeze-k8s-dev-r2 { fill: #d0b344 }
+.breeze-k8s-dev-r3 { fill: #c5c8c6;font-weight: bold }
+.breeze-k8s-dev-r4 { fill: #68a0b3;font-weight: bold }
+.breeze-k8s-dev-r5 { fill: #868887 }
+.breeze-k8s-dev-r6 { fill: #98a84b;font-weight: bold }
+.breeze-k8s-dev-r7 { fill: #8d7b39 }
+    </style>
+
+    <defs>
+    <clipPath id="breeze-k8s-dev-clip-terminal">
+      <rect x="0" y="0" width="1463.0" height="779.8" />
+    </clipPath>
+    <clipPath id="breeze-k8s-dev-line-0">
+    <rect x="0" y="1.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-1">
+    <rect x="0" y="25.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-2">
+    <rect x="0" y="50.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-3">
+    <rect x="0" y="74.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-4">
+    <rect x="0" y="99.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-5">
+    <rect x="0" y="123.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-6">
+    <rect x="0" y="147.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-7">
+    <rect x="0" y="172.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-8">
+    <rect x="0" y="196.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-9">
+    <rect x="0" y="221.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-10">
+    <rect x="0" y="245.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-11">
+    <rect x="0" y="269.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-12">
+    <rect x="0" y="294.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-13">
+    <rect x="0" y="318.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-14">
+    <rect x="0" y="343.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-15">
+    <rect x="0" y="367.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-16">
+    <rect x="0" y="391.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-17">
+    <rect x="0" y="416.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-18">
+    <rect x="0" y="440.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-19">
+    <rect x="0" y="465.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-20">
+    <rect x="0" y="489.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-21">
+    <rect x="0" y="513.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-22">
+    <rect x="0" y="538.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-23">
+    <rect x="0" y="562.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-24">
+    <rect x="0" y="587.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-25">
+    <rect x="0" y="611.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-26">
+    <rect x="0" y="635.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-27">
+    <rect x="0" y="660.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-28">
+    <rect x="0" y="684.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-29">
+    <rect x="0" y="709.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-k8s-dev-line-30">
+    <rect x="0" y="733.5" width="1464" height="24.65"/>
+            </clipPath>
+    </defs>
+
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="828.8" rx="8"/><text 
class="breeze-k8s-dev-title" fill="#c5c8c6" text-anchor="middle" x="740" 
y="27">Command:&#160;k8s&#160;dev</text>
+            <g transform="translate(26,22)">
+            <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+            <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+            <circle cx="44" cy="0" r="7" fill="#28c840"/>
+            </g>
+        
+    <g transform="translate(9, 41)" 
clip-path="url(#breeze-k8s-dev-clip-terminal)">
+    
+    <g class="breeze-k8s-dev-matrix">
+    <text class="breeze-k8s-dev-r1" x="1464" y="20" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-0)">
+</text><text class="breeze-k8s-dev-r2" x="12.2" y="44.4" textLength="73.2" 
clip-path="url(#breeze-k8s-dev-line-1)">Usage:</text><text 
class="breeze-k8s-dev-r3" x="97.6" y="44.4" textLength="170.8" 
clip-path="url(#breeze-k8s-dev-line-1)">breeze&#160;k8s&#160;dev</text><text 
class="breeze-k8s-dev-r1" x="280.6" y="44.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-1)">[</text><text class="breeze-k8s-dev-r4" 
x="292.8" y="44.4" textLength="85.4" clip-path="url(#breeze-k8s-dev-line-1) 
[...]
+</text><text class="breeze-k8s-dev-r1" x="1464" y="68.8" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-2)">
+</text><text class="breeze-k8s-dev-r1" x="12.2" y="93.2" textLength="1354.2" 
clip-path="url(#breeze-k8s-dev-line-3)">Run&#160;skaffold&#160;dev&#160;loop&#160;to&#160;sync&#160;dags&#160;and&#160;airflow-core&#160;sources&#160;to&#160;running&#160;pods&#160;(scheduler/triggerer/dag-processor&#160;</text><text
 class="breeze-k8s-dev-r1" x="1464" y="93.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-3)">
+</text><text class="breeze-k8s-dev-r1" x="12.2" y="117.6" textLength="634.4" 
clip-path="url(#breeze-k8s-dev-line-4)">hot-reload;&#160;API&#160;server/webserver&#160;UI&#160;not&#160;by&#160;default).</text><text
 class="breeze-k8s-dev-r1" x="1464" y="117.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-4)">
+</text><text class="breeze-k8s-dev-r1" x="1464" y="142" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-5)">
+</text><text class="breeze-k8s-dev-r5" x="0" y="166.4" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-6)">╭─</text><text 
class="breeze-k8s-dev-r5" x="24.4" y="166.4" textLength="244" 
clip-path="url(#breeze-k8s-dev-line-6)">&#160;Skaffold&#160;dev&#160;flags&#160;</text><text
 class="breeze-k8s-dev-r5" x="268.4" y="166.4" textLength="1171.2" 
clip-path="url(#breeze-k8s-dev-line-6)">────────────────────────────────────────────────────────────────────────────────────────────────</text>
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="190.8" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-7)">│</text><text class="breeze-k8s-dev-r4" 
x="24.4" y="190.8" textLength="97.6" 
clip-path="url(#breeze-k8s-dev-line-7)">--python</text><text 
class="breeze-k8s-dev-r6" x="317.2" y="190.8" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-7)">-p</text><text 
class="breeze-k8s-dev-r1" x="366" y="190.8" textLength="732" 
clip-path="url(#breeze-k8s-dev-line-7)">Python&#160;major/min [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="215.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-8)">│</text><text class="breeze-k8s-dev-r7" 
x="366" y="215.2" textLength="732" 
clip-path="url(#breeze-k8s-dev-line-8)">(&gt;3.10&lt;&#160;|&#160;3.11&#160;|&#160;3.12&#160;|&#160;3.13)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
  [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="239.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-9)">│</text><text class="breeze-k8s-dev-r5" 
x="366" y="239.6" textLength="732" 
clip-path="url(#breeze-k8s-dev-line-9)">[default:&#160;3.10]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="264" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-10)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="264" textLength="244" 
clip-path="url(#breeze-k8s-dev-line-10)">--kubernetes-version</text><text 
class="breeze-k8s-dev-r1" x="366" y="264" textLength="658.8" 
clip-path="url(#breeze-k8s-dev-line-10)">Kubernetes&#160;version&#160;used&#160;to&#160;create&#160;the&#160;KinD&#160;cluster&#160;of.</text><text
 class="breeze-k8s-dev-r [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="288.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-11)">│</text><text 
class="breeze-k8s-dev-r7" x="366" y="288.4" textLength="658.8" 
clip-path="url(#breeze-k8s-dev-line-11)">(&gt;v1.30.13&lt;&#160;|&#160;v1.31.12&#160;|&#160;v1.32.8&#160;|&#160;v1.33.4&#160;|&#160;v1.34.0)&#160;</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="288.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-11)">│</text><text class="breeze-k8s-dev- 
[...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="312.8" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-12)">│</text><text 
class="breeze-k8s-dev-r5" x="366" y="312.8" textLength="658.8" 
clip-path="url(#breeze-k8s-dev-line-12)">[default:&#160;v1.30.13]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="337.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-13)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="337.2" textLength="122" 
clip-path="url(#breeze-k8s-dev-line-13)">--executor</text><text 
class="breeze-k8s-dev-r1" x="366" y="337.2" textLength="1073.6" 
clip-path="url(#breeze-k8s-dev-line-13)">Executor&#160;to&#160;use&#160;for&#160;a&#160;kubernetes&#160;cluster.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="361.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-14)">│</text><text 
class="breeze-k8s-dev-r7" x="366" y="361.6" textLength="1073.6" 
clip-path="url(#breeze-k8s-dev-line-14)">(&gt;LocalExecutor&lt;&#160;|&#160;KubernetesExecutor&#160;|&#160;CeleryExecutor&#160;|&#160;CeleryKubernetesExecutor&#160;|&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="361.6" textLength="12.2" 
clip-path="url(#breeze-k8s- [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="386" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-15)">│</text><text 
class="breeze-k8s-dev-r7" x="366" y="386" textLength="1073.6" 
clip-path="url(#breeze-k8s-dev-line-15)">EdgeExecutor)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="410.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-16)">│</text><text 
class="breeze-k8s-dev-r5" x="366" y="410.4" textLength="1073.6" 
clip-path="url(#breeze-k8s-dev-line-16)">[default:&#160;LocalExecutor]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="434.8" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-17)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="434.8" textLength="134.2" 
clip-path="url(#breeze-k8s-dev-line-17)">--log-level</text><text 
class="breeze-k8s-dev-r1" x="366" y="434.8" textLength="634.4" 
clip-path="url(#breeze-k8s-dev-line-17)">Log&#160;level&#160;for&#160;Airflow&#160;components&#160;when&#160;using&#160;k8s&#160;dev.</text><text
 class="breeze-k8s-dev-r5"  [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="459.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-18)">│</text><text 
class="breeze-k8s-dev-r7" x="366" y="459.2" textLength="634.4" 
clip-path="url(#breeze-k8s-dev-line-18)">(&gt;INFO&lt;&#160;|&#160;DEBUG&#160;|&#160;WARNING&#160;|&#160;ERROR&#160;|&#160;CRITICAL)&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="459.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-18)">│</text> [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="483.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-19)">│</text><text 
class="breeze-k8s-dev-r5" x="366" y="483.6" textLength="634.4" 
clip-path="url(#breeze-k8s-dev-line-19)">[default:&#160;INFO]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="508" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-20)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="508" textLength="256.2" 
clip-path="url(#breeze-k8s-dev-line-20)">--use-standard-naming</text><text 
class="breeze-k8s-dev-r1" x="366" y="508" textLength="244" 
clip-path="url(#breeze-k8s-dev-line-20)">Use&#160;standard&#160;naming.</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="508" textLength="12.2" 
clip-path="url(#breeze- [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="532.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-21)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="532.4" textLength="268.4" 
clip-path="url(#breeze-k8s-dev-line-21)">--multi-namespace-mode</text><text 
class="breeze-k8s-dev-r1" x="366" y="532.4" textLength="305" 
clip-path="url(#breeze-k8s-dev-line-21)">Use&#160;multi&#160;namespace&#160;mode.</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="532.4" textLength="12.2" clip [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="556.8" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-22)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="556.8" textLength="134.2" 
clip-path="url(#breeze-k8s-dev-line-22)">--dags-path</text><text 
class="breeze-k8s-dev-r1" x="366" y="556.8" textLength="353.8" 
clip-path="url(#breeze-k8s-dev-line-22)">Local&#160;dags&#160;directory&#160;to&#160;sync.</text><text
 class="breeze-k8s-dev-r7" x="732" y="556.8" textLength="134.2" clip-p [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="581.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-23)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="581.2" textLength="134.2" 
clip-path="url(#breeze-k8s-dev-line-23)">--dags-dest</text><text 
class="breeze-k8s-dev-r1" x="366" y="581.2" textLength="671" 
clip-path="url(#breeze-k8s-dev-line-23)">Destination&#160;path&#160;inside&#160;the&#160;Airflow&#160;container&#160;for&#160;dags.</text><text
 class="breeze-k8s-dev-r7" x="1 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="605.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-24)">│</text><text 
class="breeze-k8s-dev-r5" x="366" y="605.6" textLength="671" 
clip-path="url(#breeze-k8s-dev-line-24)">[default:&#160;/opt/airflow/dags]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-k8s-dev-r5" x="1451.8" y="605.6" textLen [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="630" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-25)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="630" textLength="97.6" 
clip-path="url(#breeze-k8s-dev-line-25)">--deploy</text><text 
class="breeze-k8s-dev-r1" x="122" y="630" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-25)">/</text><text 
class="breeze-k8s-dev-r4" x="134.2" y="630" textLength="134.2" 
clip-path="url(#breeze-k8s-dev-line-25)">--no-deploy</text><text  [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="654.4" textLength="1464" 
clip-path="url(#breeze-k8s-dev-line-26)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-dev-r1" x="1464" y="654.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-26)">
+</text><text class="breeze-k8s-dev-r5" x="0" y="678.8" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-27)">╭─</text><text 
class="breeze-k8s-dev-r5" x="24.4" y="678.8" textLength="195.2" 
clip-path="url(#breeze-k8s-dev-line-27)">&#160;Common&#160;options&#160;</text><text
 class="breeze-k8s-dev-r5" x="219.6" y="678.8" textLength="1220" 
clip-path="url(#breeze-k8s-dev-line-27)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><t
 [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="703.2" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-28)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="703.2" textLength="109.8" 
clip-path="url(#breeze-k8s-dev-line-28)">--verbose</text><text 
class="breeze-k8s-dev-r6" x="158.6" y="703.2" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-28)">-v</text><text 
class="breeze-k8s-dev-r1" x="207.4" y="703.2" textLength="585.6" 
clip-path="url(#breeze-k8s-dev-line-28)">Print&#160; [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="727.6" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-29)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="727.6" textLength="109.8" 
clip-path="url(#breeze-k8s-dev-line-29)">--dry-run</text><text 
class="breeze-k8s-dev-r6" x="158.6" y="727.6" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-29)">-D</text><text 
class="breeze-k8s-dev-r1" x="207.4" y="727.6" textLength="719.8" 
clip-path="url(#breeze-k8s-dev-line-29)">If&#160;dry [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="752" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-30)">│</text><text 
class="breeze-k8s-dev-r4" x="24.4" y="752" textLength="73.2" 
clip-path="url(#breeze-k8s-dev-line-30)">--help</text><text 
class="breeze-k8s-dev-r6" x="158.6" y="752" textLength="24.4" 
clip-path="url(#breeze-k8s-dev-line-30)">-h</text><text 
class="breeze-k8s-dev-r1" x="207.4" y="752" textLength="329.4" 
clip-path="url(#breeze-k8s-dev-line-30)">Show&#160;this&#160;mes [...]
+</text><text class="breeze-k8s-dev-r5" x="0" y="776.4" textLength="1464" 
clip-path="url(#breeze-k8s-dev-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-k8s-dev-r1" x="1464" y="776.4" textLength="12.2" 
clip-path="url(#breeze-k8s-dev-line-31)">
+</text>
+    </g>
+    </g>
+</svg>
diff --git a/dev/breeze/doc/images/output_k8s_dev.txt 
b/dev/breeze/doc/images/output_k8s_dev.txt
new file mode 100644
index 00000000000..6b642713c15
--- /dev/null
+++ b/dev/breeze/doc/images/output_k8s_dev.txt
@@ -0,0 +1 @@
+99d0fd2bbb0308c67b89d704ed4b7b97
diff --git a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg 
b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
index 63e5006c43d..1df98773e64 100644
--- a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
+++ b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
@@ -206,8 +206,8 @@
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="215.2" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="215.2" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-8)">ci-image:import-mount-cache&#160;|&#160;ci-image:load&#160;|&#160;ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;&#160;&#160;&#1
 [...]
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="239.6" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="239.6" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-9)">ci:fix-ownership&#160;|&#160;ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;ci:selective-check&#160;|&#160;&#160;&#160;
 [...]
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="264" 
textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="264" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-10)">ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;generate-migration-file&#160;|&#160;k8s&#160;|&#160;k8s:build-k8s-i
 [...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="288.4" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="288.4" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:k9s&#160;|&#160;&#160;&#160;
 [...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="312.8" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="312.8" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:shell&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;|&#160;&#160;&#16
 [...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="288.4" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="288.4" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-11)">k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:dev&#160;|&#160;&#160;&#160;
 [...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="312.8" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="312.8" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-12)">k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:shell&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#1
 [...]
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="337.2" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="337.2" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-13)">k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build&#160;|&#160;prod-image:load&#160;|&#160;prod-image:pull&#160;|&#160;&#160;&#160;&#160;
 [...]
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" 
y="361.6" textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="361.6" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-14)">prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;release-management&#160;|&#160;release-management:add-back-references&#160;|&#160;&#160;&#160;&#160;<
 [...]
 </text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="386" 
textLength="12.2" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">│</text><text 
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="386" 
textLength="1256.6" 
clip-path="url(#breeze-setup-check-all-params-in-groups-line-15)">release-management:check-release-files&#160;|&#160;release-management:clean-old-provider-artifacts&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
diff --git a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt 
b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
index 0e394088c98..85993056fe5 100644
--- a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
+++ b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
@@ -1 +1 @@
-c5a1381f3c729a659592af33c840201f
+7e7a7c30c1e7fdc5417d2167dff16e12
diff --git a/dev/breeze/doc/images/output_setup_regenerate-command-images.svg 
b/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
index d7eb4c80afb..732444d3c66 100644
--- a/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
+++ b/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
@@ -216,8 +216,8 @@
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="239.6" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-9)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="239.6" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-9)">ci-image:import-mount-cache&#160;|&#160;ci-image:load&#160;|&#160;ci-image:pull&#160;|&#160;ci-image:save&#160;|&#160;ci-image:verify&#160;|&#160;&#160;&#160;&#160;&
 [...]
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="264" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-10)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="264" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-10)">ci:fix-ownership&#160;|&#160;ci:free-space&#160;|&#160;ci:get-workflow-info&#160;|&#160;ci:resource-check&#160;|&#160;ci:selective-check&#160;|&#160;&#160;</text><text
 [...]
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-11)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="288.4" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-11)">ci:upgrade&#160;|&#160;cleanup&#160;|&#160;doctor&#160;|&#160;down&#160;|&#160;exec&#160;|&#160;generate-migration-file&#160;|&#160;k8s&#160;|&#160;k8s:build-k8s-i
 [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="312.8" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-12)">k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:k9s&#160;|&#160;&#160;&#160;&#16
 [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="337.2" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-13)">k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:shell&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;|&#160;&#160;&#160;&#
 [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-12)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="312.8" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-12)">k8s:configure-cluster&#160;|&#160;k8s:create-cluster&#160;|&#160;k8s:delete-cluster&#160;|&#160;k8s:deploy-airflow&#160;|&#160;k8s:dev&#160;|&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-13)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="337.2" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-13)">k8s:k9s&#160;|&#160;k8s:logs&#160;|&#160;k8s:run-complete-tests&#160;|&#160;k8s:setup-env&#160;|&#160;k8s:shell&#160;|&#160;k8s:status&#160;|&#160;k8s:tests&#160;|
 [...]
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-14)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="361.6" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-14)">k8s:upload-k8s-image&#160;|&#160;prod-image&#160;|&#160;prod-image:build&#160;|&#160;prod-image:load&#160;|&#160;prod-image:pull&#160;|&#160;&#160;&#160;&#160;&#16
 [...]
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="386" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-15)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="386" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-15)">prod-image:save&#160;|&#160;prod-image:verify&#160;|&#160;release-management&#160;|&#160;release-management:add-back-references&#160;|&#160;</text><text
 class="breeze- [...]
 </text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="410.4" 
textLength="12.2" 
clip-path="url(#breeze-setup-regenerate-command-images-line-16)">│</text><text 
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="410.4" 
textLength="1220" 
clip-path="url(#breeze-setup-regenerate-command-images-line-16)">release-management:check-release-files&#160;|&#160;release-management:clean-old-provider-artifacts&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
diff --git a/dev/breeze/doc/images/output_setup_regenerate-command-images.txt 
b/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
index 2416eaa09d1..908310fb627 100644
--- a/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
+++ b/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
@@ -1 +1 @@
-627080d0a4c38036722abf94230efa2f
+cafa5d4043930c4c1b8e2b2db4a12b6a
diff --git a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py 
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
index 11c6e731f01..0de0496405b 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
@@ -26,8 +26,10 @@ from copy import deepcopy
 from itertools import chain
 from pathlib import Path
 from shlex import quote
+from typing import Any
 
 import click
+import yaml
 
 from airflow_breeze.commands.common_options import (
     option_answer,
@@ -44,9 +46,13 @@ from airflow_breeze.commands.common_options import (
 )
 from airflow_breeze.commands.production_image_commands import 
run_build_production_image
 from airflow_breeze.global_constants import (
+    AIRFLOW_SOURCES_TO,
     ALLOWED_EXECUTORS,
     ALLOWED_KUBERNETES_VERSIONS,
+    ALLOWED_LOG_LEVELS,
     CELERY_EXECUTOR,
+    DEFAULT_ALLOWED_EXECUTOR,
+    DEFAULT_LOG_LEVEL,
     KUBERNETES_EXECUTOR,
 )
 from airflow_breeze.params.build_prod_params import BuildProdParams
@@ -69,6 +75,7 @@ from airflow_breeze.utils.kubernetes_utils import (
     get_kubernetes_port_numbers,
     get_kubernetes_python_combos,
     make_sure_kubernetes_tools_are_installed,
+    make_sure_skaffold_installed,
     print_cluster_urls,
     run_command_with_k8s_env,
     set_random_cluster_ports,
@@ -80,6 +87,7 @@ from airflow_breeze.utils.parallel import (
     check_async_run_results,
     run_with_pool,
 )
+from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
 from airflow_breeze.utils.recording import generating_command_images
 from airflow_breeze.utils.run_utils import RunCommandResult, 
check_if_image_exists, run_command
 
@@ -128,9 +136,17 @@ option_executor = click.option(
     help="Executor to use for a kubernetes cluster.",
     type=CacheableChoice(ALLOWED_EXECUTORS),
     show_default=True,
-    default=CacheableDefault(ALLOWED_EXECUTORS[0]),
+    default=CacheableDefault(DEFAULT_ALLOWED_EXECUTOR),
     envvar="EXECUTOR",
 )
+option_log_level = click.option(
+    "--log-level",
+    help="Log level for Airflow components when using k8s dev.",
+    type=CacheableChoice(ALLOWED_LOG_LEVELS),
+    show_default=True,
+    default=CacheableDefault(DEFAULT_LOG_LEVEL),
+    envvar="LOG_LEVEL",
+)
 option_force_recreate_cluster = click.option(
     "--force-recreate-cluster",
     help="Force recreation of the cluster even if it is already created.",
@@ -165,6 +181,26 @@ option_multi_namespace_mode = click.option(
     is_flag=True,
     envvar="MULTI_NAMESPACE_MODE",
 )
+option_dags_path = click.option(
+    "--dags-path",
+    help="Local dags directory to sync.",
+    type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
+    default="dags",
+    show_default=True,
+)
+option_dags_dest = click.option(
+    "--dags-dest",
+    help="Destination path inside the Airflow container for dags.",
+    default="/opt/airflow/dags",
+    show_default=True,
+)
+option_skaffold_deploy = click.option(
+    "--deploy/--no-deploy",
+    help="Let skaffold deploy/upgrade Airflow via Helm.",
+    default=False,
+    show_default=True,
+    envvar="SKAFFOLD_DEPLOY",
+)
 option_rebuild_base_image = click.option(
     "--rebuild-base-image",
     help="Rebuilds base Airflow image before building K8S image.",
@@ -804,6 +840,100 @@ HELM_AIRFLOW_NAMESPACE = "airflow"
 TEST_NAMESPACE = "test-namespace"
 
 
+def _build_skaffold_config(
+    python: str,
+    kubernetes_version: str,
+    executor: str,
+    use_standard_naming: bool,
+    multi_namespace_mode: bool,
+    dags_relative_path: str,
+    dags_dest: str,
+    log_level: str,
+) -> dict[str, Any]:
+    from packaging.version import Version
+
+    params = BuildProdParams(python=python)
+    use_flask_appbuilder = Version(python) < Version("3.13")
+    if use_flask_appbuilder:
+        auth_manager = 
"airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager"
+    else:
+        auth_manager = 
"airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager"
+    _, api_server_port = get_kubernetes_port_numbers(python=python, 
kubernetes_version=kubernetes_version)
+    set_values: dict[str, object] = {
+        "defaultAirflowRepository": params.airflow_image_kubernetes,
+        "defaultAirflowTag": "latest",
+        "images.airflow.repository": params.airflow_image_kubernetes,
+        "images.airflow.tag": "latest",
+        "config.logging.logging_level": log_level,
+        "executor": executor,
+        "airflowVersion": params.airflow_semver_version,
+        "config.api_auth.jwt_secret": "foo",
+        "config.core.auth_manager": auth_manager,
+        "config.api.base_url": f"http://localhost:{api_server_port}";,
+    }
+    if multi_namespace_mode:
+        set_values["multiNamespaceMode"] = True
+    if not use_flask_appbuilder:
+        set_values["webserver.defaultUser.enabled"] = False
+    if use_standard_naming:
+        set_values["useStandardNaming"] = True
+    sync_entries: list[dict[str, str]] = []
+    sync_entry: dict[str, str] = {
+        "src": f"{dags_relative_path}/**",
+        "dest": dags_dest,
+    }
+    if dags_relative_path != ".":
+        sync_entry["strip"] = f"{dags_relative_path}/"
+        dependencies_paths = [f"{dags_relative_path}/**"]
+    else:
+        dependencies_paths = ["**"]
+    sync_entries.append(sync_entry)
+    core_relative_path = "airflow-core/src/airflow"
+    core_dest = f"{AIRFLOW_SOURCES_TO}/airflow-core/src/airflow"
+    sync_entries.append(
+        {
+            "src": f"{core_relative_path}/**",
+            "dest": core_dest,
+            "strip": f"{core_relative_path}/",
+        }
+    )
+    if dependencies_paths != ["**"]:
+        dependencies_paths.append(f"{core_relative_path}/**")
+    return {
+        "apiVersion": "skaffold/v4beta13",
+        "kind": "Config",
+        "metadata": {"name": "airflow-dags-dev"},
+        "build": {
+            "tagPolicy": {"envTemplate": {"template": "latest"}},
+            "artifacts": [
+                {
+                    "image": params.airflow_image_kubernetes,
+                    "context": AIRFLOW_ROOT_PATH.as_posix(),
+                    "custom": {
+                        "buildCommand": "true",
+                        "dependencies": {"paths": dependencies_paths},
+                    },
+                    "sync": {"manual": sync_entries},
+                }
+            ],
+            "local": {"push": False},
+        },
+        "deploy": {
+            "helm": {
+                "releases": [
+                    {
+                        "name": "airflow",
+                        "chartPath": CHART_PATH.as_posix(),
+                        "namespace": HELM_AIRFLOW_NAMESPACE,
+                        "skipBuildDependencies": True,
+                        "setValues": set_values,
+                    }
+                ]
+            }
+        },
+    }
+
+
 def _recreate_namespaces(
     python: str,
     kubernetes_version: str,
@@ -1256,6 +1386,116 @@ def deploy_airflow(
         sys.exit(return_code)
 
 
+@kubernetes_group.command(
+    name="dev",
+    help=(
+        "Run skaffold dev loop to sync dags and airflow-core sources to 
running pods "
+        "(scheduler/triggerer/dag-processor hot-reload; API server/webserver 
UI not by default)."
+    ),
+    context_settings=dict(
+        ignore_unknown_options=True,
+    ),
+)
+@option_python
+@option_kubernetes_version
+@option_executor
+@option_log_level
+@option_use_standard_naming
+@option_multi_namespace_mode
+@option_dags_path
+@option_dags_dest
+@option_skaffold_deploy
+@option_verbose
+@option_dry_run
[email protected]("skaffold_args", nargs=-1, type=click.UNPROCESSED)
+def dev(
+    python: str,
+    kubernetes_version: str,
+    executor: str,
+    log_level: str,
+    use_standard_naming: bool,
+    multi_namespace_mode: bool,
+    dags_path: Path,
+    dags_dest: str,
+    deploy: bool,
+    skaffold_args: tuple[str, ...],
+):
+    result = sync_virtualenv(force_venv_setup=False)
+    if result.returncode != 0:
+        sys.exit(result.returncode)
+    make_sure_kubernetes_tools_are_installed()
+    make_sure_skaffold_installed()
+    dags_path_abs = dags_path
+    if not dags_path_abs.is_absolute():
+        dags_path_abs = AIRFLOW_ROOT_PATH / dags_path_abs
+    dags_path_abs = dags_path_abs.resolve()
+    if not dags_path_abs.is_dir():
+        get_console().print(f"[error]DAGs path does not exist or is not a 
directory: {dags_path_abs}")
+        sys.exit(1)
+    try:
+        dags_relative_path = 
dags_path_abs.relative_to(AIRFLOW_ROOT_PATH).as_posix()
+    except ValueError:
+        get_console().print(f"[error]DAGs path must be under the Airflow 
sources: {AIRFLOW_ROOT_PATH}")
+        sys.exit(1)
+    if not get_kind_cluster_config_path(python=python, 
kubernetes_version=kubernetes_version).exists():
+        get_console().print(
+            f"\n[warning]Cluster for Python {python} and Kubernetes 
{kubernetes_version} "
+            "has not been created yet.\n"
+        )
+        get_console().print(
+            "[info]Run: "
+            f"`breeze k8s create-cluster --python {python} 
--kubernetes-version {kubernetes_version}`\n"
+        )
+        sys.exit(1)
+    skaffold_config = _build_skaffold_config(
+        python=python,
+        kubernetes_version=kubernetes_version,
+        executor=executor,
+        use_standard_naming=use_standard_naming,
+        multi_namespace_mode=multi_namespace_mode,
+        dags_relative_path=dags_relative_path,
+        dags_dest=dags_dest,
+        log_level=log_level,
+    )
+    if not deploy:
+        get_console().print(
+            "[info]Running skaffold without deploying Helm resources. "
+            "If sync cannot find pods, rerun with --deploy."
+        )
+    with tempfile.TemporaryDirectory(prefix="skaffold_") as tmp_dir:
+        dev_env_values = {
+            "scheduler": {"env": [{"name": "DEV_MODE", "value": "true"}]},
+            "triggerer": {"env": [{"name": "DEV_MODE", "value": "true"}]},
+            "dagProcessor": {"env": [{"name": "DEV_MODE", "value": "true"}]},
+        }
+        dev_env_values_path = Path(tmp_dir) / "dev-env-values.yaml"
+        dev_env_values_path.write_text(yaml.safe_dump(dev_env_values, 
sort_keys=False))
+        skaffold_config["deploy"]["helm"]["releases"][0]["valuesFiles"] = 
[dev_env_values_path.as_posix()]
+        skaffold_config_path = Path(tmp_dir) / "skaffold.yaml"
+        skaffold_config_path.write_text(yaml.safe_dump(skaffold_config, 
sort_keys=False))
+        skaffold_command = [
+            "skaffold",
+            "dev",
+            "-f",
+            skaffold_config_path.as_posix(),
+            "--auto-build=false",
+        ]
+        if not deploy:
+            skaffold_command.append("--auto-deploy=false")
+            skaffold_command.append("--cleanup=false")
+        if skaffold_args:
+            skaffold_command.extend(skaffold_args)
+        result = run_command_with_k8s_env(
+            skaffold_command,
+            python=python,
+            kubernetes_version=kubernetes_version,
+            executor=executor,
+            check=False,
+            cwd=AIRFLOW_ROOT_PATH.as_posix(),
+        )
+        sys.exit(result.returncode)
+
+
 @kubernetes_group.command(
     name="k9s",
     help="Run k9s tool. You can pass any k9s args as extra args.",
diff --git 
a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands_config.py
index 1a38be39022..4a048949be8 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands_config.py
@@ -25,6 +25,7 @@ KUBERNETES_CLUSTER_COMMANDS: dict[str, str | list[str]] = {
         "build-k8s-image",
         "upload-k8s-image",
         "deploy-airflow",
+        "dev",
         "delete-cluster",
     ],
 }
@@ -177,6 +178,22 @@ KUBERNETES_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
             ],
         },
     ],
+    "breeze k8s dev": [
+        {
+            "name": "Skaffold dev flags",
+            "options": [
+                "--python",
+                "--kubernetes-version",
+                "--executor",
+                "--log-level",
+                "--use-standard-naming",
+                "--multi-namespace-mode",
+                "--dags-path",
+                "--dags-dest",
+                "--deploy",
+            ],
+        },
+    ],
     "breeze k8s delete-cluster": [
         {
             "name": "K8S cluster delete flags",
diff --git a/dev/breeze/src/airflow_breeze/global_constants.py 
b/dev/breeze/src/airflow_breeze/global_constants.py
index bb31177ee87..a632ddead65 100644
--- a/dev/breeze/src/airflow_breeze/global_constants.py
+++ b/dev/breeze/src/airflow_breeze/global_constants.py
@@ -142,6 +142,8 @@ AUTOCOMPLETE_ALL_INTEGRATIONS = sorted(
 )
 ALLOWED_TTY = ["auto", "enabled", "disabled"]
 ALLOWED_DOCKER_COMPOSE_PROJECTS = ["breeze", "prek", "docker-compose"]
+ALLOWED_LOG_LEVELS = ["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"]
+DEFAULT_LOG_LEVEL = ALLOWED_LOG_LEVELS[0]
 
 # Unlike everything else, k8s versions are supported as long as 2 major cloud 
providers support them.
 # See:
@@ -679,6 +681,7 @@ DEFAULT_EXECUTOR = CURRENT_EXECUTORS[0]
 
 KIND_VERSION = "v0.30.0"
 HELM_VERSION = "v3.17.3"
+SKAFFOLD_VERSION = "v2.17.0"
 
 # Initialize image build variables - Have to check if this has to go to ci 
dataclass
 USE_AIRFLOW_VERSION = None
diff --git a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py 
b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
index a3fb7d9974d..1d11e00cbdc 100644
--- a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
@@ -36,6 +36,7 @@ from airflow_breeze.global_constants import (
     ALLOWED_ARCHITECTURES,
     HELM_VERSION,
     KIND_VERSION,
+    SKAFFOLD_VERSION,
 )
 from airflow_breeze.utils.console import Output, get_console
 from airflow_breeze.utils.host_info_utils import Architecture, 
get_host_architecture, get_host_os
@@ -49,6 +50,7 @@ K8S_BIN_BASE_PATH = K8S_ENV_PATH / "bin"
 KIND_BIN_PATH = K8S_BIN_BASE_PATH / "kind"
 KUBECTL_BIN_PATH = K8S_BIN_BASE_PATH / "kubectl"
 HELM_BIN_PATH = K8S_BIN_BASE_PATH / "helm"
+SKAFFOLD_BIN_PATH = K8S_BIN_BASE_PATH / "skaffold"
 PYTHON_BIN_PATH = K8S_BIN_BASE_PATH / "python"
 SCRIPTS_CI_KUBERNETES_PATH = AIRFLOW_ROOT_PATH / "scripts" / "ci" / 
"kubernetes"
 PYPROJECT_TOML_AIRFLOW_CORE_PATH = AIRFLOW_ROOT_PATH / "airflow-core" / 
"pyproject.toml"
@@ -228,6 +230,18 @@ def _download_helm_if_needed():
     )
 
 
+def _download_skaffold_if_needed():
+    _download_tool_if_needed(
+        tool="skaffold",
+        version=SKAFFOLD_VERSION,
+        version_pattern=r".*v?(\d+\.\d+\.\d+).*",
+        version_flag=["version"],
+        url=f"https://storage.googleapis.com/skaffold/releases/";
+        
f"{SKAFFOLD_VERSION}/skaffold-{get_host_os()}-{get_architecture_string_for_urls()}",
+        path=SKAFFOLD_BIN_PATH,
+    )
+
+
 def _check_architecture_supported():
     architecture, machine = get_host_architecture()
     if architecture not in ALLOWED_ARCHITECTURES:
@@ -244,6 +258,12 @@ def make_sure_helm_installed():
     _download_helm_if_needed()
 
 
+def make_sure_skaffold_installed():
+    K8S_CLUSTERS_PATH.mkdir(parents=True, exist_ok=True)
+    _check_architecture_supported()
+    _download_skaffold_if_needed()
+
+
 def make_sure_kubernetes_tools_are_installed():
     K8S_CLUSTERS_PATH.mkdir(parents=True, exist_ok=True)
     _check_architecture_supported()

Reply via email to