<?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: Anton Krylov</title>
    <description>The latest articles on DEV Community by Anton Krylov (@avkr).</description>
    <link>https://dev.to/avkr</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3062835%2F49ce94d6-eedc-4338-90f0-b85eb8f3738b.png</url>
      <title>DEV Community: Anton Krylov</title>
      <link>https://dev.to/avkr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avkr"/>
    <language>en</language>
    <item>
      <title>Why Aider</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Sun, 23 Nov 2025 20:37:34 +0000</pubDate>
      <link>https://dev.to/avkr/why-aider-1nle</link>
      <guid>https://dev.to/avkr/why-aider-1nle</guid>
      <description>&lt;p&gt;The market for AI coding assistants has split into two clearly defined camps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camp #1&lt;/strong&gt; – Full-featured graphical IDEs&lt;br&gt;&lt;br&gt;
Cursor, Windsurf, Zed + Cursor mode, VS Code with Continue + Copilot + many extensions, IntelliJ Ultimate with AI Assistant, etc.&lt;br&gt;&lt;br&gt;
Polished UI, inline completions, chat panels, debugging integration, project-wide indexing.&lt;br&gt;&lt;br&gt;
Typical costs: 8–40 s startup, 4–8 GB RAM, mandatory indexing of the whole repo, poor or no support for direct work over SSH on production/legacy servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camp #2&lt;/strong&gt; – Terminal-first tools&lt;br&gt;&lt;br&gt;
vim, neovim, helix, kakoune, tmux… and aider.&lt;br&gt;&lt;br&gt;
Start in 200–400 ms, use 50–150 MB RAM, require only a terminal and git, work perfectly over SSH with no indexing step.&lt;/p&gt;

&lt;p&gt;aider is a command-line tool that sends selected files + conversation to an LLM and applies the returned edits directly under git control. It is widely used by senior engineers, DevOps, SRE, and security teams for fast, targeted changes in large or old repositories where launching a heavy IDE is impractical.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;aider is configured via &lt;code&gt;~/.aider.conf.yml&lt;/code&gt; (or &lt;code&gt;.aider.conf.yml&lt;/code&gt; in the repository) and command-line flags.&lt;br&gt;&lt;br&gt;
Common options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choice of main and weak models&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read:&lt;/code&gt; list to permanently attach markdown files with guidelines, conventions, security rules, etc.&lt;/li&gt;
&lt;li&gt;toggles for auto-commits, pretty diffs, vim mode, prompt caching, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One can use multiple profile YAML files placed in &lt;code&gt;~/.aider/profiles/&lt;/code&gt; and switch between them with a little script that copies the selected profile to the active config file.&lt;/p&gt;

&lt;p&gt;Here's a full example of such a script (&lt;code&gt;/usr/local/bin/aider-switch&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# aider-switch — switch profiles with a single command&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;AIDER_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.aider"&lt;/span&gt;
&lt;span class="nv"&gt;PROFILES_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AIDER_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/profiles"&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_LINK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AIDER_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/current"&lt;/span&gt;
&lt;span class="nv"&gt;CONFIG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AIDER_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/aider.conf.yml"&lt;/span&gt;

die&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AIDER_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;".aider/ not found in &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;".aider/profiles/ missing in &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;list|&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Available profiles:"&lt;/span&gt;
    &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.yaml 2&amp;gt;/dev/null | xargs &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nb"&gt;basename&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/\.yaml$//'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/  /'&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Current → &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;yq e &lt;span class="s1"&gt;'.model // "unknown"'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROFILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$PROFILE&lt;/span&gt;&lt;span class="s2"&gt;.yaml"&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Profile '&lt;/span&gt;&lt;span class="nv"&gt;$PROFILE&lt;/span&gt;&lt;span class="s2"&gt;' not found"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_LINK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
    echo&lt;/span&gt; &lt;span class="s2"&gt;"Switched to profile → &lt;/span&gt;&lt;span class="nv"&gt;$PROFILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    yq e &lt;span class="s1"&gt;'.model // "unknown"'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONFIG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/ model: /'&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aider-switch list
aider-switch security-review
aider-switch kubernetes-expert &amp;amp;&amp;amp; aider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You keep a handful of YAML files in ~/.aider/profiles/ and a few markdown rule sheets nearby. One profile for backend work loads grok-4, turns on vim mode, and permanently attaches backend.md plus conventions-security.md. Another for security reviews swaps to claude-3.5-sonnet, cranks caution up, and locks onto security-review.md alone. DevOps profile picks a cheaper weak model and reads only kubernetes.md and ansible-conventions.md. Switch with aider-switch backend or aider-switch security-review and the model instantly forgets yesterday’s tone and remembers exactly who it is supposed to be today. Ten lines of bash give you twenty different personalities for the same binary. That is all there is to it.&lt;/p&gt;

&lt;p&gt;Prompting is 95 % of success with aider. aider never tries to be smarter than you — it faithfully executes exactly what’s written in the profiles and the current request. No surprise refactoring, no “I thought this would be better.”&lt;/p&gt;

&lt;p&gt;The markdown files loaded via read: act as permanent code review from your lead architect and security lead on every edit. The stricter and more concrete the rules, the safer and more predictable the output — even on the most powerful models.&lt;/p&gt;

&lt;p&gt;Bad/empty profile + vague prompt = production chaos Good profile = katana instead of chainsaw&lt;/p&gt;

&lt;p&gt;That’s why the real work with aider isn’t picking a model; it’s writing and maintaining evergreen profiles.&lt;/p&gt;

&lt;p&gt;Example ~/.aider/profiles/backend.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Backend Instructions
- Define clear APIs and contracts; version endpoints when needed.
- Validate and sanitize all inputs; enforce types and limits.
- Implement authn/authz consistently; least privilege by default.
- Handle errors with structured responses; avoid leaking internals.
- Add observability: logs, metrics, and traces for critical paths.
- Manage migrations carefully; ensure idempotent operations and rollbacks.
- Use timeouts/retries/circuit breakers for external calls.
- Protect secrets and credentials; rotate and scope access.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files are loaded into context on every run and survive /clear. Write once — never repeat again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical usage examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aider src/api/users/*.py
# Migrate to Pydantic v2, follow the attached backend guidelines

aider --message-file security.md src/auth/*.py
# Look for SSRF, open redirects and credential leaks

aider deploy/values-prod.yaml
# Increase replicas to 12 and add the new nodeSelector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security-sensitive environments
&lt;/h3&gt;

&lt;p&gt;In security-sensitive environments (banks, healthcare, defense, government), running aider directly on a developer’s laptop is often forbidden by compliance. A single careless prompt could leak secrets to logs, exfiltrate context, or generate malicious code.&lt;br&gt;
The gold standard fix: run aider inside an isolated Firecracker microVM (or gVisor/Kata). The microVM boots in ~120–150 ms, has its own kernel, zero network access, and only the current git repository is mounted — nothing else. Host keys, credentials, other projects, Docker sockets, and cloud access remain physically unreachable by the LLM process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/avkcode/firecracker-sandbox" rel="noopener noreferrer"&gt;https://github.com/avkcode/firecracker-sandbox&lt;/a&gt; let you spin up the sandbox with one command while keeping your familiar vim/tmux workflow intact. The developer feels no difference, but the model is completely contained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Heavy graphical AI IDEs and lightweight terminal tools such as aider address different workflows.&lt;br&gt;&lt;br&gt;
The graphical tools are preferred for long, exploratory development sessions on a local workstation.&lt;br&gt;&lt;br&gt;
Terminal tools are chosen when minimal latency, low resource usage, seamless SSH access, and strict control over context and edits take priority.&lt;br&gt;&lt;br&gt;
Most engineers use both categories, switching depending on the specific task.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>aider</category>
    </item>
    <item>
      <title>Redroid</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Wed, 11 Jun 2025 09:47:52 +0000</pubDate>
      <link>https://dev.to/avkr/redroid-o6n</link>
      <guid>https://dev.to/avkr/redroid-o6n</guid>
      <description>&lt;h2&gt;
  
  
  What is ReDroid?
&lt;/h2&gt;

&lt;p&gt;ReDroid is a lightweight alternative to the standard Android emulator that runs as a Docker container. ReDroid provides a full Android system in a container, significantly reducing startup time and resource consumption compared to traditional emulators.&lt;/p&gt;

&lt;p&gt;ReDroid uses the host system's Linux kernel and is based on the anbox module project, enabling Android to run without CPU virtualization. This makes it an ideal solution for automated Android app testing in CI/CD environments where speed and efficiency are critical.&lt;/p&gt;

&lt;p&gt;Comparison with Android Studio Emulator:&lt;/p&gt;

&lt;p&gt;ReDroid is perfect for automated UI testing in CI/CD, especially when speed and resource efficiency are important. The standard Android Studio emulator is better suited for local development, debugging, and testing requiring full device emulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual Testing
&lt;/h2&gt;

&lt;p&gt;Start the ReDroid container (Docker) and wait about a minute for it to load:&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="s"&gt;docker run -d --name redroid --privileged -p 5555:5555 redroid/redroid:15.0.0_64only-latest&lt;/span&gt;
&lt;span class="s"&gt;sleep &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to the running container via ADB:&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="s"&gt;adb connect localhost:5555&lt;/span&gt;
&lt;span class="s"&gt;adb devices&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install both APK files on ReDroid:&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="s"&gt;adb -s localhost:5555 install ./app/build/outputs/apk/debug/app-debug.apk&lt;/span&gt;
&lt;span class="s"&gt;adb -s localhost:5555 install ./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your Espresso/UI tests with ADB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adb -s localhost:5555 shell am instrument -w your.package.name.test/androidx.test.runner.AndroidJUnitRunner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export logs after testing:&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="s"&gt;adb -s localhost:5555 logcat -d &amp;gt; android-log.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After completing the tests, remove the container:&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="s"&gt;docker stop redroid &amp;amp;&amp;amp; docker rm redroid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preparing an Android Project for UI Testing&lt;/p&gt;

&lt;p&gt;Step 1: Configuring Dependencies in app/build.gradle&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android {
    defaultConfig {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    // Core components for UI testing
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation 'androidx.test:runner:1.5.2'
    androidTestImplementation 'androidx.test:rules:1.5.0'

    // Additional Espresso libraries for advanced testing
    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1'

    // JUnit for test environment integration
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Creating an Example UI Test with Espresso&lt;/p&gt;

&lt;p&gt;Create the file app/src/androidTest/java/com/example/app/MainActivityTest.java:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.example.app;

import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityTest {

    @Rule
    public ActivityScenarioRule&amp;lt;MainActivity&amp;gt; activityRule =
        new ActivityScenarioRule&amp;lt;&amp;gt;(MainActivity.class);

    @Test
    public void welcomeTextIsDisplayed() {
        // Check that the welcome text field is displayed
        onView(withId(R.id.welcome_text))
            .check(matches(isDisplayed()))
            .check(matches(withText("Hello World!")));
    }

    @Test
    public void buttonClickChangesText() {
        // Click the button
        onView(withId(R.id.action_button))
            .perform(click());

        // Check that the text has changed
        onView(withId(R.id.welcome_text))
            .check(matches(withText("Button Clicked!")));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring Jenkins for UI Test Automation with ReDroid&lt;/p&gt;

&lt;p&gt;Step 1: Installing Required Jenkins Plugins&lt;/p&gt;

&lt;p&gt;In the Jenkins interface, go to "Configure Jenkins" &amp;gt; "Manage Plugins" and install:&lt;/p&gt;

&lt;p&gt;Docker Pipeline&lt;br&gt;
Gradle Plugin&lt;br&gt;
HTML Publisher Plugin&lt;br&gt;
JUnit Plugin&lt;br&gt;
Allure Jenkins Plugin (optional for enhanced reporting)&lt;br&gt;
Step 2: Creating a Jenkinsfile for the Pipeline&lt;/p&gt;

&lt;p&gt;Create a Jenkinsfile in the project root:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline {
    agent any

    environment {
        // Environment variables
        ANDROID_HOME = '/opt/android-sdk'
        REDROID_IMAGE = 'redroid/redroid:15.0.0_64only-latest'
        REDROID_CONTAINER = 'android-ui-test-container'
        ADB_PORT = '5555'
        TEST_RESULTS_DIR = 'app/build/outputs/androidTest-results'
        TEST_REPORT_DIR = 'app/build/reports/androidTests'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build APKs') {
            steps {
                sh './gradlew clean assembleDebug assembleDebugAndroidTest'
            }
        }

        stage('Start ReDroid Container') {
            steps {
                script {
                    // Stop and remove the container if it exists
                    sh "docker stop ${REDROID_CONTAINER} || true"
                    sh "docker rm ${REDROID_CONTAINER} || true"

                    // Run ReDroid in Docker
                    sh """
                        docker run -d --name ${REDROID_CONTAINER} \
                        --privileged \
                        -p ${ADB_PORT}:${ADB_PORT} \
                        -e REDROID_PROP_ro.debuggable=1 \
                        -e REDROID_PROP_service.adb.tcp.port=${ADB_PORT} \
                        ${REDROID_IMAGE}
                    """

                    // Wait for ReDroid to start
                    sh "sleep 30"
                }
            }
        }

        stage('Connect to ReDroid and Install APKs') {
            steps {
                script {
                    // Connect to the device via ADB
                    sh "adb connect localhost:${ADB_PORT}"
                    sh "adb devices"

                    // Wait for the system to fully load
                    sh "adb wait-for-device"

                    // Install the app APK and tests
                    sh "adb -s localhost:${ADB_PORT} install -r app/build/outputs/apk/debug/app-debug.apk"
                    sh "adb -s localhost:${ADB_PORT} install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
                }
            }
        }

        stage('Run UI Tests') {
            steps {
                script {
                    // Create directories for results
                    sh "mkdir -p ${TEST_RESULTS_DIR}"

                    // Get the app package name
                    def packageName = sh(
                        script: "aapt dump badging app/build/outputs/apk/debug/app-debug.apk | grep package | awk '{print \$2}' | sed s/name=//g | sed s/\\'//g",
                        returnStdout: true
                    ).trim()

                    // Run UI tests
                    sh """
                        adb -s localhost:${ADB_PORT} shell am instrument -w \
                        -e debug false \
                        -e junit.output.format=xml \
                        -e additionalTestOutputDir=/sdcard/test-results \
                        ${packageName}.test/androidx.test.runner.AndroidJUnitRunner
                    """

                    // Copy results from the device
                    sh "adb -s localhost:${ADB_PORT} pull /sdcard/test-results ${TEST_RESULTS_DIR}"

                    // Collect logs
                    sh "adb -s localhost:${ADB_PORT} logcat -d &amp;gt; ${TEST_RESULTS_DIR}/device-log.txt"

                    // Take a screenshot for diagnostics (optional)
                    sh "adb -s localhost:${ADB_PORT} shell screencap -p /sdcard/screen.png"
                    sh "adb -s localhost:${ADB_PORT} pull /sdcard/screen.png ${TEST_RESULTS_DIR}/"
                }
            }
        }
    }

    post {
        always {
            // Publish JUnit results
            junit "${TEST_RESULTS_DIR}/**/*.xml"

            // Publish test reports
            publishHTML([
                allowMissing: true,
                alwaysLinkToLastBuild: true,
                keepAll: true,
                reportDir: "${TEST_REPORT_DIR}/connected",
                reportName: 'Espresso Test Report',
                reportFiles: 'index.html'
            ])

            // Stop and remove the container
            script {
                sh "docker stop ${REDROID_CONTAINER} || true"
                sh "docker rm ${REDROID_CONTAINER} || true"
            }

            // Clean up ADB
            sh "adb disconnect localhost:${ADB_PORT} || true"

            // Archive artifacts
            archiveArtifacts artifacts: "${TEST_RESULTS_DIR}/**/*", allowEmptyArchive: true
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scrcpy&lt;/p&gt;

&lt;p&gt;Scrcpy is a lightweight, free, open-source utility for displaying and controlling Android device screens on a computer. It works over USB (ADB) or Wi-Fi without requiring any mobile apps on the device.&lt;/p&gt;

&lt;p&gt;Usage Examples:&lt;/p&gt;

&lt;p&gt;Connect and start (device connected via USB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrcpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect via Wi-Fi (knowing the device's IP address):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adb connect 192.168.1.5:5555
scrcpy -s 192.168.1.5:5555
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change video resolution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrcpy -m 1024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record device video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrcpy --record file.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Control the device without displaying the screen (keyboard and mouse input only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scrcpy -N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need visual monitoring of test execution, you can integrate Scrcpy into the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stage('Visual Debug with Scrcpy') {
    when {
        expression { return params.ENABLE_VISUAL_DEBUG }
    }
    steps {
        script {
            // Install Scrcpy if not already installed
            sh '''
                if ! command -v scrcpy &amp;amp;&amp;gt; /dev/null; then
                    apt-get update &amp;amp;&amp;amp; apt-get install -y scrcpy
                fi
            '''

            // Start Scrcpy to record test execution
            sh "mkdir -p ${TEST_RESULTS_DIR}/recordings"

            for (int i = 0; i &amp;lt; DEVICE_COUNT; i++) {
                def adbPort = BASE_ADB_PORT + i
                sh """
                    nohup scrcpy --no-display --record=${TEST_RESULTS_DIR}/recordings/device-${i}-recording.mp4 \
                    --serial=localhost:${adbPort} &amp;amp;
                """
            }

            // Limit recording duration
            sh "sleep 60"  // Record the first minute of testing
            sh "pkill scrcpy || true"
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kubernetes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Use ReDroid in Kubernetes
&lt;/h3&gt;

&lt;p&gt;ReDroid in Kubernetes simplifies Android app testing. Here are the key benefits:&lt;/p&gt;

&lt;p&gt;Parallel Tests — Run multiple virtual Android devices simultaneously, saving testing time.&lt;/p&gt;

&lt;p&gt;Lower Resource Costs — ReDroid is lightweight, and Kubernetes efficiently distributes the load across servers.&lt;/p&gt;

&lt;p&gt;Automatic Management — The system automatically starts, restarts, and stops Android containers without manual intervention.&lt;/p&gt;

&lt;p&gt;Easy CI/CD Integration — Seamlessly integrates with existing Jenkins or GitLab CI pipelines.&lt;/p&gt;

&lt;p&gt;Test Isolation — Each test environment operates independently without interfering with others.&lt;/p&gt;

&lt;p&gt;Centralized Management — Everything is controlled through a single interface.&lt;/p&gt;

&lt;p&gt;Budget Savings — Virtual devices replace expensive physical phones.&lt;/p&gt;

&lt;p&gt;Consistent Conditions — All tests run in the same environment, improving result reliability.&lt;/p&gt;

&lt;p&gt;Hardware Acceleration Access — Can be configured to use GPU for better performance.&lt;/p&gt;

&lt;p&gt;This solution is ideal for teams needing to regularly test Android apps in different configurations quickly and predictably.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redroid-farm&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;android-testing&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redroid"&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# Number of parallel device instances&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redroid-device&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redroid-device&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Run as root&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redroid&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redroid/redroid:15.0.0_64only-latest&lt;/span&gt;
        &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Required for redroid&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5555&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adb&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDROID_PROP_ro.debuggable&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDROID_PROP_service.adb.tcp.port&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5555"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ANDROID_ARCH&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x86_64"&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3Gi&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6Gi&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dri&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/dri&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kvm&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/kvm&lt;/span&gt;
        &lt;span class="na"&gt;lifecycle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;preStop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/system/bin/reboot"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dri&lt;/span&gt;
        &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/dri&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kvm&lt;/span&gt;
        &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/kvm&lt;/span&gt;
      &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hardware-acceleration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;  &lt;span class="c1"&gt;# Select nodes with GPU/hardware acceleration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wireshark and ReDroid: Capturing and Analyzing Android App Network Traffic
&lt;/h2&gt;

&lt;p&gt;Wireshark combined with ReDroid provides powerful tools for testing and debugging Android app network interactions. This combination is particularly useful for analyzing API requests, checking connection security, and debugging network issues.&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Real Traffic Monitoring — Wireshark lets you see all network packets exchanged between the Android app in ReDroid and external services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy Setup — Since ReDroid runs as a Docker container, its network traffic is easily captured through Docker networking without additional tools on the device itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure Traffic Analysis — With proper setup, you can even inspect HTTPS traffic (using tools like mitmproxy with ReDroid).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance Issue Detection — Easily identify slow requests and app performance delays from network data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automated API Testing — Record and replay network interaction scenarios for testing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Network Setup for Testing:&lt;/p&gt;

&lt;p&gt;Run ReDroid with special network parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --name redroid-test --network host redroid/redroid:11.0.0-latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure traffic capture in Wireshark, filtering for the specific ReDroid container traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ip.addr == [container IP address]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For HTTPS traffic, configure a proxy in ReDroid via ADB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adb connect localhost:5555
adb shell settings put global http_proxy "localhost:8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start mitmproxy for HTTPS decryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mitmproxy --listen-port 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution helps developers and testers better understand how their Android apps interact with the network, identify security issues, and optimize network interactions without needing physical devices or complex emulator setups.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>android</category>
      <category>kotlin</category>
      <category>testing</category>
    </item>
    <item>
      <title>Terraform and Ansible is not enough</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Sat, 17 May 2025 16:32:30 +0000</pubDate>
      <link>https://dev.to/avkr/terraform-and-ansible-is-not-enough-4nf6</link>
      <guid>https://dev.to/avkr/terraform-and-ansible-is-not-enough-4nf6</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/avkcode/ansamble" rel="noopener noreferrer"&gt;Terraform and Ansible is not enough&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tabel of contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Ansible without/with Temporal&lt;/li&gt;
&lt;li&gt;Simple Workflow&lt;/li&gt;
&lt;li&gt;Long-Running Workflows with Checkpointing&lt;/li&gt;
&lt;li&gt;Dynamic Parallel Execution&lt;/li&gt;
&lt;li&gt;Human-in-the-Loop Approvals&lt;/li&gt;
&lt;li&gt;Cross-Cloud Orchestration&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Event-Driven Ansible (EDA) on Steroids&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Stateful Workflows with Recovery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Time Travel Debugging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Cross-Tool Chaining&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;Dynamic Ansible Inventory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=""&gt;GitLab Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Database Migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;When it comes to orchestrating complex workflows in hybrid cloud environments, Temporal stands out as one of the best open-source tools available. It doesn’t try to replace tools like Terraform or Ansible but instead complements them by adding a layer of reliability, scalability, and flexibility.&lt;/p&gt;

&lt;p&gt;For example, while Terraform excels at provisioning infrastructure and Ansible handles configuration management, Temporal ties everything together. It ensures that workflows can recover from failures, adapt to runtime conditions, and integrate seamlessly with external systems like monitoring tools, CMDBs, or approval processes. And because it’s open source, it avoids vendor lock-in while supporting virtually any technology stack.&lt;/p&gt;

&lt;p&gt;The real value of workflow orchestrator lies in its ability to handle long-running, dynamic workflows without skipping a beat. Whether it’s waiting for a VM to pass health checks, triggering a Kubernetes deployment, or updating a CMDB after provisioning, Temporal keeps things running smoothly. It’s not about reinventing the wheel—it’s about making the tools you already use work better, together.&lt;/p&gt;

&lt;p&gt;Another advantage is how Temporal handles long-running processes common in Day 2 operations. For instance, if you’re migrating workloads between clouds or performing a rolling update across a Kubernetes cluster, Temporal keeps track of progress and ensures the workflow resumes from where it left off—even if there’s a failure or interruption. This makes it ideal for tasks like database upgrades, application rollbacks, or compliance audits, where consistency and reliability are critical.&lt;/p&gt;

&lt;p&gt;In short, Temporal helps IT teams move faster and more efficiently without sacrificing control or governance, which is exactly what modern enterprises need in today’s fast-changing hybrid cloud world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ansible with Temporal
&lt;/h3&gt;

&lt;p&gt;Temporal addresses challenges in automating workflows by providing a robust framework for managing complex, long-running processes that require reliability, scalability, and fault tolerance. Without Temporal, running Ansible playbooks or similar automation tasks can face issues like failed executions due to transient errors, lack of visibility into ongoing processes, inability to resume workflows after system crashes, and difficulty in coordinating multiple dependent tasks. Temporal ensures that workflows can recover gracefully from failures, maintain state across retries, and allow dynamic adjustments based on runtime conditions. It also enables seamless integration of different tools and systems within a single workflow, making it easier to orchestrate multi-step operations that involve not just Ansible but potentially other infrastructure or application management tools. By handling the complexities of task coordination, retries, and state management, Temporal allows developers to focus on defining the logic of their workflows rather than worrying about the underlying execution mechanics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    %% Custom Styling
    classDef temporal fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff;
    classDef ansible fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff;
    classDef problem fill:#F44336,stroke:#333,stroke-width:2px,color:#fff;
    classDef solution fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff;

    %% Nodes
    A[Challenges Without Temporal]:::problem --&amp;gt; B["Failed Executions&amp;lt;br&amp;gt;(Transient Errors)"];
    A --&amp;gt; C["Lack of Visibility&amp;lt;br&amp;gt;(No Monitoring)"];
    A --&amp;gt; D["Inability to Resume&amp;lt;br&amp;gt;(Crash Recovery)"];
    A --&amp;gt; E["Coordination Issues&amp;lt;br&amp;gt;(Dependent Tasks)"];

    F[Temporal Framework]:::temporal --&amp;gt; G["Retry Mechanisms&amp;lt;br&amp;gt;(Fault Tolerance)"];
    F --&amp;gt; H["State Management&amp;lt;br&amp;gt;(Persist State Across Failures)"];
    F --&amp;gt; I["Dynamic Adjustments&amp;lt;br&amp;gt;(Runtime Conditions)"];
    F --&amp;gt; J["Multi-Tool Integration&amp;lt;br&amp;gt;(Ansible + Others)"];

    K[Benefits of Temporal]:::solution --&amp;gt; L["Graceful Recovery&amp;lt;br&amp;gt;(From Failures)"];
    K --&amp;gt; M["Maintain State&amp;lt;br&amp;gt;(Across Retries)"];
    K --&amp;gt; N["Orchestrate Multi-Step&amp;lt;br&amp;gt;(Complex Operations)"];
    K --&amp;gt; O["Focus on Logic&amp;lt;br&amp;gt;(Not Execution Mechanics)"];

    %% Flow
    B --&amp;gt;|Solved By| F
    C --&amp;gt;|Solved By| F
    D --&amp;gt;|Solved By| F
    E --&amp;gt;|Solved By| F

    F --&amp;gt;|Enables| K

    %% Subgraph for Grouping Temporal Features
    subgraph TemporalFeatures
        direction TB
        G[Retry Mechanisms]
        H[State Management]
        I[Dynamic Adjustments]
        J[Multi-Tool Integration]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simple workflow
&lt;/h3&gt;

&lt;p&gt;This Python script shows how to use Temporal to run an Ansible playbook, like one that updates system packages on a server. It sets up a workflow and activity to execute the playbook in a reliable way, with features like retries, live output streaming, and clean shutdowns.&lt;/p&gt;

&lt;p&gt;The execute_ansible_playbook activity runs the ansible-playbook command using asyncio to capture and display the output as it happens. You can pass the playbook file and, optionally, an inventory file. If something goes wrong and the playbook fails, the script raises an error, and Temporal automatically retries the operation up to three times before stopping.&lt;/p&gt;

&lt;p&gt;The AnsibleWorkflow defines the workflow logic, which basically calls the activity to run the playbook. It’s set up with timeouts and retry policies to handle cases where the playbook takes a long time or runs into temporary issues.&lt;/p&gt;

&lt;p&gt;When you run the script, it connects to a Temporal server, starts a worker to listen for tasks, and immediately kicks off the workflow to run the playbook you specify. For example, you could point it to a playbook like update_packages.yml, which might use Ansible modules like apt or yum to update packages on your servers. The script ensures the playbook runs smoothly, streams its output in real-time, and handles interruptions or errors without losing track of what’s happening.&lt;/p&gt;

&lt;p&gt;This approach is great for automating tasks like updating packages across servers, where reliability and visibility are key. By using Temporal, you get built-in retries, fault tolerance, and the ability to monitor and debug the process easily if something doesn’t go as planned.&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;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;temporalio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;temporalio.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;temporalio.worker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;temporalio.common&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetryPolicy&lt;/span&gt;

&lt;span class="c1"&gt;# --- Activity Definition ---
&lt;/span&gt;&lt;span class="nd"&gt;@activity.defn&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playbook_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inventory_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Execute ansible-playbook with live output.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&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;ansible-playbook&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;playbook_path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inventory_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inventory_path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_subprocess_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;stdout_chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;stderr_chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stdout_chunk&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stderr_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stdout_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout_chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout_chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stderr_chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr_chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stderr_chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Playbook failed with exit code &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# --- Workflow Definition ---
&lt;/span&gt;&lt;span class="nd"&gt;@workflow.defn&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnsibleWorkflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@workflow.run&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;execute_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;playbook_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;params&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;inventory_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
            &lt;span class="n"&gt;start_to_close_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;retry_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;initial_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;maximum_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;maximum_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_workflow_and_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playbook_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inventory_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Start worker and execute workflow automatically&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;shutdown_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signal_handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;shutdown_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_running_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_signal_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_signal_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Connect to Temporal
&lt;/span&gt;        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:7233&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Start worker
&lt;/span&gt;        &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;task_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ansible-queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;workflows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AnsibleWorkflow&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;activities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;execute_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;graceful_shutdown_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Run worker in background
&lt;/span&gt;        &lt;span class="n"&gt;worker_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="c1"&gt;# Execute workflow
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executing playbook: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;playbook_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;AnsibleWorkflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&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;playbook_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;playbook_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inventory_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inventory_path&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ansible-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;task_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ansible-queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;execution_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Playbook execution result:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Clean shutdown
&lt;/span&gt;        &lt;span class="n"&gt;shutdown_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--playbook&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path to playbook file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--inventory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Path to inventory file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Validate paths
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;playbook&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Playbook not found at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;playbook&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Inventory file not found at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_workflow_and_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;playbook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Process stopped by user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command lists all workflows of type AnsibleWorkflow that are currently tracked by Temporal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tctl workflow list --query "WorkflowType='AnsibleWorkflow'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command starts a new instance of the AnsibleWorkflow to execute the apt_update.yml playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tctl workflow run \
    --taskqueue ansible-queue \
    --workflow_type AnsibleWorkflow \
    --input '"apt_update.yml"'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--inventory&lt;/code&gt; is optional in this case.&lt;/p&gt;

&lt;p&gt;This command retrieves detailed information about a specific workflow instance identified by its workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tctl workflow show -w ansible-1dcbe2e1-8b10-4616-8e11-948cb81d83b1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temporal UI simplifies workflow management by providing an intuitive interface to check statuses, inspect details, and take actions like canceling workflows when necessary.&lt;br&gt;
&lt;a href="https://radikal.host/i/Ir0Q5K" rel="noopener noreferrer"&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%2Fxjuvz5ht5do334akbuxy.png" alt="Temporal" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This GitLab pipeline job, ansible_workflows, automates the execution of an Ansible playbook like apt_update.yml to update system packages. It runs in the deploy stage using a shell executor and triggers a Python script (ansible.py) that orchestrates the playbook via Temporal.&lt;/p&gt;

&lt;p&gt;The script uses Temporal to run the playbook reliably, with features like retries and live output streaming. If the playbook fails, the pipeline reflects the error for easy debugging. This setup integrates infrastructure updates into your CI/CD process, ensuring reliable and automated deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ansible_workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;shell&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3 ansible.py --playbook apt_update.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://radikal.host/i/IGWUQg" rel="noopener noreferrer"&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%2Fr0ypevgwnnyggm3nqrwf.png" alt="GitLab" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Long-Running Workflows with Checkpointing
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible playbooks fail on long-running tasks (e.g., cloud provisioning, multi-hour deployments).&lt;br&gt;
Solution:&lt;br&gt;
Use Temporal workflows to automatically resume from failures.&lt;br&gt;
Checkpoint progress (e.g., "50% of VMs deployed") even if the process crashes.&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="nd"&gt;@workflow.defn&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnsibleWorkflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;playbook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_replaying&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;playbook&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;start_to_close_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;heartbeat_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Retry after 5 minutes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant User
    participant Temporal
    participant Ansible
    User-&amp;gt;&amp;gt;Temporal: Start Workflow
    Temporal-&amp;gt;&amp;gt;Ansible: Run Playbook
    Ansible--&amp;gt;&amp;gt;Temporal: Progress (50% done)
    Note over Temporal: Crash occurs
    Temporal-&amp;gt;&amp;gt;Temporal: Resume from checkpoint
    Temporal-&amp;gt;&amp;gt;Ansible: Retry failed tasks
    Ansible--&amp;gt;&amp;gt;Temporal: Task completed
    Temporal--&amp;gt;&amp;gt;User: Workflow completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Dynamic Parallel Execution
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible’s async is limited—hard to manage 1000s of nodes dynamically.&lt;br&gt;
Solution:&lt;br&gt;
Fan-out/fan-in workflows (e.g., deploy 500 VMs in parallel, then aggregate results).&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deploy_vms&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;vm_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_vm_list_from_api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy_vm.yml --extra-vars &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vm_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&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;vm&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vm_list&lt;/span&gt;&lt;span class="p"&gt;]&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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 plaintext"&gt;&lt;code&gt;graph TD
    A[Start Workflow] --&amp;gt; B[Fetch VM List]
    B --&amp;gt; C[Fan-Out: Deploy VMs in Parallel]
    C --&amp;gt; D[Deploy VM 1]
    C --&amp;gt; E[Deploy VM 2]
    C --&amp;gt; F[Deploy VM 3]
    D --&amp;gt; G[Aggregate Results]
    E --&amp;gt; G
    F --&amp;gt; G
    G --&amp;gt; H[Workflow Completed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Human-in-the-Loop Approvals
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible Tower requires manual static approvals.&lt;br&gt;
Solution:&lt;br&gt;
Dynamic pauses (e.g., "Wait for Slack approval before deleting prod DB").&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="nd"&gt;@workflow.defn&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdDeploymentWorkflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy_staging.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_replaying&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod_approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;send_slack_approval_request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod_approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Blocks until approved
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deploy_prod.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant Workflow
    participant Slack
    participant User
    Workflow-&amp;gt;&amp;gt;Slack: Send Approval Request
    Slack-&amp;gt;&amp;gt;User: Notify for Approval
    User-&amp;gt;&amp;gt;Slack: Approve
    Slack-&amp;gt;&amp;gt;Workflow: Signal Approval
    Workflow-&amp;gt;&amp;gt;Workflow: Proceed to Next Step
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross-Cloud Orchestration
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible alone can’t coordinate AWS + Azure + GCP workflows.&lt;br&gt;
Solution:&lt;br&gt;
Temporal workflows orchestrate multi-cloud playbooks:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate_to_aws&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_shutdown.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_provision.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;check_aws_health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cutover_dns.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Start Workflow] --&amp;gt; B[Shutdown Azure Resources]
    B --&amp;gt; C[Provision AWS Resources]
    C --&amp;gt; D[Check AWS Health]
    D --&amp;gt; E[Update DNS]
    E --&amp;gt; F[Workflow Completed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Event-Driven Ansible (EDA) on Steroids
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible EDA reacts to simple events (e.g., webhooks).&lt;br&gt;
Solution:&lt;br&gt;
Complex event chains (e.g., "If CPU &amp;gt; 90% for 5min, scale + notify PagerDuty + ticket").&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auto_scale_workflow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_cloud_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scale_out.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;page_team&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Cooldown
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant Monitor
    participant Workflow
    participant Ansible
    participant PagerDuty
    Monitor-&amp;gt;&amp;gt;Workflow: CPU &amp;gt; 90% for 5min
    Workflow-&amp;gt;&amp;gt;Ansible: Scale Out
    Workflow-&amp;gt;&amp;gt;PagerDuty: Notify Team
    Workflow-&amp;gt;&amp;gt;Workflow: Cooldown for 5min
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Stateful Workflows with Recovery
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible has no memory of past runs.&lt;br&gt;
Solution:&lt;br&gt;
Temporal remembers state (e.g., "Retry only failed nodes after outage").&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;patch_workflow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_hosts&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;host&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch.yml -l &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ActivityError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;log_failed_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_replaying&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retry_failed_hosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Only retries what crashed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Start Workflow] --&amp;gt; B[Get Host List]
    B --&amp;gt; C[Run Playbook on Host 1]
    B --&amp;gt; D[Run Playbook on Host 2]
    B --&amp;gt; E[Run Playbook on Host 3]
    C --&amp;gt; F[Log Failed Host]
    D --&amp;gt; F
    E --&amp;gt; F
    F --&amp;gt; G[Retry Failed Hosts]
    G --&amp;gt; H[Workflow Completed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Time Travel Debugging
&lt;/h4&gt;

&lt;p&gt;Problem: Debugging Ansible failures is painful.&lt;br&gt;
Solution:&lt;br&gt;
Replay workflows exactly (e.g., "See why playbook failed 3 days ago").&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="c1"&gt;# Temporal UI shows full history + inputs/outputs for every step.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant User
    participant Temporal
    User-&amp;gt;&amp;gt;Temporal: Replay Workflow
    Temporal-&amp;gt;&amp;gt;Temporal: Load History
    Temporal-&amp;gt;&amp;gt;Temporal: Re-execute Steps
    Temporal--&amp;gt;&amp;gt;User: Show Failure Details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross-Tool Chaining
&lt;/h4&gt;

&lt;p&gt;Problem: Ansible can’t seamlessly call Terraform + Kubernetes.&lt;br&gt;
Solution:&lt;br&gt;
Mix tools in one workflow:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deploy_full_stack&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_terraform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_kubectl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apply -f k8s/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_ansible_playbook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configure_ingress.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Start Workflow] --&amp;gt; B[Run Terraform Apply]
    B --&amp;gt; C[Run Kubectl Apply]
    C --&amp;gt; D[Run Ansible Playbook]
    D --&amp;gt; E[Workflow Completed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dynamic Ansible inventory
&lt;/h3&gt;

&lt;p&gt;Here’s how to create a dynamic Ansible inventory using a Temporal workflow that fetches host data from a CMDB via REST API, ensuring real-time, fault-tolerant infrastructure management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Temporal Workflow → Fetches CMDB Data (REST API) → Generates Dynamic Inventory → Runs Ansible
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_cmdb_hosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdb_api_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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="n"&gt;headers&lt;/span&gt; &lt;span class="o"&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmdb_api_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdb_data&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="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&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;_meta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;hostvars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;all&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmdb_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_meta&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;hostvars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ansible_host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&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;ansible_user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&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;ansible_become&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Start Workflow] --&amp;gt; B[Fetch CMDB Data]
    B --&amp;gt; C[Generate Dynamic Inventory]
    C --&amp;gt; D[Run Ansible Playbook]
    D --&amp;gt; E[Workflow Completed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GitLab
&lt;/h4&gt;

&lt;p&gt;To integrate the Temporal-based Ansible workflow with a &lt;strong&gt;GitLab webhook&lt;/strong&gt; , you can configure GitLab to trigger the workflow whenever specific events occur (e.g., a push to a branch, a merge request, or a tag creation).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant GitLab
    participant WebhookHandler
    participant Temporal
    participant Ansible
    GitLab-&amp;gt;&amp;gt;WebhookHandler: Send webhook payload (Push to main)
    WebhookHandler-&amp;gt;&amp;gt;WebhookHandler: Validate payload
    WebhookHandler-&amp;gt;&amp;gt;Temporal: Trigger workflow
    Temporal-&amp;gt;&amp;gt;Ansible: Execute playbook (apt_update.yml)
    Ansible--&amp;gt;&amp;gt;Temporal: Playbook completed
    Temporal--&amp;gt;&amp;gt;WebhookHandler: Workflow completed
    WebhookHandler--&amp;gt;&amp;gt;GitLab: Acknowledge webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Terraform
&lt;/h3&gt;

&lt;p&gt;Using Terraform and Temporal together offers a powerful combination for infrastructure automation, addressing gaps that arise when using either tool in isolation. Terraform excels at declarative infrastructure provisioning but requires custom providers for new or niche services, which can be time-consuming to develop and maintain. On the other hand, Temporal provides a robust orchestration layer for automating workflows, including those involving APIs, without the need for custom providers. For example, you can use Temporal to seamlessly automate interactions with services like Equinix Metal, GoDaddy, or VMware via their REST APIs, orchestrating complex, stateful workflows that Terraform alone cannot handle. While Terraform focuses on defining and managing infrastructure as code, Temporal complements it by enabling dynamic, fault-tolerant, and long-running operations, such as retries, approvals, and cross-service coordination. Together, they allow teams to leverage Terraform's strength in infrastructure provisioning while relying on Temporal for workflow automation, creating a more flexible and scalable solution than using either tool alone.&lt;/p&gt;

&lt;p&gt;An example of a GitLab pipeline (.gitlab-ci.yml) that integrates with the Temporal workflow to deploy a VMware virtual machine using Terraform. This pipeline assumes you have already set up the Temporal server and worker, and it uses the Python-based Temporal workflow provided earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;setup&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform-init&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform-apply&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;temporal-workflow&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;TERRAFORM_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform"&lt;/span&gt;
  &lt;span class="na"&gt;TEMPORAL_TASK_QUEUE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vmware-task-queue"&lt;/span&gt;

&lt;span class="na"&gt;setup-dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.9&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y terraform&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install temporalio requests&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$TERRAFORM_DIR&lt;/span&gt;

&lt;span class="na"&gt;terraform-init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-init&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd $TERRAFORM_DIR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$TERRAFORM_DIR/.terraform&lt;/span&gt;

&lt;span class="na"&gt;terraform-plan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd $TERRAFORM_DIR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform plan -out=tfplan&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;terraform-init&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$TERRAFORM_DIR/tfplan&lt;/span&gt;

&lt;span class="na"&gt;terraform-apply&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-apply&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/terraform:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd $TERRAFORM_DIR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terraform apply -auto-approve tfplan&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;terraform-plan&lt;/span&gt;

&lt;span class="na"&gt;temporal-workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal-workflow&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.9&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install temporalio requests&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git clone https://github.com/your-repo/temporal-vmware-workflow.git&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd temporal-vmware-workflow&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python run_workflow.py --task-queue $TEMPORAL_TASK_QUEUE --terraform-dir $TERRAFORM_DIR&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;terraform-apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Temporal-based workflow for deploying VMware VMs with Terraform is far more flexible than traditional Terraform setups. Unlike plain Terraform, which is limited to provisioning resources, Temporal acts as an orchestration layer that can easily integrate external systems like monitoring tools, CMDBs, and other services. For example, after Terraform provisions a VM, the workflow can automatically update a CMDB, trigger health checks in a monitoring system, or send notifications to stakeholders.&lt;/p&gt;

&lt;p&gt;Temporal also handles retries and failures gracefully, ensuring the workflow continues even if a step like a CMDB update fails. It supports dynamic decision-making, such as querying a monitoring system to decide whether to proceed, and can pause for human approval when needed. This makes it ideal for complex, real-world scenarios where infrastructure deployment doesn’t stop at provisioning—it involves integrating with multiple tools and processes seamlessly.&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="nd"&gt;@activity.defn&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_terraform_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform&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;init&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Terraform init failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="nd"&gt;@activity.defn&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_terraform_apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform&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;apply&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;-auto-approve&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;tfplan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Terraform apply failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database migrations
&lt;/h3&gt;

&lt;p&gt;Large database migrations, especially for production systems with 500GB or more of data, can be incredibly complex and risky. Tools like Liquibase are great for managing schema changes and versioning, but when it comes to executing these migrations reliably—particularly in distributed or hybrid environments—they often fall short on their own. This is where Temporal shines.&lt;/p&gt;

&lt;p&gt;Temporal adds a layer of orchestration that ensures migrations run smoothly, even for massive databases. For example, migrating a 500GB+ production database might involve steps like backing up data, applying schema changes, validating the migration, and rolling back if something goes wrong. Temporal coordinates these steps, retries failed tasks automatically, and keeps track of progress to avoid starting over if the process is interrupted.&lt;/p&gt;

&lt;p&gt;It’s also perfect for handling long-running operations that can take hours or even days. If a migration hits an issue—a network outage or a spike in database load—Temporal can pause the workflow and resume once conditions improve. And because real-world migrations often require human oversight, Temporal can pause for approvals before making critical changes, like altering a schema in production, and then proceed once the green light is given.&lt;/p&gt;

&lt;p&gt;By integrating with monitoring tools, backup systems, and other external services, Temporal ensures everything stays in sync throughout the migration. Whether you’re moving terabytes of data across regions or applying delicate schema updates to a live system, Temporal bridges the gap between Liquibase’s capabilities and the operational complexity of modern database management.&lt;/p&gt;

&lt;p&gt;Below is a single Go file that implements the Temporal workflow for orchestrating a database schema migration using Liquibase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;

    &lt;span class="s"&gt;"go.temporal.io/sdk/activity"&lt;/span&gt;
    &lt;span class="s"&gt;"go.temporal.io/sdk/client"&lt;/span&gt;
    &lt;span class="s"&gt;"go.temporal.io/sdk/worker"&lt;/span&gt;
    &lt;span class="s"&gt;"go.temporal.io/sdk/workflow"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Workflow Input Struct&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MigrationInputs&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;DatabaseURL&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="c"&gt;// Connection URL for the database&lt;/span&gt;
    &lt;span class="n"&gt;ChangelogFile&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="c"&gt;// Path to the Liquibase changelog file&lt;/span&gt;
    &lt;span class="n"&gt;BackupRequired&lt;/span&gt;  &lt;span class="kt"&gt;bool&lt;/span&gt;   &lt;span class="c"&gt;// Whether a backup is needed before migration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Workflow Definition&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;DatabaseMigrationWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="n"&gt;MigrationInputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BackupRequired&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting database backup..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;backupActivity&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BackupDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;backupActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"database backup failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Database backup completed successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting Liquibase schema migration..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;updateActivity&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RunLiquibaseUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LiquibaseInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;DatabaseURL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ChangelogFile&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangelogFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;updateActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibase update failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Liquibase schema migration completed successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Validating the migration..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;validateActivity&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ValidateMigration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;validateActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"migration validation failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Migration validation completed successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Notifying the team..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notifyActivity&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecuteActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotifyTeam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Database migration completed successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;notifyActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to send notification, but migration was successful."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Activity: Backup the database&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BackupDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;databaseURL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pg_dump"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-Fc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/backups/db_backup.dump"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to execute backup command: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Activity: Run Liquibase update&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LiquibaseInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;DatabaseURL&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ChangelogFile&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RunLiquibaseUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;LiquibaseInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"liquibase"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--changeLogFile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangelogFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibase update failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Activity: Validate the migration&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ValidateMigration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;databaseURL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example: Run a query to check if the schema is applied correctly&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"psql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SELECT * FROM information_schema.tables WHERE table_schema = 'public';"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;databaseURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"validation query failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Activity: Notify the team&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NotifyTeam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example: Send a notification via Slack or email&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Notification:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Worker Setup&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Create Temporal client&lt;/span&gt;
    &lt;span class="n"&gt;temporalClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to create Temporal client: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;temporalClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Create a worker&lt;/span&gt;
    &lt;span class="n"&gt;taskQueue&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"database-migration-task-queue"&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temporalClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taskQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="c"&gt;// Register the workflow and activities&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DatabaseMigrationWorkflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BackupDatabase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RunLiquibaseUpdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ValidateMigration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotifyTeam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Start the worker&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting worker..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InterruptCh&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Worker failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>terraform</category>
      <category>ansible</category>
    </item>
    <item>
      <title>Fine-Tuning Models with Your Own Data, Effortlessly</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Wed, 07 May 2025 07:19:37 +0000</pubDate>
      <link>https://dev.to/avkr/fine-tuning-models-with-your-own-data-effortlessly-4j0d</link>
      <guid>https://dev.to/avkr/fine-tuning-models-with-your-own-data-effortlessly-4j0d</guid>
      <description>&lt;p&gt;Most blog posts focus on using top-tier LLMs or setting up complex AI pipelines for large corporations. But what if your data is private, and you don’t have access to top-tier ML talent or massive infrastructure? In this article, we show how to fine-tune a model for mid-sized software development teams or IT support, using your own domain expertise. With &lt;strong&gt;Apache Answer&lt;/strong&gt; and &lt;strong&gt;InstructLab&lt;/strong&gt;, you can build a powerful, cost-effective AI solution tailored to your specific needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  InstructLab
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/instructlab" rel="noopener noreferrer"&gt;InstructLab&lt;/a&gt; is an open-source AI community project aimed at empowering individuals to shape the future of generative AI. It provides tools for users to fine-tune existing large language models (LLMs), such as Granite, using additional data sources. This allows LLMs to continuously gain new knowledge, filling in gaps from their initial training, including real-time updates on current events. Subject matter experts from any domain can contribute to enhancing the LLMs' knowledge. InstructLab’s collaborative platform fosters community-driven improvements and offers tools for experimenting with model updates and ensuring their quality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://arxiv.org/html/2403.01081v1" rel="noopener noreferrer"&gt;LAB: Large-Scale Alignment for ChatBots&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/instructlab/taxonomy" rel="noopener noreferrer"&gt;&lt;strong&gt;taxonomy YAML&lt;/strong&gt;&lt;/a&gt; in InstructLab is a structured file that contains a set of &lt;strong&gt;question-answer pairs&lt;/strong&gt;, organized by domain, to represent specific &lt;strong&gt;skills or knowledge&lt;/strong&gt;. Each YAML file includes metadata like version, task description, contributor info, and example prompts.&lt;/p&gt;

&lt;p&gt;InstructLab uses these YAML files to &lt;strong&gt;generate synthetic training data&lt;/strong&gt; that fine-tunes &lt;strong&gt;open-weight models&lt;/strong&gt; like Qwen or DeepSeek. By aligning the model with curated, domain-specific content, InstructLab helps improve accuracy and relevance in the model's responses across specialized topics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Apache Answer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/apache/answer" rel="noopener noreferrer"&gt;Apache Answer&lt;/a&gt; is similar to Stack Overflow in that it provides a platform for users to ask and answer questions, especially in technical or specialized domains. However, &lt;strong&gt;unlike Stack Overflow, Apache Answer is open source and can be fully self-hosted on-premises&lt;/strong&gt;, giving organizations complete control over their data, customization, and user access.&lt;/p&gt;

&lt;p&gt;While Stack Overflow is a public, centralized platform mainly focused on general programming and tech topics, &lt;strong&gt;Apache Answer can be tailored to any industry or organization&lt;/strong&gt;. It allows companies to build their own internal knowledge-sharing platforms—whether for software engineering teams, legal departments, medical institutions, or customer support operations.&lt;/p&gt;

&lt;p&gt;In essence, Apache Answer offers the same collaborative Q&amp;amp;A experience as Stack Overflow but with &lt;strong&gt;full ownership, flexibility, and adaptability&lt;/strong&gt; for private or specialized use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthetic data
&lt;/h2&gt;

&lt;p&gt;Synthetic data generation is crucial for industries where real-world data cannot be used due to privacy or regulatory concerns. &lt;strong&gt;Apache Answer&lt;/strong&gt;, a Q&amp;amp;A platform similar to StackOverflow, allows organizations to gather relevant industry-specific data and insights from codebases or domain experts. This data can be leveraged for generating high-quality synthetic datasets tailored to specific business needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InstructLab&lt;/strong&gt; enhances this process by fine-tuning models to produce contextually accurate synthetic data, ensuring it reflects real-world scenarios. Combined with &lt;strong&gt;RAG&lt;/strong&gt; (Retrieval-Augmented Generation) and &lt;strong&gt;CAG&lt;/strong&gt; (Cache-Augmented Generation), &lt;strong&gt;InstructLab&lt;/strong&gt; enables both real-time and cached synthetic data generation.&lt;/p&gt;

&lt;p&gt;YAML files produced by &lt;strong&gt;Apache Answer&lt;/strong&gt; contain structured question-answer data that can be used to &lt;strong&gt;fine-tune open-source language models&lt;/strong&gt;. By feeding this data into training workflows, models like &lt;strong&gt;Qwen&lt;/strong&gt; or &lt;strong&gt;DeepSeek&lt;/strong&gt; can be aligned with specific domains or organizational knowledge, improving their accuracy and relevance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/instructlab/taxonomy/blob/main/knowledge/science/animals/birds/black_capped_chickadee/qna.yaml" rel="noopener noreferrer"&gt;Example YAML Q&amp;amp;A&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://radikal.host/i/InnjyW" rel="noopener noreferrer"&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%2F4dw2sw3gd08rnef7l5id.png" alt="apache_answer_flow.png" width="353" height="1096"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  RAG &amp;amp; CAG
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RAG&lt;/strong&gt; (Retrieval-Augmented Generation) is an AI method that finds the most up-to-date information from external sources every time a question is asked. It combines a search system with a language model so the answers are both current and accurate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAG&lt;/strong&gt; (Cache-Augmented Generation) is different. It collects and stores all the needed information in advance. The model then uses this stored data to give fast answers without needing to search each time.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;RAG&lt;/strong&gt;, InstructLab helps improve the model’s ability to follow instructions &lt;em&gt;after&lt;/em&gt; retrieving fresh information. Since RAG fetches real-time data, the model needs to blend that information into a clear, helpful response. InstructLab’s fine-tuning methods make the model better at using that external content effectively.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;CAG&lt;/strong&gt;, where information is pre-loaded into a cache, InstructLab helps train the model to give accurate and helpful answers &lt;em&gt;from that fixed data&lt;/em&gt;. It can improve how well the model uses the cached knowledge by fine-tuning it on examples that mirror expected questions and answers, ensuring faster and more relevant results.&lt;/p&gt;

&lt;p&gt;In short, InstructLab makes models better at following instructions and delivering useful answers — whether the data comes in real-time (RAG) or from a pre-built cache (CAG).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://radikal.host/i/InDhgO" rel="noopener noreferrer"&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%2F4lmi4o7wo5i1ndc0xt35.png" alt="instructlab_flow.png" width="771" height="916"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;InstructLab can be installed locally using &lt;code&gt;pip&lt;/code&gt; or the &lt;code&gt;uv&lt;/code&gt; package manager, making it easy to set up for individual use or testing. Alternatively, it can run inside a Docker container, providing a consistent environment for development and deployment. For scalable production environments, &lt;a href="https://github.com/avkcode/Finetune/blob/main/instructlab.yaml" rel="noopener noreferrer"&gt;InstructLab can also be deployed on Kubernetes&lt;/a&gt;, leveraging its orchestration capabilities to handle scaling and resource management efficiently. This flexibility ensures it adapts to various workflows, from local experimentation to large-scale distributed deployments.&lt;/p&gt;

&lt;p&gt;To deploy InstructLab on Kubernetes, use a Makefile to define and manage Kubernetes resources like Deployments and ConfigMaps as multi-line variables. Dynamically generate labels and annotations using Git metadata and environment variables. Validate configurations, enforce constraints, and apply manifests with &lt;code&gt;kubectl&lt;/code&gt;. This approach avoids Helm's complexity while maintaining flexibility and transparency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/avkr/replace-helm-with-kiss-456a"&gt;Time to replace Helm: Back to the Future&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/avkcode/InstructLab.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using kubectl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f instructlab.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using make:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make =&amp;gt;
InstructLab Management System

Available targets:

Deployment:
  deploy              - Deploy InstructLab with ConfigMap
  undeploy            - Remove InstructLab deployment

Interaction:
  logs                - View container logs
  status              - Show deployment status

Utility:
  help                - Show this help message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;With tools like &lt;strong&gt;Apache Answer&lt;/strong&gt; and &lt;strong&gt;InstructLab&lt;/strong&gt;, you can fine-tune models using your domain knowledge without needing vast resources. You don't need to be a large corporation or have a huge ML team to harness the power of AI for your specific needs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building a Secret Scanner in Julia: A GitLeaks Alternative</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Thu, 24 Apr 2025 15:24:10 +0000</pubDate>
      <link>https://dev.to/avkr/building-a-secret-scanner-in-julia-a-gitleaks-alternative-4egl</link>
      <guid>https://dev.to/avkr/building-a-secret-scanner-in-julia-a-gitleaks-alternative-4egl</guid>
      <description>&lt;p&gt;There is a tool for scanning secrets, passwords, and API key leaks called GitLeaks.&lt;/p&gt;

&lt;p&gt;It’s a very popular project, but in my opinion, its popularity is undeserved. The code quality is questionable, it doesn’t allow specifying custom regex pattern lists for scanning, and it fails to produce proper JSON in stdout, making it useless for automation or backend use.&lt;/p&gt;

&lt;p&gt;This was a great opportunity to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To use &lt;a href="https://julialang.org" rel="noopener noreferrer"&gt;Julia&lt;/a&gt; – one of the best programming languages, which is unfairly considered niche. Its applications go far beyond HPC. It’s perfectly suited for solving a wide range of problems.&lt;/li&gt;
&lt;li&gt;Learn how to properly publish projects on GitHub.&lt;/li&gt;
&lt;li&gt;Learn how to create Linux packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We won’t dwell too long on the code itself. Code is better written or read—discussing it isn’t very interesting.&lt;/p&gt;

&lt;p&gt;The script works as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usage: julia leaquor.jl [options]

Options:
  -h, --help          Display this help message.
  --json              Output results in JSON format.
  --output-file FILE  Write JSON results to the specified file.
  --patterns FILE     Load additional patterns from a YAML file.
  --ignore-files LIST Comma-separated list of files to ignore (e.g., "file1.txt,file2.json").
  --repo URL          Clone and scan a GitHub repository (e.g., https://github.com/user/repo.git).
  --dir PATH          Scan a specific directory on the file system.
  --entropy-threshold FLOAT Set custom entropy threshold (default: 3.5).
  --log-file FILE     Write logs to the specified file.

Arguments:
  Either --repo or --dir must be provided.

Examples:
  julia leaquor.jl --repo https://github.com/user/repo.git --json --output-file out.json
  julia leaquor.jl --dir ./my_project --patterns custom_patterns.yaml | jq .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;--patterns – A YAML file with custom regex patterns for scanning.&lt;br&gt;
--dir – Specifies a directory to scan.&lt;br&gt;
--repo – Allows scanning a GitHub repository directly.&lt;/p&gt;

&lt;p&gt;More interesting is how to properly structure this for GitHub:&lt;/p&gt;
&lt;h1&gt;
  
  
  Docker and DEB Packages
&lt;/h1&gt;

&lt;p&gt;In one &lt;a href="https://github.com/avkcode/leaquor/blob/main/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;, we build the project in Julia. Julia is no different from Python or other more popular languages in this regard.&lt;/p&gt;

&lt;p&gt;In a second &lt;a href="https://github.com/avkcode/leaquor/blob/main/Dockerfile.package" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;, we also build the project but add nfpm a tool that replaces the standard toolchain for DEB and RPM packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate DEB packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;nfpm pkg &lt;span class="nt"&gt;--config&lt;/span&gt; nfpm.yaml &lt;span class="nt"&gt;--target&lt;/span&gt; leaquor.deb

&lt;span class="c"&gt;# Final stage to output packages&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=packager /app/leaquor.deb /leaquor.deb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two directives build and copy the package to the current directory.&lt;/p&gt;

&lt;h1&gt;
  
  
  Make
&lt;/h1&gt;

&lt;p&gt;A &lt;a href="https://github.com/avkcode/leaquor/blob/main/Makefile" rel="noopener noreferrer"&gt;Makefile&lt;/a&gt; is created to handle Docker builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the Docker image&lt;/span&gt;
docker-build:
    @echo &lt;span class="s2"&gt;"Building Docker image &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_IMAGE&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_IMAGE&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run the Docker container&lt;/span&gt;
docker-run:
    @echo &lt;span class="s2"&gt;"Running Docker container &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_CONTAINER&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;PWD&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_IMAGE&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If called without arguments, it displays the help menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make
Available targets:
  &lt;span class="nb"&gt;test&lt;/span&gt;             - Run unit tests
  docker-build     - Build the Docker image
  docker-run       - Run the Docker container
  docker-push      - Push the Docker image to Docker Hub
  docker-clean     - Clean up Docker artifacts &lt;span class="o"&gt;(&lt;/span&gt;images, containers, volumes&lt;span class="o"&gt;)&lt;/span&gt;
  docker-clean-all - Force cleanup of all Docker images and artifacts
  release          - Create a GitHub release
  open-issue       - Open a new issue on GitHub
  create-pr        - Create a pull request
  list-issues      - List open issues
  list-prs         - List open pull requests
  deploy-docs      - Deploy documentation to GitHub Pages
  check-workflows  - Check GitHub Actions workflow status
  generate-changelog - Generate a changelog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command pushes the image to DockerHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Push the Docker image to Docker Hub&lt;/span&gt;
docker-push: docker-build
    @echo &lt;span class="s2"&gt;"Logging into Docker Hub..."&lt;/span&gt;
    @docker login &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_HUB_USERNAME&lt;span class="si"&gt;)&lt;/span&gt;
    @echo &lt;span class="s2"&gt;"Tagging image for Docker Hub..."&lt;/span&gt;
    docker tag &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_IMAGE&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_HUB_REPO&lt;span class="si"&gt;)&lt;/span&gt;:latest
    @echo &lt;span class="s2"&gt;"Pushing image to Docker Hub..."&lt;/span&gt;
    docker push &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_HUB_REPO&lt;span class="si"&gt;)&lt;/span&gt;:latest
    @echo &lt;span class="s2"&gt;"Image pushed successfully to &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_HUB_REPO&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:latest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing stops us from using GitHub CLI to tag the repository, upload it, and create a GitHub release (that’s the section on the left with version numbers like v1.0.0):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Target: Create a Git tag and release on GitHub&lt;/span&gt;
.PHONY: release
release:
    @echo &lt;span class="s2"&gt;"Creating Git tag and releasing on GitHub..."&lt;/span&gt;
    @read &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Enter the version number (e.g., v1.0.0): "&lt;/span&gt; version&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    git tag &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;version &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Release &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;version"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    git push origin &lt;span class="nv"&gt;$$&lt;/span&gt;version&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    gh release create &lt;span class="nv"&gt;$$&lt;/span&gt;version &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;
    @echo &lt;span class="s2"&gt;"Release &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;version created and pushed to GitHub."&lt;/span&gt;

&lt;span class="c"&gt;# Upload .deb package to GitHub release&lt;/span&gt;
upload-release:
    @if &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"leaquor.deb"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: leaquor.deb not found. Please run 'make package' first."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="k"&gt;fi&lt;/span&gt;
    @echo &lt;span class="s2"&gt;"Uploading leaquor.deb to GitHub release &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;RELEASE_TAG&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    gh release upload &lt;span class="si"&gt;$(&lt;/span&gt;RELEASE_TAG&lt;span class="si"&gt;)&lt;/span&gt; leaquor.deb &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;GITHUB_REPO&lt;span class="si"&gt;)&lt;/span&gt;
    @echo &lt;span class="s2"&gt;"Upload complete"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F4wrvra1zgrqcwibwk40h.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%2F4wrvra1zgrqcwibwk40h.png" alt="Release" width="771" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Test
&lt;/h1&gt;

&lt;p&gt;In this case, we simply run the script in a Docker container against a "leaky-repo"—a test repository that definitely contains secret leaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test target to validate the script&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt;: docker-build clone-test-repo
    @echo &lt;span class="s2"&gt;"Running tests..."&lt;/span&gt;
    docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;PWD&lt;span class="si"&gt;)&lt;/span&gt;/&lt;span class="si"&gt;$(&lt;/span&gt;JULIA_SCRIPT&lt;span class="si"&gt;)&lt;/span&gt;:/app/&lt;span class="si"&gt;$(&lt;/span&gt;JULIA_SCRIPT&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;PWD&lt;span class="si"&gt;)&lt;/span&gt;/&lt;span class="si"&gt;$(&lt;/span&gt;TEST_REPO_DIR&lt;span class="si"&gt;)&lt;/span&gt;:/app/&lt;span class="si"&gt;$(&lt;/span&gt;TEST_REPO_DIR&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_CONTAINER&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="si"&gt;$(&lt;/span&gt;DOCKER_IMAGE&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            julia /app/&lt;span class="si"&gt;$(&lt;/span&gt;JULIA_SCRIPT&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; /app/&lt;span class="si"&gt;$(&lt;/span&gt;TEST_REPO_DIR&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--patterns&lt;/span&gt; patterns.yaml &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="nt"&gt;--output-file&lt;/span&gt; results.json &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="nt"&gt;--log-file&lt;/span&gt; /app/test.log
    @echo &lt;span class="s2"&gt;"Tests completed. Check test.log for details."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Pipeline
&lt;/h1&gt;

&lt;p&gt;Finally, to keep everything GitOps-compliant, let’s set up a GitHub pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI/CD Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v2&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to Docker Hub&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; startsWith(github.ref, 'refs/tags/')&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_HUB_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_HUB_TOKEN }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker image&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make docker-build&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make test&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build package (on tag push)&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; startsWith(github.ref, 'refs/tags/')&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make package&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push Docker image to Docker Hub (on tag push)&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' &amp;amp;&amp;amp; startsWith(github.ref, 'refs/tags/')&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;docker tag leaquor-image:latest ${{ secrets.DOCKER_HUB_REPO }}:${{ github.ref_name }}&lt;/span&gt;
        &lt;span class="s"&gt;docker push ${{ secrets.DOCKER_HUB_REPO }}:${{ github.ref_name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, everything is very simple because we wrapped all logic in Make. We can just run make docker-build, make test, and automate it on push.&lt;/p&gt;

&lt;p&gt;In reality, it’s all very straightforward. Julia can be used for standard day-to-day tasks, and Docker and GitHub make it even easier.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>programming</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>YAML templating was a mistake</title>
      <dc:creator>Anton Krylov</dc:creator>
      <pubDate>Fri, 18 Apr 2025 14:49:36 +0000</pubDate>
      <link>https://dev.to/avkr/replace-helm-with-kiss-456a</link>
      <guid>https://dev.to/avkr/replace-helm-with-kiss-456a</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;
Helm

&lt;ul&gt;
&lt;li&gt;No Dependency Hell&lt;/li&gt;
&lt;li&gt;No Helm Release Metadata Bloat&lt;/li&gt;
&lt;li&gt;No Stateful "helm upgrade" Surprises&lt;/li&gt;
&lt;li&gt;No Need for "helm rollback"&lt;/li&gt;
&lt;li&gt;No Helm Hook Complexity&lt;/li&gt;
&lt;li&gt;No Need for "helm-secrets" or External Plugins&lt;/li&gt;
&lt;li&gt;No Helm Chart Size Limitations&lt;/li&gt;
&lt;li&gt;No "Helm Delete" Orphaned Resources&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Advanced Rollback Strategies with Makefiles

&lt;ul&gt;
&lt;li&gt;Atomic Rollbacks with Git Tags&lt;/li&gt;
&lt;li&gt;Differential Rollback (Partial Reverts&lt;/li&gt;
&lt;li&gt;Time-Based Rollback&lt;/li&gt;
&lt;li&gt;Automated Health-Check Rollback&lt;/li&gt;
&lt;li&gt;Why This Beats Helm Rollback&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Kustomize&lt;/li&gt;

&lt;li&gt;ArgoCD&lt;/li&gt;

&lt;li&gt;Other Approaches&lt;/li&gt;

&lt;li&gt;

KISS

&lt;ul&gt;
&lt;li&gt;Debugging and Transparency&lt;/li&gt;
&lt;li&gt;Security Implications&lt;/li&gt;
&lt;li&gt;GitOps Compatibility&lt;/li&gt;
&lt;li&gt;Extensibility&lt;/li&gt;
&lt;li&gt;Community and Maintenance&lt;/li&gt;
&lt;li&gt;Future-Proofing&lt;/li&gt;
&lt;li&gt;Hybrid Templating Flexibility&lt;/li&gt;
&lt;li&gt;Native Integration with CI/CD Pipelines&lt;/li&gt;
&lt;li&gt;Granular Control Over Deployment Order&lt;/li&gt;
&lt;li&gt;Lifecycle Hooks Without CRDs&lt;/li&gt;
&lt;li&gt;Version Control Transparency&lt;/li&gt;
&lt;li&gt;Gradual Adoption Path&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Vault Specific Targets (vault.mk)&lt;/li&gt;

&lt;li&gt;How It Works&lt;/li&gt;

&lt;li&gt;Constraints&lt;/li&gt;

&lt;li&gt;Labels&lt;/li&gt;

&lt;li&gt;Diff&lt;/li&gt;

&lt;li&gt;Helm Charts&lt;/li&gt;

&lt;li&gt;Releases&lt;/li&gt;

&lt;li&gt;Validate&lt;/li&gt;

&lt;li&gt;Feature flags&lt;/li&gt;

&lt;li&gt;Ingress&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Modern Kubernetes deployment methodologies have grown increasingly complex, layering abstraction upon abstraction in pursuit of flexibility. This article challenges that trajectory by examining how fundamental Unix tools combined with Makefiles can provide a more transparent and maintainable alternative to popular solutions like Helm and Kustomize.&lt;/p&gt;

&lt;p&gt;What you'll find here is a way to deploy applications where you can always see the actual YAML being applied to your cluster, where debugging means looking at real configuration rather than guessing what a template might generate, and where changes follow predictable paths rather than disappearing into abstraction layers.&lt;/p&gt;

&lt;p&gt;If you've ever spent an afternoon chasing a Helm templating error only to discover it was caused by a misplaced whitespace, or wasted hours debugging why your Kustomize overlay isn't applying correctly, you'll understand why we need simpler approaches to Kubernetes deployments. The complexity tax we pay with these tools has become too high for what should be straightforward operations.&lt;/p&gt;

&lt;p&gt;What follows is not a prescriptive framework, but rather an examination of core patterns that can be adapted to various deployment scenarios. The techniques shown deliberately avoid tool-specific lock-in, focusing instead on transferable concepts that work across environments and use cases. Whether managing secrets engines, web applications, or data services, the underlying principles remain consistently applicable.&lt;/p&gt;

&lt;p&gt;This is about getting back to basics - not because simple is trendy, but because simple works. When your deployment breaks at 3AM, you'll appreciate being able to understand the system with sleep-deprived eyes rather than fighting through layers of tooling magic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you begin, ensure you have the following tools installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kubectl: Kubernetes command-line tool.&lt;/li&gt;
&lt;li&gt;yq: YAML processor.&lt;/li&gt;
&lt;li&gt;jq: JSON processor.&lt;/li&gt;
&lt;li&gt;git: Version control system.&lt;/li&gt;
&lt;li&gt;make: Build automation tool.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Helm
&lt;/h2&gt;

&lt;p&gt;Helm was designed to simplify Kubernetes application deployment, but it has become another abstraction layer that introduces unnecessary complexity. Helm charts often hide the underlying process with layers of Go templating and nested &lt;code&gt;values.yaml&lt;/code&gt; files, making it difficult to understand what is actually being deployed. Debugging often requires navigating through these files, which can obscure the true configuration. This approach shifts from infrastructure-as-code to something less transparent, making it harder to manage and troubleshoot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.replicas | default 1&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .strategy&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;progressDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.progressDeadlineSeconds | default 600&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.app.selectorLabels" $ | nindent 6&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .extraSelectorLabels&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 6&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.app.selectorLabels" $ | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .extraSelectorLabels&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with $.Values.generic.podLabels&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .podLabels&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with $.Values.generic.podAnnotations&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- with .podAnnotations&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "helpers.pod" (dict "value" . "general" $general "name" $name "extraLabels" .extraSelectorLabels "context" $) | indent 6&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;YAML itself isn’t inherently problematic, and with modern IDE support, schema validation, and linting tools, it can be a clear and effective configuration format. The issues arise when YAML is combined with Go templating, as seen in Helm. While each component is reasonable on its own, their combination creates complexity. Go templates in YAML introduce fragile constructs, where whitespace sensitivity and imperative logic make configurations difficult to read, maintain, and test. This blending of logic and data undermines transparency and predictability, which are crucial in infrastructure management.&lt;/p&gt;

&lt;p&gt;Helm's dependency management also adds unnecessary complexity. Dependencies are fetched into a &lt;code&gt;charts/&lt;/code&gt; directory, but version pinning and overrides often become brittle. Instead of clean component reuse, Helm encourages nested charts with their own &lt;code&gt;values.yaml&lt;/code&gt;, which complicates customization and requires understanding multiple charts to override a single value. In practice, Helm’s dependency management can feel like nesting shell scripts inside other shell scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Dependency Hell
&lt;/h3&gt;

&lt;p&gt;Helm Problem: Charts often depend on other charts (subcharts), leading to version conflicts and complex dependency resolution.&lt;br&gt;
Makefile Solution:&lt;br&gt;
Each component is explicitly defined in the Makefile.&lt;br&gt;
No hidden dependencies—everything is visible in the Git repo.&lt;br&gt;
No helm dependency update surprises.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Explicit dependencies (no magic)
deploy: 
    @kubectl apply -f rbac.yaml
    @kubectl apply -f deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No Helm Release Metadata Bloat
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helm stores release metadata in &lt;strong&gt;Secrets/ConfigMaps&lt;/strong&gt; (e.g., &lt;code&gt;sh.helm.release.v1.*&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Over time, this clutters the cluster with thousands of entries.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hidden metadata—just raw Kubernetes manifests.&lt;/li&gt;
&lt;li&gt;Cleaner cluster state, no orphaned Helm releases.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Helm's hidden release objects (pollutes `kubectl get all`)
kubectl get secrets -l owner=helm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No Stateful "helm upgrade" Surprises
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;helm upgrade&lt;/code&gt; sometimes fails due to stateful behavior (e.g., immutable fields).&lt;/li&gt;
&lt;li&gt;Helm tracks previous releases, which can cause conflicts.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just &lt;code&gt;kubectl apply -f&lt;/code&gt;—no hidden state.&lt;/li&gt;
&lt;li&gt;If a change fails, it’s immediately visible in Git diffs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  No Need for "helm rollback"
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rollbacks require Helm’s release history.&lt;/li&gt;
&lt;li&gt;If Helm metadata is corrupted, rollback fails.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rollback = &lt;code&gt;git revert&lt;/code&gt; + &lt;code&gt;kubectl apply&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No dependency on Helm’s internal state.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rollback with Git + Makefile
git checkout HEAD~1  # Revert to previous version
make apply           # Re-apply old manifests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No Helm Hook Complexity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helm hooks (pre-install, post-upgrade) are powerful but opaque.&lt;/li&gt;
&lt;li&gt;Difficult to debug when hooks fail.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hooks = Makefile targets (&lt;code&gt;pre-install&lt;/code&gt;, &lt;code&gt;post-deploy&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Full visibility into what runs when.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy: pre-check apply-manifests verify
pre-check:
    @./scripts/check-dependencies.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No Need for "helm-secrets" or External Plugins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managing secrets requires plugins like &lt;code&gt;helm-secrets&lt;/code&gt; (sops/vault).&lt;/li&gt;
&lt;li&gt;Adds another layer of tooling.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets handled natively (e.g., &lt;code&gt;kubectl create secret&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Works with any secret manager (Vault, AWS Secrets Manager).
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create-secret:
    @vault read -field=token secret/vault-token | kubectl create secret generic vault-token --from-file=token=-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No Helm Chart Size Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large charts (e.g., 1000+ resources) slow down &lt;code&gt;helm install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Helm has to process all templates sequentially.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parallel manifest generation (&lt;code&gt;make -j&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;No artificial limits—just what &lt;code&gt;kubectl&lt;/code&gt; can handle.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  No "Helm Delete" Orphaned Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm Problem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;helm delete&lt;/code&gt; sometimes leaves resources behind (e.g., CRDs).&lt;/li&gt;
&lt;li&gt;Requires &lt;code&gt;--purge&lt;/code&gt; or manual cleanup.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Makefile Solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;make delete&lt;/code&gt; removes &lt;strong&gt;exactly&lt;/strong&gt; what was defined.&lt;/li&gt;
&lt;li&gt;No surprises.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delete:
    @kubectl delete -f manifests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Rollback Strategies with Makefiles
&lt;/h2&gt;

&lt;p&gt;While the basic git checkout + make apply works, let's explore more robust rollback techniques that give you greater control than Helm's built-in rollback mechanism.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Atomic Rollbacks with Git Tags
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Tag releases during deployment
make deploy &amp;amp;&amp;amp; git tag $(date +%Y%m%d-%H%M)-vault-deploy

# Rollback to last known good version
git checkout $(git describe --tags --match '*-vault-deploy' --abbrev=0)
make apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Differential Rollback (Partial Reverts)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# In Makefile
rollback-%:  # Rollback specific resource
    git show HEAD~1:manifests/$*.yaml | kubectl apply -f -

# Example: Rollback just the deployment
make rollback-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Time-Based Rollback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Find the last good config before outage
git checkout $(git rev-list -n1 --before="2023-12-01 15:00" main)
make apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated Health-Check Rollback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rollback-with-check:
    @make apply
    @sleep 30  # Wait for pods to initialize
    @if ! kubectl rollout status deployment/vault --timeout=60s; then \
        git checkout HEAD~1; \
        make apply; \
        echo "Automatic rollback completed"; \
    fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why This Beats Helm Rollback&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Hidden State&lt;/li&gt;
&lt;li&gt;Helm stores rollback info in cluster secrets&lt;/li&gt;
&lt;li&gt;Makefiles use Git's immutable history&lt;/li&gt;
&lt;li&gt;Partial Rollbacks&lt;/li&gt;
&lt;li&gt;Helm rolls back entire releases&lt;/li&gt;
&lt;li&gt;Make can target specific resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pre-Rollback Verification&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pre-rollback-check:
    @git diff HEAD~1 manifests/ | grep -q 'replicaCount: 3' &amp;amp;&amp;amp; \
      echo "WARNING: Rolling back to 3 replicas"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    User-&amp;gt;&amp;gt;Git: git tag v1.0
    User-&amp;gt;&amp;gt;Cluster: make apply
    Cluster-&amp;gt;&amp;gt;User: Deployment fails
    User-&amp;gt;&amp;gt;Git: git checkout v1.0
    User-&amp;gt;&amp;gt;Cluster: make apply
    Cluster-&amp;gt;&amp;gt;User: Previous working version restored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Advantages Over Helm&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Helm&lt;/th&gt;
&lt;th&gt;Makefile + Git&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rollback Target&lt;/td&gt;
&lt;td&gt;Entire release&lt;/td&gt;
&lt;td&gt;Any resource/file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History Storage&lt;/td&gt;
&lt;td&gt;Cluster secrets&lt;/td&gt;
&lt;td&gt;Git repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full diff/pre-checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation&lt;/td&gt;
&lt;td&gt;Basic hooks&lt;/td&gt;
&lt;td&gt;Custom health checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit Trail&lt;/td&gt;
&lt;td&gt;Helm metadata&lt;/td&gt;
&lt;td&gt;Git commit history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Medium (needs helm history)&lt;/td&gt;
&lt;td&gt;Fast (direct git checkout)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial Rollbacks&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-Rollback Checks&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Custom scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Environment&lt;/td&gt;
&lt;td&gt;Complex (per-cluster)&lt;/td&gt;
&lt;td&gt;Simple (git branches)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required Infrastructure&lt;/td&gt;
&lt;td&gt;Helm server (v2)&lt;/td&gt;
&lt;td&gt;Just git+kubectl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disaster Recovery&lt;/td&gt;
&lt;td&gt;Vulnerable (secrets can be lost)&lt;/td&gt;
&lt;td&gt;Immutable (git)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Pro Tip: Add this to your Makefile for safer rollbacks:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirm-rollback:
    @read -p "Rollback to HEAD~1? (y/n) " ans; \
    [ "$$ans" = y ] || exit 1

rollback: confirm-rollback
    git checkout HEAD~1 manifests/
    make apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kustomzie
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kubernetes-sigs/kustomize" rel="noopener noreferrer"&gt;Kustomize&lt;/a&gt; offers a declarative approach to managing Kubernetes configurations, but its structure often blurs the line between declarative and imperative. Kustomize applies transformations to a base set of Kubernetes manifests, where users define overlays and patches that &lt;em&gt;appear&lt;/em&gt; declarative, but are actually order-dependent and procedural.&lt;/p&gt;

&lt;p&gt;It supports various patching mechanisms, which require a deep understanding of Kubernetes objects and can lead to verbose, hard-to-maintain configurations. Features like generators pulling values from files or environment variables introduce dynamic behavior, further complicating the system. When built-in functionality falls short, users can use KRM (Kubernetes Resource Model) functions for transformations, but these are still defined in structured data, leading to a complex layering of data-as-code that lacks clarity.&lt;/p&gt;

&lt;p&gt;While Kustomize avoids explicit templating, it introduces a level of orchestration that can be just as opaque and requires extensive knowledge to ensure predictable results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argocd
&lt;/h2&gt;

&lt;p&gt;In many Kubernetes environments, the configuration pipeline has become a complex chain of tools and abstractions. What the Kubernetes API receives — plain YAML or JSON — is often the result of multiple intermediate stages, such as Helm charts, Helmsman, or GitOps systems like Flux or Argo CD. As these layers accumulate, they can obscure the final output, preventing engineers from easily accessing the fully rendered manifests.&lt;/p&gt;

&lt;p&gt;This lack of visibility makes it hard to verify what will actually be deployed, leading to operational challenges and a loss of confidence in the system. When teams cannot inspect or reproduce the deployment artifacts, it becomes difficult to review changes or troubleshoot issues, ultimately turning a once-transparent process into a black box that complicates debugging and undermines reliability.&lt;/p&gt;

&lt;p&gt;ArgoCD is designed to work with plain Kubernetes manifests, making it naturally compatible with Makefile-generated deployments. Unlike Helm or Kustomize which require special plugins, our Makefile approach produces standard YAML/JSON that ArgoCD can deploy natively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Git Repository] --&amp;gt;|Manifests| B(ArgoCD Application)
    B --&amp;gt; C{Kubernetes Cluster}
    D[Makefile] --&amp;gt;|Generates| A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure ArgoCD Application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://github.com/your-org/vault-deployments.git'&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifests/&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HEAD&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://kubernetes.default.svc'&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other approaches
&lt;/h2&gt;

&lt;p&gt;Apple’s &lt;a href="https://pkl-lang.org/index.html" rel="noopener noreferrer"&gt;pkl&lt;/a&gt; (short for "Pickle") is a configuration language designed to replace YAML, offering greater flexibility and dynamic capabilities. It includes features like classes, built-in packages, methods, and bindings for multiple languages, as well as IDE integrations, making it resemble a full programming language rather than a simple configuration format.&lt;/p&gt;

&lt;p&gt;However, the complexity of pkl may be unnecessary. Its extensive documentation and wide range of features may be overkill for most use cases, especially when YAML itself can handle configuration management needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  KISS
&lt;/h2&gt;

&lt;p&gt;Kubernetes configuration management is ultimately a string manipulation problem. Makefiles, combined with standard Unix tools, are ideal for solving this. Make provides a declarative way to define steps to generate Kubernetes manifests, with each step clearly outlined and only re-run when necessary. Tools like &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, and &lt;code&gt;jq&lt;/code&gt; excel at text transformation and complement Make’s simplicity, allowing for quick manipulation of YAML or JSON files.&lt;/p&gt;

&lt;p&gt;This approach is transparent — you can see exactly what each command does and debug easily when needed. Unlike more complex tools, which hide the underlying processes, Makefiles and Unix tools provide full control, making the configuration management process straightforward and maintainable.&lt;/p&gt;

&lt;p&gt;HashiCorp Vault is a tool for managing secrets and sensitive data, offering features like encryption, access control, and secure storage. It was used as an example of critical infrastructure deployed on Kubernetes without Helm, emphasizing manual, customizable management of resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/avkcode/vault/refs/heads/main/Makefile" rel="noopener noreferrer"&gt;This Makefile&lt;/a&gt; automates Kubernetes deployment, Docker image builds, and Git operations. It handles environment-specific configurations, validates Kubernetes manifests, and manages Vault resources like Docker image builds, retrieving unseal/root keys, and interacting with Vault pods. It also facilitates Git operations such as creating tags, pushing releases, and generating archives or bundles. The file includes tasks for managing Kubernetes resources like services, statefulsets, and secrets, switching namespaces, and cleaning up generated files. Additionally, it supports interactive deployment sessions, variable listing, and manifest validation both client and server-side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running make without any targets outputs the help:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;make&lt;/span&gt;
&lt;span class="nl"&gt;Available targets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="err"&gt;generate-chart&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Generate&lt;/span&gt; &lt;span class="err"&gt;Helm&lt;/span&gt; &lt;span class="err"&gt;chart&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt;
  &lt;span class="err"&gt;template&lt;/span&gt;          &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Generate&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;templates&lt;/span&gt;
  &lt;span class="err"&gt;apply&lt;/span&gt;             &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Apply&lt;/span&gt; &lt;span class="err"&gt;generated&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;cluster&lt;/span&gt;
  &lt;span class="err"&gt;delete&lt;/span&gt;            &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Delete&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;resources&lt;/span&gt; &lt;span class="err"&gt;defined&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt;
  &lt;span class="err"&gt;validate-%&lt;/span&gt;        &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Validate&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;specific&lt;/span&gt; &lt;span class="err"&gt;manifest&lt;/span&gt; &lt;span class="err"&gt;using&lt;/span&gt; &lt;span class="err"&gt;yq,&lt;/span&gt; &lt;span class="err"&gt;e.g.&lt;/span&gt; &lt;span class="err"&gt;make&lt;/span&gt; &lt;span class="err"&gt;validate-rbac&lt;/span&gt;
  &lt;span class="err"&gt;print-%&lt;/span&gt;           &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Print&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;value&lt;/span&gt; &lt;span class="err"&gt;of&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;specific&lt;/span&gt; &lt;span class="err"&gt;variable&lt;/span&gt;
  &lt;span class="err"&gt;get-vault-ui&lt;/span&gt;      &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Fetch&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Vault&lt;/span&gt; &lt;span class="err"&gt;UI&lt;/span&gt; &lt;span class="err"&gt;Node&lt;/span&gt; &lt;span class="err"&gt;IP&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;NodePort&lt;/span&gt;
  &lt;span class="err"&gt;build-vault-image&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Build&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Vault&lt;/span&gt; &lt;span class="err"&gt;Docker&lt;/span&gt; &lt;span class="err"&gt;image&lt;/span&gt;
  &lt;span class="err"&gt;exec&lt;/span&gt;              &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Execute&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;shell&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;vault&lt;/span&gt; &lt;span class="err"&gt;pod&lt;/span&gt;
  &lt;span class="err"&gt;logs&lt;/span&gt;              &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Stream&lt;/span&gt; &lt;span class="err"&gt;logs&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;vault&lt;/span&gt; &lt;span class="err"&gt;pod&lt;/span&gt;
  &lt;span class="err"&gt;switch-namespace&lt;/span&gt;  &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Switch&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;current&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;namespace&lt;/span&gt;
  &lt;span class="err"&gt;archive&lt;/span&gt;           &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Create&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;git&lt;/span&gt; &lt;span class="err"&gt;archive&lt;/span&gt;
  &lt;span class="err"&gt;bundle&lt;/span&gt;            &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Create&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;git&lt;/span&gt; &lt;span class="err"&gt;bundle&lt;/span&gt;
  &lt;span class="err"&gt;clean&lt;/span&gt;             &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Clean&lt;/span&gt; &lt;span class="err"&gt;up&lt;/span&gt; &lt;span class="err"&gt;generated&lt;/span&gt; &lt;span class="err"&gt;files&lt;/span&gt;
  &lt;span class="err"&gt;release&lt;/span&gt;           &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Create&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;Git&lt;/span&gt; &lt;span class="err"&gt;tag&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;release&lt;/span&gt; &lt;span class="err"&gt;on&lt;/span&gt; &lt;span class="err"&gt;GitHub&lt;/span&gt;
  &lt;span class="err"&gt;get-vault-keys&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Initialize&lt;/span&gt; &lt;span class="err"&gt;Vault&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;retrieve&lt;/span&gt; &lt;span class="err"&gt;unseal&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;root&lt;/span&gt; &lt;span class="err"&gt;keys&lt;/span&gt;
  &lt;span class="err"&gt;show-params&lt;/span&gt;       &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Show&lt;/span&gt; &lt;span class="err"&gt;contents&lt;/span&gt; &lt;span class="err"&gt;of&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;parameter&lt;/span&gt; &lt;span class="err"&gt;file&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;current&lt;/span&gt; &lt;span class="err"&gt;environment&lt;/span&gt;
  &lt;span class="err"&gt;interactive&lt;/span&gt;       &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;an&lt;/span&gt; &lt;span class="err"&gt;interactive&lt;/span&gt; &lt;span class="err"&gt;session&lt;/span&gt;
  &lt;span class="err"&gt;create-release&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Create&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;secret&lt;/span&gt; &lt;span class="err"&gt;with&lt;/span&gt; &lt;span class="err"&gt;VERSION&lt;/span&gt; &lt;span class="err"&gt;set&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;Git&lt;/span&gt; &lt;span class="err"&gt;commit&lt;/span&gt; &lt;span class="err"&gt;SHA&lt;/span&gt;
  &lt;span class="err"&gt;remove-release&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Remove&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;dynamically&lt;/span&gt; &lt;span class="err"&gt;created&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;secret&lt;/span&gt;
  &lt;span class="err"&gt;dump-manifests&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Dump&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;both&lt;/span&gt; &lt;span class="err"&gt;YAML&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;JSON&lt;/span&gt; &lt;span class="err"&gt;formats&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;current&lt;/span&gt; &lt;span class="err"&gt;directory&lt;/span&gt;
  &lt;span class="err"&gt;convert-to-json&lt;/span&gt;   &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Convert&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;JSON&lt;/span&gt; &lt;span class="err"&gt;format&lt;/span&gt;
  &lt;span class="err"&gt;validate-server&lt;/span&gt;   &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Validate&lt;/span&gt; &lt;span class="err"&gt;JSON&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;against&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;API&lt;/span&gt; &lt;span class="err"&gt;(server-side)&lt;/span&gt;
  &lt;span class="err"&gt;validate-client&lt;/span&gt;   &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Validate&lt;/span&gt; &lt;span class="err"&gt;JSON&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;against&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Kubernetes&lt;/span&gt; &lt;span class="err"&gt;API&lt;/span&gt; &lt;span class="err"&gt;(client-side)&lt;/span&gt;
  &lt;span class="err"&gt;list-vars&lt;/span&gt;         &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;List&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;non-built-in&lt;/span&gt; &lt;span class="err"&gt;variables,&lt;/span&gt; &lt;span class="err"&gt;their&lt;/span&gt; &lt;span class="err"&gt;origins,&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;values.&lt;/span&gt;
  &lt;span class="err"&gt;package&lt;/span&gt;           &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Create&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;tar.gz&lt;/span&gt; &lt;span class="err"&gt;archive&lt;/span&gt; &lt;span class="err"&gt;of&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;entire&lt;/span&gt; &lt;span class="err"&gt;directory&lt;/span&gt;
  &lt;span class="err"&gt;diff&lt;/span&gt;              &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Interactive&lt;/span&gt; &lt;span class="err"&gt;diff&lt;/span&gt; &lt;span class="err"&gt;selection&lt;/span&gt; &lt;span class="err"&gt;menu&lt;/span&gt;
  &lt;span class="err"&gt;diff-live&lt;/span&gt;         &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Compare&lt;/span&gt; &lt;span class="err"&gt;live&lt;/span&gt; &lt;span class="err"&gt;cluster&lt;/span&gt; &lt;span class="err"&gt;state&lt;/span&gt; &lt;span class="err"&gt;with&lt;/span&gt; &lt;span class="err"&gt;generated&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt;
  &lt;span class="err"&gt;diff-previous&lt;/span&gt;     &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Compare&lt;/span&gt; &lt;span class="err"&gt;previous&lt;/span&gt; &lt;span class="err"&gt;applied&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;with&lt;/span&gt; &lt;span class="err"&gt;current&lt;/span&gt; &lt;span class="err"&gt;generated&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt;
  &lt;span class="err"&gt;diff-revisions&lt;/span&gt;    &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Compare&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;between&lt;/span&gt; &lt;span class="err"&gt;two&lt;/span&gt; &lt;span class="err"&gt;git&lt;/span&gt; &lt;span class="err"&gt;revisions&lt;/span&gt;
  &lt;span class="err"&gt;diff-environments&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Compare&lt;/span&gt; &lt;span class="err"&gt;manifests&lt;/span&gt; &lt;span class="err"&gt;between&lt;/span&gt; &lt;span class="err"&gt;two&lt;/span&gt; &lt;span class="err"&gt;environments&lt;/span&gt;
  &lt;span class="err"&gt;diff-params&lt;/span&gt;       &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Compare&lt;/span&gt; &lt;span class="err"&gt;parameters&lt;/span&gt; &lt;span class="err"&gt;between&lt;/span&gt; &lt;span class="err"&gt;two&lt;/span&gt; &lt;span class="err"&gt;environments&lt;/span&gt;
  &lt;span class="err"&gt;help&lt;/span&gt;              &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Display&lt;/span&gt; &lt;span class="err"&gt;this&lt;/span&gt; &lt;span class="err"&gt;help&lt;/span&gt; &lt;span class="err"&gt;message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Debugging and Transparency&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm/Kustomize&lt;/strong&gt;: Debugging requires understanding intermediate states (e.g., &lt;code&gt;helm template --debug&lt;/code&gt;, &lt;code&gt;kustomize build&lt;/code&gt;). Errors are often opaque (e.g., "template rendering failed").&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make&lt;/strong&gt;: Every step is explicit. You can inspect intermediate files (e.g., &lt;code&gt;manifest.yaml&lt;/code&gt;) or run targets like &lt;code&gt;make print-rbac&lt;/code&gt; to debug.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Debug a Helm error:
helm template vault --debug | less  # Scroll through rendered YAML

# Debug with Make:
make template &amp;gt; debug.yaml &amp;amp;&amp;amp; code debug.yaml  # Directly inspect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With makefile-based approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Print any variable - usage: make print-VARIABLE
&lt;/span&gt;&lt;span class="nl"&gt;print-%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'$*=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;$*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'  origin = &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;origin $*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'  flavor = &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;flavor $*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'  value  = &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;value $*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make print-DOCKER_IMAGE
DOCKER_IMAGE=hashicorp/vault:1.18.0
  origin: file
  flavor: recursive
  value: hashicorp/vault:1.18.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Security Implications&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm&lt;/strong&gt;: Dynamic templating can introduce injection risks (e.g., untrusted &lt;code&gt;values.yaml&lt;/code&gt;). Helm 3 improved security by removing Tiller, but charts still execute arbitrary logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make&lt;/strong&gt;: Static manifests are safer. Secrets can be managed separately (e.g., &lt;code&gt;sops&lt;/code&gt;, &lt;code&gt;vault-agent&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Secure secret handling with Make:
get-secrets:
    @vault read -field=token secret/vault-token &amp;gt; .env
    @kubectl create secret generic vault-secret --from-file=.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;GitOps Compatibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm/Kustomize&lt;/strong&gt;: Tightly integrated with ArgoCD/Flux but require custom plugins for advanced features (e.g., Helm secrets).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make&lt;/strong&gt;: Works natively with GitOps tools. Manifests are plain YAML/JSON, making drift detection easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example GitOps workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# With Make:
git add manifests/
git commit -m "Update vault config"
flux reconcile source git vault-repo

# Vs. Helm:
helm repo update
helm upgrade vault --values overrides.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Extensibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm/Kustomize&lt;/strong&gt;: Extending requires learning their DSLs (Go templates, Kustomize patches).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make&lt;/strong&gt;: Easily extended with shell/python scripts. For example, adding Terraform integration:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy-infra:
    @terraform apply -auto-approve
    @$(MAKE) apply  # Deploy Kubernetes resources after infra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Community and Maintenance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helm&lt;/strong&gt;: Large ecosystem but charts often lag behind upstream releases (e.g., &lt;code&gt;stable/vault&lt;/code&gt; is deprecated).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make&lt;/strong&gt;: No dependency hell. You control the toolchain.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Helm: Upgrading a chart might break dependencies
helm upgrade vault --version 1.17.0  # Might fail due to subchart conflicts

# Make: Just update the image tag in Makefile
DOCKER_IMAGE=hashicorp/vault:1.17.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Future-Proofing&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Makefiles&lt;/strong&gt; are 40+ years old and won’t disappear. Helm/Kustomize might be replaced (e.g., by &lt;code&gt;cdk8s&lt;/code&gt;, &lt;code&gt;pkl&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes-native alternatives&lt;/strong&gt;: Tools like &lt;code&gt;kpt&lt;/code&gt; or &lt;code&gt;carvel&lt;/code&gt; are emerging, but Make remains a stable baseline.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hybrid Templating Flexibility
&lt;/h3&gt;

&lt;p&gt;Combine Unix tools like envsubst, yq, or jq for templating only where needed, avoiding Helm's "all-or-nothing" templating. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;template: 
    envsubst &amp;lt; template.yaml &amp;gt; manifest.yaml
    yq e '.metadata.labels += {"env": "${ENV}"}' -i manifest.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Native Integration with CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;Makefiles work seamlessly with any CI system (GitHub Actions, GitLab CI) without requiring plugins or custom runners. Each target (make validate, make apply) can be a standalone CI step.&lt;br&gt;
Contrast with Helm/Kustomize, which often need specialized CI steps (e.g., helm template | kubectl apply).&lt;/p&gt;
&lt;h3&gt;
  
  
  Granular Control Over Deployment Order
&lt;/h3&gt;

&lt;p&gt;Makefiles allow explicit definition of dependencies between deployment steps (e.g., ensuring secrets are created before deployments). This avoids race conditions common in Helm/Kustomize where resource ordering can be unpredictable.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make
deploy: create-secrets apply-manifests
create-secrets:
    kubectl apply -f secrets/
apply-manifests:
    kubectl apply -f manifests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lifecycle Hooks Without CRDs
&lt;/h3&gt;

&lt;p&gt;Execute pre/post-deployment tasks (e.g., database migrations) natively in Makefiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy: preflight apply-manifests notify
preflight:
    ./scripts/check-dependencies.sh
notify:
    curl -X POST ${SLACK_WEBHOOK}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Version Control Transparency
&lt;/h3&gt;

&lt;p&gt;Every change (Makefile, scripts, manifests) is visible in Git history. Helm/Kustomize obfuscate changes through templating or patches, making git diff less informative&lt;/p&gt;

&lt;h3&gt;
  
  
  Gradual Adoption Path
&lt;/h3&gt;

&lt;p&gt;Can incrementally replace Helm/Kustomize by starting with make helm-template targets, then phasing out Helm as needed. No "big bang" migration &lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Make + Unix Tools over ytt
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero New Syntax&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makefiles use standard shell commands vs learning Starlark (Python-like)&lt;/li&gt;
&lt;li&gt;Example debugging:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="c"&gt;# Make approach (transparent)&lt;/span&gt;
 &lt;span class="nb"&gt;cat &lt;/span&gt;generated/manifest.yaml

 &lt;span class="c"&gt;# ytt approach (requires special commands)&lt;/span&gt;
 ytt template &lt;span class="nt"&gt;-f&lt;/span&gt; config/ &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct Cluster Interaction&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native &lt;code&gt;kubectl&lt;/code&gt; integration without intermediate steps:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt; &lt;span class="nl"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; manifests/
     kubectl rollout status deployment/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;ytt requires additional tools like &lt;code&gt;kapp&lt;/code&gt; for deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bare Metal Transparency&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See exact YAML being applied (no hidden transformations):
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; +---------------------+
 | Makefile Output     |
 +---------------------+
 | apiVersion: v1      |
 | kind: ConfigMap     |
 | data:               |
 |   key: plain value  |
 +---------------------+
&lt;span class="err"&gt;
&lt;/span&gt; vs
&lt;span class="err"&gt;
&lt;/span&gt; +---------------------+
 | ytt Output          |
 +---------------------+
 | #@data/values       |
 | ---                 |
 | #@overlay/match ... |
 +---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simpler CI/CD Integration&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No special binaries needed - works with any Linux/Unix agent:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="c1"&gt;# GitLab CI example&lt;/span&gt;
 &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make generate&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make apply ENV=prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;ytt requires custom container images with Carvel tools&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance with Large Configs&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Benchmark comparison (1000 resources):
| Operation       | Make   | ytt   |
|----------------|--------|-------|
| Generation     | 0.8s   | 2.3s  |
| Diff           | 0.2s   | 1.1s  |
| Memory Usage   | 50MB   | 210MB |&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Emergency Debugging&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct access to intermediate files:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="c"&gt;# During outage:&lt;/span&gt;
 make template &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; emergency.yaml
 vi emergency.yaml &lt;span class="c"&gt;# immediate edits&lt;/span&gt;
 kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; emergency.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bash Ecosystem Integration&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leverage existing tools without wrappers:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt; &lt;span class="nl"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s1"&gt;'.[] | select(.kind == "Deployment")'&lt;/span&gt; manifests/&lt;span class="k"&gt;*&lt;/span&gt;.yaml
     kubectl apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nt"&gt;-f&lt;/span&gt; manifests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Vault specific targets (vault.mk):
&lt;/h3&gt;

&lt;p&gt;The include directive in Makefiles allows for modular and organized build systems by nesting multiple Makefiles. In this case, all Vault-specific functionality is encapsulated in the vault.mk file, which can be included in a primary Makefile. This approach enables clear separation of concerns and better maintainability, while providing a unified interface to access all Vault-related targets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make vault-help
...
Available Targets:
  enable-metrics        Enable Prometheus metrics endpoint
  create-backup         Create a manual backup of Vault's Raft storage
  restore-backup        Restore Vault from a backup
  enable-audit          Enable Vault audit logging
  scale-vault           Scale Vault cluster replicas
  upgrade-vault         Upgrade Vault version
  enable-auto-unseal    Configure Vault for auto-unseal
  enable-raft           Enable Raft storage backend
  enable-namespace      Enable Vault namespaces
  enable-ldap           Enable LDAP authentication
  enable-oidc           Enable OIDC authentication
  enable-k8s-auth       Enable Kubernetes authentication
  enable-transit        Enable Transit secrets engine
  enable-pki            Enable PKI secrets engine
  enable-aws            Enable AWS secrets engine
  enable-database       Enable Database secrets engine
  enable-consul         Enable Consul secrets engine
  enable-ssh            Enable SSH secrets engine
  enable-totp           Enable TOTP secrets engine
  enable-kv             Enable Key/Value secrets engine
  enable-transform      Enable Transform secrets engine

Utility Targets:
  build-vault-image     Build a custom Vault Docker image
  get-vault-ui          Fetch Vault UI access details (Node IP and NodePort)
  get-vault-keys        Retrieve unseal and root keys for a specific Vault pod
  exec                  Open an interactive shell in a specific Vault pod
  logs                  Stream logs from a specific Vault pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;In a Makefile, multiline variables (defined using define) can store arbitrary data such as YAML or JSON, making it convenient to embed Kubernetes manifests or other configuration files directly within the Makefile. These variables, like rbac, configmap, or statefulset, can be added to a manifests array, which acts as a collection of all resources to be processed. The manifests array is then iterated over by utility targets such as template, apply, and delete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="nv"&gt;configmap&lt;/span&gt;
&lt;span class="err"&gt;---&lt;/span&gt;
&lt;span class="nl"&gt;apiVersion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;v1&lt;/span&gt;
&lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ConfigMap&lt;/span&gt;
&lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vault-config&lt;/span&gt;
  &lt;span class="nl"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;${VAULT_NAMESPACE}&lt;/span&gt;
&lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nl"&gt;extraconfig-from-values.hcl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;|-&lt;/span&gt;
    disable_mlock &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
    ui &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;${&lt;/span&gt;VAULT_UI&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="err"&gt;listener&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;tls_disable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 1
      &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"[::]:8200"&lt;/span&gt;
      &lt;span class="nv"&gt;cluster_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"[::]:8201"&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;storage&lt;/span&gt; &lt;span class="s2"&gt;"file"&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/vault/data"&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;${TELEMETRY_CONFIG}&lt;/span&gt;
&lt;span class="k"&gt;endef&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;configmap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template target outputs the contents of each manifest to the console for review.&lt;br&gt;
The apply target pipes each manifest into kubectl apply, deploying the resources to the cluster.&lt;br&gt;
The delete target pipes each manifest into kubectl delete, removing the resources from the cluster.&lt;br&gt;
This approach allows for flexible and dynamic management of Kubernetes resources, enabling the inclusion and processing of any number of YAML or JSON configurations stored in multiline variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;manifests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;$${&lt;/span&gt;rbac&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;manifests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;$${&lt;/span&gt;configmap&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;manifests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;$${&lt;/span&gt;services&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;manifests&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;$${&lt;/span&gt;statefulset&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;template apply delete&lt;/span&gt;

&lt;span class="c"&gt;# Outputs the generated Kubernetes manifests to the console.
&lt;/span&gt;&lt;span class="nl"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach manifest,&lt;span class="p"&gt;$(&lt;/span&gt;manifests&lt;span class="p"&gt;)&lt;/span&gt;,echo &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt;

&lt;span class="c"&gt;# Applies the generated Kubernetes manifests to the cluster using `kubectl apply`.
&lt;/span&gt;&lt;span class="nl"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;create-release&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach manifest,&lt;span class="p"&gt;$(&lt;/span&gt;manifests&lt;span class="p"&gt;)&lt;/span&gt;,echo &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="p"&gt;;)&lt;/span&gt;

&lt;span class="c"&gt;# Deletes the Kubernetes resources defined in the generated manifests using `kubectl delete`.
&lt;/span&gt;&lt;span class="nl"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;remove-release&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach manifest,&lt;span class="p"&gt;$(&lt;/span&gt;manifests&lt;span class="p"&gt;)&lt;/span&gt;,echo &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="p"&gt;;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run make apply, several steps are executed in sequence to apply the Kubernetes manifests to your cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------+     +----------------+     +-----------------+     +---------------+
|  make apply   | --&amp;gt; |  Create Release| --&amp;gt; | Apply Manifests | --&amp;gt; | Output Status |
+---------------+     +----------------+     +-----------------+     +---------------+
     |                      |                        |                       |
     |                      v                        v                       v
     |               +----------------+       +-----------------+     +---------------+
     +-------------&amp;gt; | Store Version  |       | Apply Resources |     | Check Status  |
                     | Secret in K8s  |       | (RBAC/SVC/etc)  |     |               |
                     +----------------+       +-----------------+     +---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time you run make apply, the Makefile is designed to automatically trigger the create-release target as part of the process. This ensures that a Kubernetes secret is created with the current Git commit SHA, which helps track the version of the app being deployed. By including this step, the Makefile guarantees that the release information is always up-to-date and stored in the cluster whenever the manifests are applied. This makes it easier to identify which version of the app is running and maintain consistency across deployments. Make delete triggers remove-release target.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;global.param&lt;/code&gt; file contains shared parameters that apply to all environments unless explicitly overridden. For example, it might define default values for &lt;code&gt;VAULT_NAMESPACE&lt;/code&gt;, &lt;code&gt;DOCKER_IMAGE&lt;/code&gt;, or resource allocation (&lt;code&gt;CPU_REQUEST&lt;/code&gt;, &lt;code&gt;MEMORY_REQUEST&lt;/code&gt;, etc.).&lt;br&gt;
&lt;code&gt;global.param&lt;/code&gt;&lt;br&gt;
This Makefile contains configuration variables for deploying HashiCorp Vault in a Kubernetes environment. It allows customization of deployment parameters such as namespace, Docker image, resource allocation (CPU and memory), and optional features like the Vault UI and Istio sidecar injection.&lt;/p&gt;

&lt;p&gt;dev.param - enviroment variables&lt;br&gt;
It’s possible to override parameters via CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make apply \
VAULT_NAMESPACE=my-vault-namespace \
DOCKER_IMAGE=hashicorp/vault:1.17.0 \
REPLICA_NUM=3 \
CPU_REQUEST="4000m" \
MEMORY_REQUEST="1024Mi" \
ENABLE_ISTIO_SIDECAR='false'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The dump-manifests target in the Makefile lets you output raw Kubernetes YAML files that can be used in different ways. You can apply these YAML files directly to a cluster using kubectl apply -f, making it easy to deploy changes manually. The same files also work well with tools like ArgoCD, Flux, or Spinnaker, giving you flexibility in how you manage deployments. This approach keeps things simple and avoids being locked into any specific tool or framework. The YAML files show exactly what will be deployed, making it easier to review, back up, or move between environments without surprises.&lt;/p&gt;




&lt;p&gt;Using Make with tools like &lt;code&gt;curl&lt;/code&gt; is a super flexible way to handle Kubernetes deployments, and it can easily replace some of the things Helm does. For example, instead of using Helm charts to manage releases, we’re just using &lt;code&gt;kubectl&lt;/code&gt; in a Makefile to create and delete Kubernetes secrets. By running simple shell commands and using &lt;code&gt;kubectl&lt;/code&gt;, we can manage things like versioning and configuration directly in Kubernetes without all the complexity of Helm. This approach gives us more control and is lighter weight, which is perfect for projects where you want simplicity and flexibility without the overhead of managing full Helm charts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;create-release&lt;/span&gt;
&lt;span class="nl"&gt;create-release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating Kubernetes secret with VERSION set to Git commit SHA..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-version-secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;JSON_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;VERSION&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;GIT_COMMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl create secret generic &lt;span class="nv"&gt;$$&lt;/span&gt;SECRET_NAME &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;version.json&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;JSON_DATA"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; -
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secret created successfully: app-version-secret"&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;remove-release&lt;/span&gt;
&lt;span class="nl"&gt;remove-release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deleting Kubernetes secret: app-version-secret..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-version-secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl delete secret &lt;span class="nv"&gt;$$&lt;/span&gt;SECRET_NAME 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secret deleted successfully: app-version-secret"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the &lt;code&gt;manifests&lt;/code&gt; array contains all the Kubernetes resource definitions, we can easily dump them into both YAML and JSON formats. The &lt;code&gt;dump-manifests&lt;/code&gt; target runs &lt;code&gt;make template&lt;/code&gt; to generate the YAML output and &lt;code&gt;make convert-to-json&lt;/code&gt; to convert the same output into JSON. By redirecting the output to &lt;code&gt;manifest.yaml&lt;/code&gt; and &lt;code&gt;manifest.json&lt;/code&gt;, you're able to keep both versions of the resources for further use. It’s a simple and efficient way to generate multiple formats from the same set of manifests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dump-manifests&lt;/span&gt;
&lt;span class="nl"&gt;dump-manifests&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;template convert-to-json&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Dumping manifests to manifest.yaml and manifest.json..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;make template &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; manifest.yaml
    &lt;span class="p"&gt;@&lt;/span&gt;make convert-to-json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; manifest.json
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Manifests successfully dumped to manifest.yaml and manifest.json."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;validate-%&lt;/code&gt; target, you can easily validate any specific manifest by piping it through &lt;code&gt;yq&lt;/code&gt; to check the structure or content in a readable format. This leverages external tools like &lt;code&gt;yq&lt;/code&gt; to validate and process YAML directly within the Makefile, without needing to write complex scripts. Similarly, the &lt;code&gt;print-%&lt;/code&gt; target allows you to quickly print the value of any Makefile variable, giving you an easy way to inspect variables or outputs. By using external tools like &lt;code&gt;yq&lt;/code&gt;, you can enhance the flexibility of your Makefile, making it easy to validate, process, and manipulate manifests directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validates a specific manifest using `yq`.
&lt;/span&gt;&lt;span class="nl"&gt;validate-%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; -

&lt;span class="c"&gt;# Prints the value of a specific variable.
&lt;/span&gt;&lt;span class="nl"&gt;print-%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Makefile and simple Bash scripting, you can easily implement auxiliary functions like getting Vault keys. In this case, the &lt;code&gt;get-vault-keys&lt;/code&gt; target lists available Vault pods, prompts for the pod name, and retrieves the Vault unseal key and root token by executing commands on the chosen pod. The approach uses basic tools like &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt;, and Bash, making it much more flexible than dealing with Helm’s syntax or other complex tools. It simplifies the process and gives you full control over your deployment logic without having to rely on heavyweight tools or charts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get-vault-keys&lt;/span&gt;
&lt;span class="nl"&gt;get-vault-keys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Available Vault pods:"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;PODS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$$(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-l&lt;/span&gt; app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;vault &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.items[*].metadata.name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;PODS"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Enter the Vault pod name (e.g., vault-0): "&lt;/span&gt; POD_NAME&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;PODS"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qw&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;POD_NAME"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;POD_NAME &lt;span class="nt"&gt;--&lt;/span&gt; vault operator init &lt;span class="nt"&gt;-key-shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-key-threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; keys.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nv"&gt;VAULT_UNSEAL_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;keys_&lt;span class="nv"&gt;$$&lt;/span&gt;POD_NAME.json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;".unseal_keys_b64[]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unseal Key: &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;VAULT_UNSEAL_KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nv"&gt;VAULT_ROOT_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;keys.json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;".root_token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Root Token: &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;VAULT_ROOT_KEY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Pod '&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;POD_NAME' not found."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Constraints
&lt;/h2&gt;

&lt;p&gt;When managing complex workflows, especially in DevOps or Kubernetes environments, constraints play a vital role in ensuring consistency, preventing errors, and maintaining control over the build process. In Makefiles, constraints can be implemented to validate inputs, restrict environment configurations, and enforce best practices. Let’s explore how this works with a practical example.&lt;/p&gt;

&lt;p&gt;What Are Constraints in Makefiles?&lt;/p&gt;

&lt;p&gt;Constraints are rules or conditions that ensure only valid inputs or configurations are accepted during execution. For instance, you might want to limit the environments (dev, sit, uat, prod) where your application can be deployed, or validate parameter files before proceeding with Kubernetes manifest generation.&lt;/p&gt;

&lt;p&gt;Example: Restricting Environment Configurations&lt;/p&gt;

&lt;p&gt;Consider the following snippet from the provided Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;ENV&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; dev
&lt;span class="nv"&gt;ALLOWED_ENVS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; global dev sit uat prod

&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(filter $(ENV),$(ALLOWED_ENVS)),)&lt;/span&gt;
    &lt;span class="nl"&gt;$(error Invalid ENV value '$(ENV)'. Allowed values are&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(ALLOWED_ENVS))&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s how this works:&lt;/p&gt;

&lt;p&gt;Default Value : The ENV variable defaults to dev if not explicitly set.&lt;br&gt;
Allowed Values : The ALLOWED_ENVS variable defines a list of valid environments.&lt;br&gt;
Validation Check : The ifeq block checks if the provided ENV value exists in the ALLOWED_ENVS list. If not, it throws an error and stops execution.&lt;br&gt;
 For example:&lt;/p&gt;

&lt;p&gt;Running make apply ENV=test will fail because test is not in the allowed list.&lt;br&gt;
Running make apply ENV=prod will proceed as prod is valid.&lt;/p&gt;

&lt;p&gt;This snippet validates that the MEMORY_REQUEST and MEMORY_LIMIT values are within the acceptable range of 128Mi to 4096Mi. It extracts the numeric value, converts units (e.g., Gi to Mi), and checks if the values fall within the specified bounds. If not, it raises an error to prevent invalid configurations from being applied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate memory ranges (e.g., 128Mi &amp;lt;= MEMORY_REQUEST &amp;lt;= 4096Mi)
&lt;/span&gt;&lt;span class="nv"&gt;MEMORY_REQUEST_VALUE&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;subst Mi,,&lt;span class="p"&gt;$(&lt;/span&gt;subst Gi,,&lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_REQUEST&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nv"&gt;MEMORY_REQUEST_UNIT&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;suffix &lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_REQUEST&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(MEMORY_REQUEST_UNIT),Gi)&lt;/span&gt;
  &lt;span class="nv"&gt;MEMORY_REQUEST_VALUE&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_REQUEST_VALUE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1024&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(shell [ $(MEMORY_REQUEST_VALUE) -ge 128 ] &amp;amp;&amp;amp; [ $(MEMORY_REQUEST_VALUE) -le 4096 ] &amp;amp;&amp;amp; echo true),)&lt;/span&gt;
  &lt;span class="nf"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;error&lt;/span&gt; Invalid MEMORY_REQUEST value &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;MEMORY_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; It must be between 128Mi and 4096Mi.&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="nv"&gt;MEMORY_LIMIT_VALUE&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;subst Mi,,&lt;span class="p"&gt;$(&lt;/span&gt;subst Gi,,&lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_LIMIT&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nv"&gt;MEMORY_LIMIT_UNIT&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;suffix &lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_LIMIT&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(MEMORY_LIMIT_UNIT),Gi)&lt;/span&gt;
  &lt;span class="nv"&gt;MEMORY_LIMIT_VALUE&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;MEMORY_LIMIT_VALUE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1024&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(shell [ $(MEMORY_LIMIT_VALUE) -ge 128 ] &amp;amp;&amp;amp; [ $(MEMORY_LIMIT_VALUE) -le 4096 ] &amp;amp;&amp;amp; echo true),)&lt;/span&gt;
  &lt;span class="nf"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;error&lt;/span&gt; Invalid MEMORY_LIMIT value &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;MEMORY_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; It must be between 128Mi and 4096Mi.&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Labels
&lt;/h2&gt;

&lt;p&gt;Using Make for Kubernetes deployments offers greater flexibility compared to Helm, as it allows for fully customizable workflows without enforcing a rigid structure. While Helm is designed specifically for Kubernetes and provides a standardized templating system, it can feel restrictive for non-standard or highly dynamic use cases. With Make, you can dynamically generate lists of labels and annotations programmatically, avoiding the need to manually define them in manifests. This approach ensures consistency and reduces repetitive work by leveraging variables, environment data, and tools like Git metadata. Additionally, Make integrates seamlessly with external scripts and tools, enabling more complex logic and automation that Helm’s opinionated framework might not easily support.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract Git metadata
&lt;/span&gt;&lt;span class="nv"&gt;GIT_BRANCH&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_COMMIT&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_REPO_URL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git config &lt;span class="nt"&gt;--get&lt;/span&gt; remote.origin.url &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"local-repo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ENV_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.environment:&lt;span class="p"&gt;$(&lt;/span&gt;ENV&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_BRANCH_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/branch:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_BRANCH&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_COMMIT_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/commit:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_COMMIT&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_REPO_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/repo:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_REPO_URL&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TEAM_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.team:devops
&lt;span class="nv"&gt;OWNER_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.owner:engineering
&lt;span class="nv"&gt;DEPLOYMENT_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.deployment:nginx-controller
&lt;span class="nv"&gt;VERSION_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.version:v1.23.0
&lt;span class="nv"&gt;BUILD_TIMESTAMP_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.build-timestamp:&lt;span class="p"&gt;$(&lt;/span&gt;shell &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%SZ"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;RELEASE_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.release:stable
&lt;span class="nv"&gt;REGION_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.region:us-west-2
&lt;span class="nv"&gt;ZONE_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.zone:a
&lt;span class="nv"&gt;CLUSTER_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.cluster:eks-prod
&lt;span class="nv"&gt;SERVICE_TYPE_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.service-type:LoadBalancer
&lt;span class="nv"&gt;INSTANCE_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.instance:nginx-controller-instance

&lt;span class="c"&gt;# Combine all labels into a single list
&lt;/span&gt;&lt;span class="nv"&gt;LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;ENV_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;GIT_BRANCH_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;GIT_COMMIT_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;GIT_REPO_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;TEAM_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;OWNER_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;DEPLOYMENT_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;VERSION_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;BUILD_TIMESTAMP_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;RELEASE_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;REGION_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;ZONE_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;CLUSTER_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;SERVICE_TYPE_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;INSTANCE_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;DESCRIPTION_LABEL&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="nv"&gt;generate_labels&lt;/span&gt;
&lt;span class="nl"&gt;$(shell printf "    %s\n" $(patsubst %&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;%: &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;$(LABELS)))&lt;/span&gt;
&lt;span class="k"&gt;endef&lt;/span&gt;

&lt;span class="c"&gt;# Example target to print the labels in YAML format
&lt;/span&gt;&lt;span class="nl"&gt;print-labels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"metadata:"&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  labels:"&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach label,&lt;span class="p"&gt;$(&lt;/span&gt;LABELS&lt;span class="p"&gt;)&lt;/span&gt;,echo &lt;span class="s2"&gt;"    &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;shell echo &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | sed 's/:/=/; s/=/:&lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="s2"&gt;/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make -f test.mk
metadata:
  labels:
    app.environment:
    app.git/branch: main
    app.git/commit: 0158245
    app.git/repo: https://github.com/avkcode/vault.git
    app.team: devops
    app.owner: engineering
    app.deployment: nginx-controller
    app.version: v1.23.0
    app.build-timestamp: 2025-04-22T12:38:58Z
    app.release: stable
    app.region: us-west-2
    app.zone: a
    app.cluster: eks-prod
    app.service-type: LoadBalancer
    app.instance: nginx-controller-instance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using with manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="nv"&gt;deployment&lt;/span&gt;
&lt;span class="err"&gt;---&lt;/span&gt;
&lt;span class="nl"&gt;apiVersion&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;apps/v1&lt;/span&gt;
&lt;span class="nl"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Deployment&lt;/span&gt;
&lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nl"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="nf"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; generate_labels&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;release-name-ingress-nginx-controller&lt;/span&gt;
  &lt;span class="nl"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;
&lt;span class="nl"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nl"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/instance: release-name
      app.kubernetes.io/component: controller
  &lt;span class="nl"&gt;replicas&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;1&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;endef&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With general-purpose coding, dynamically generating labels based on parameters like target environment (DEV/PROD/STAGE) is straightforward—just define rules and inject values at runtime. Tools aren't needed for such simple string manipulation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make validate-configmap
---
apiVersion: v1
 kind: ConfigMap
 metadata:
 labels: app.environment:dev     app.git/branch:main     app.git/commit:af4eae9     app.git/repo:https://github.com/avkcode/nginx.git     app.team:devops     app.critical:false     app.sla:best-effort
 name: release-name-ingress-nginx-controller
 namespace: default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default DEV dev &lt;code&gt;app.team:devops app.critical:false app.sla:best-effort&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Adds &lt;code&gt;app.team:devops app.critical:true app.sla:tier-1&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ENV=prod make validate-configmap
---
apiVersion: v1
kind: ConfigMap
metadata:
  labels: app.environment:prod     app.git/branch:main     app.git/commit:af4eae9     app.git/repo:https://github.com/avkcode/nginx.git     app.team:devops     app.critical:true     app.sla:tier-1
  name: release-name-ingress-nginx-controller
  namespace: default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate and set environment
&lt;/span&gt;&lt;span class="nv"&gt;VALID_ENVS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; dev sit uat prod
&lt;span class="nv"&gt;ENV&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; dev

&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(filter $(ENV),$(VALID_ENVS)),)&lt;/span&gt;
&lt;span class="nl"&gt;$(error Invalid ENV. Valid values are&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(VALID_ENVS))&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="c"&gt;# Environment-specific settings
&lt;/span&gt;&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(ENV),prod)&lt;/span&gt;
&lt;span class="nv"&gt;APP_PREFIX&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; prod
&lt;span class="nv"&gt;EXTRA_LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.critical:true app.sla:tier-1
&lt;span class="err"&gt;else&lt;/span&gt; &lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(ENV),uat)&lt;/span&gt;
&lt;span class="nv"&gt;APP_PREFIX&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; uat
&lt;span class="nv"&gt;EXTRA_LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.critical:false app.sla:tier-2
&lt;span class="err"&gt;else&lt;/span&gt; &lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(ENV),sit)&lt;/span&gt;
&lt;span class="nv"&gt;APP_PREFIX&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; sit
&lt;span class="nv"&gt;EXTRA_LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.critical:false app.sla:tier-3
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="nv"&gt;APP_PREFIX&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; dev
&lt;span class="nv"&gt;EXTRA_LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.critical:false app.sla:best-effort
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="c"&gt;# Extract Git metadata
&lt;/span&gt;&lt;span class="nv"&gt;GIT_BRANCH&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_COMMIT&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_REPO&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;shell git config &lt;span class="nt"&gt;--get&lt;/span&gt; remote.origin.url&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ENV_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.environment:&lt;span class="p"&gt;$(&lt;/span&gt;ENV&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_BRANCH_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/branch:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_BRANCH&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_COMMIT_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/commit:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_COMMIT&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_REPO_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.git/repo:&lt;span class="p"&gt;$(&lt;/span&gt;GIT_REPO&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TEAM_LABEL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; app.team:devops

&lt;span class="nv"&gt;LABELS&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;ENV_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GIT_BRANCH_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GIT_COMMIT_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;GIT_REPO_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;TEAM_LABEL&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;EXTRA_LABELS&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="nv"&gt;generate_labels&lt;/span&gt;
&lt;span class="nl"&gt;$(shell printf "    %s\n" $(patsubst %&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;%: &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;$(LABELS)))&lt;/span&gt;
&lt;span class="k"&gt;endef&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  diff
&lt;/h2&gt;

&lt;p&gt;Advanced diff capabilities to compare Kubernetes manifests across different states: live cluster vs generated, previous vs current, git revisions, and environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;diff - Interactive diff menu&lt;/li&gt;
&lt;li&gt;diff-live - Compare live cluster vs generated manifests&lt;/li&gt;
&lt;li&gt;diff-previous - Compare previous vs current manifests&lt;/li&gt;
&lt;li&gt;diff-revisions - Compare between git revisions&lt;/li&gt;
&lt;li&gt;diff-environments - Compare manifests across environments&lt;/li&gt;
&lt;li&gt;diff-params - Compare parameter files between environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://radikal.host/i/IW7oZX" rel="noopener noreferrer"&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%2Fs75e51jf5i752c9489ow.png" alt="diff" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm charts
&lt;/h2&gt;

&lt;p&gt;If you’re absolutely required to distribute a Helm chart but don’t have one pre-made, no worries—it’s totally possible to generate one from the manifests produced by this Makefile. The gen_helm_chart.py script (referenced via include helm.mk) automates this process. It takes the Kubernetes manifests generated by the Makefile and packages them into a Helm chart. This way, you can still meet the requirement for a Helm chart while leveraging the existing templates and workflows in the Makefile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Releases
&lt;/h2&gt;

&lt;p&gt;The Makefile approach to managing releases offers a streamlined and transparent alternative to tools like Helm, focusing on simplicity and direct control over the deployment process. Releases are handled through a combination of Git tags, Kubernetes secrets, and environment-specific configurations, ensuring that every step is visible, auditable, and easy to debug.&lt;/p&gt;

&lt;p&gt;Releases are initiated by creating a Git tag, which serves as an immutable reference point for the deployed version. This ensures that every release is tied to a specific state in the repository's history, making rollbacks straightforward and reliable. The release target automates this process by prompting for a version number, tagging the repository, pushing the tag to the remote, and creating a corresponding GitHub release with autogenerated notes. This eliminates the need for Helm's internal release metadata, avoiding bloat in the cluster and keeping the process lightweight.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;release&lt;/span&gt;
&lt;span class="nl"&gt;release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating Git tag and releasing on GitHub..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Enter the version number (e.g., v1.0.0): "&lt;/span&gt; version&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    git tag &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;version &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Release &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;version"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    git push origin &lt;span class="nv"&gt;$$&lt;/span&gt;version&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    gh release create &lt;span class="nv"&gt;$$&lt;/span&gt;version &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Release &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;version created and pushed to GitHub."&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;create-release&lt;/span&gt;
&lt;span class="nl"&gt;create-release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating Kubernetes secret with VERSION set to Git commit SHA..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-version-secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;JSON_DATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;VERSION&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;GIT_COMMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl create secret generic &lt;span class="nv"&gt;$$&lt;/span&gt;SECRET_NAME &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;version.json&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;JSON_DATA"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; -
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secret created successfully: app-version-secret"&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;remove-release&lt;/span&gt;
&lt;span class="nl"&gt;remove-release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deleting Kubernetes secret: app-version-secret..."&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-version-secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl delete secret &lt;span class="nv"&gt;$$&lt;/span&gt;SECRET_NAME 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secret deleted successfully: app-version-secret"&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;show-release&lt;/span&gt;
&lt;span class="nl"&gt;show-release&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;SECRET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-version-secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl get secret &lt;span class="nv"&gt;$$&lt;/span&gt;SECRET_NAME &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.version\.json}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .VERSION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validate
&lt;/h2&gt;

&lt;p&gt;The Makefile-based validation approach provides a clear and direct method for ensuring the correctness of Kubernetes manifests. Unlike Helm which often obscures the actual YAML behind layers of templating logic and requires specific commands like helm template or helm lint to validate charts, the Makefile approach allows for explicit validation of individual manifests using standard tools like yq. This makes it easier to identify and fix issues since there are no hidden abstractions or complex dependency chains to navigate.&lt;/p&gt;

&lt;p&gt;The validation process in Makefiles is tightly integrated with the deployment workflow. Specific targets like validate-% enable focused validation of particular resources while server-side and client-side validation targets provide comprehensive checks against the Kubernetes API. These validation steps can be seamlessly incorporated into CI/CD pipelines without requiring additional tooling or plugins, offering a more native and flexible solution compared to Helm's specialized requirements.&lt;/p&gt;

&lt;p&gt;Furthermore, the Makefile approach avoids complications such as Helm release metadata bloat or stateful upgrade surprises that can interfere with validation. Since manifests remain in their raw form and changes are tracked through Git, the validation process benefits from version control transparency and an immutable history. This ensures that any rollback or differential validation is straightforward and reliable, unlike Helm's dependency on internal state stored in cluster secrets. The result is a simpler yet robust validation mechanism that aligns with the principle of keeping Kubernetes deployments transparent and maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specific Manifest Validation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;validate-%&lt;/code&gt; target allows validation of individual manifests. It takes a manifest name as an argument and processes it through &lt;code&gt;yq&lt;/code&gt; to check its structure and syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;validate-%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server-Side Validation
&lt;/h3&gt;

&lt;p&gt;The validate-server target performs server-side validation of all manifests against the Kubernetes API. This ensures that manifests are compatible with the actual cluster configuration. Each manifest is converted to JSON using yq and then validated using kubectl apply with the --dry-run=server option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;validate-server&lt;/span&gt;
&lt;span class="nl"&gt;validate-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Validating JSON manifests against the Kubernetes API (server-side validation)..."&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach manifest,&lt;span class="p"&gt;$(&lt;/span&gt;manifests&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Validating manifest: &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; - | kubectl apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All JSON manifests passed server-side validation successfully."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client-Side Validation
&lt;/h3&gt;

&lt;p&gt;The validate-client target conducts client-side validation of all manifests against the Kubernetes API. Similar to server-side validation, each manifest is converted to JSON format and checked using kubectl apply but with the --dry-run=client option. This provides a quick validation without contacting the API server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;validate-client&lt;/span&gt;
&lt;span class="nl"&gt;validate-client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Validating JSON manifests against the Kubernetes API (client-side validation)..."&lt;/span&gt;
    &lt;span class="p"&gt;@$(&lt;/span&gt;foreach manifest,&lt;span class="p"&gt;$(&lt;/span&gt;manifests&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Validating manifest: &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | yq &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;-P&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; - | kubectl apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All JSON manifests passed client-side validation successfully."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feature flags
&lt;/h2&gt;

&lt;p&gt;Flags that can be toggled on/off per environment. The Makefile approach gives you surgical control over Kubernetes configurations without the indirection of Helm values files or Kustomize patches. Here's how we can extend the environment-specific constraints with feature flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# Feature flags - enabled by environment
&lt;/span&gt;&lt;span class="nv"&gt;ENABLED_FEATURES&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;filter prod uat,&lt;span class="p"&gt;$(&lt;/span&gt;ENV&lt;span class="p"&gt;))&lt;/span&gt;,monitoring&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;filter prod,&lt;span class="p"&gt;$(&lt;/span&gt;ENV&lt;span class="p"&gt;))&lt;/span&gt;,auto-scaling&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;filter dev sit,&lt;span class="p"&gt;$(&lt;/span&gt;ENV&lt;span class="p"&gt;))&lt;/span&gt;,debug-tools&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Conditional includes based on features
&lt;/span&gt;&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(filter monitoring,$(ENABLED_FEATURES)),monitoring)&lt;/span&gt;
  &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="sx"&gt; monitoring.mk&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;ifeq&lt;/span&gt; &lt;span class="nv"&gt;($(filter auto-scaling,$(ENABLED_FEATURES)),auto-scaling)&lt;/span&gt;
  &lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="sx"&gt; hpa.mk&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;dev&lt;/th&gt;
&lt;th&gt;sit&lt;/th&gt;
&lt;th&gt;uat&lt;/th&gt;
&lt;th&gt;prod&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;monitoring&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auto-scaling&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;debug-tools&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The real power comes when combining this with the GitOps flow:&lt;br&gt;
&lt;a href="https://radikal.host/i/MTJaDa" rel="noopener noreferrer"&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%2Fuzlwa7mhbw6fq20a2sqi.png" alt="Feature Flags" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Git Blame for Infrastructure Changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git blame Makefile
^d34172a (Alice Devops 2024-03-15 14:22:03 -0500 1) # Feature flags by environment
d34172a (Alice Devops 2024-03-15 14:22:03 -0500 2) ENABLED_FEATURES := \
a5f2e1b (Bob Security 2024-04-01 09:15:47 -0500 3)   $(if $(filter prod uat,$(ENV)),monitoring) \
a5f2e1b (Bob Security 2024-04-01 09:15:47 -0500 4)   $(if $(filter prod,$(ENV)),auto-scaling) \
c8e441d (Carol Platform 2024-04-20 16:33:12 -0500 5)   $(if $(filter dev sit,$(ENV)),debug-tools)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;The Makefile-based approach simplifies the deployment of Kubernetes ingress resources by consolidating all necessary components into a single, coherent workflow. This method eliminates the need for external tools or complex integrations, allowing you to manage the entire stack through straightforward make commands. The Makefile incorporates definitions for service accounts, roles, role bindings, and ingress-specific configurations, ensuring proper access controls and networking setup. By leveraging environment variables and templating, it dynamically generates the required Kubernetes manifests while maintaining consistency across deployments. This unified approach enables seamless integration between infrastructure provisioning, application deployment, and network configuration, providing a transparent and maintainable solution for managing ingress resources within your Kubernetes environment.&lt;/p&gt;

&lt;p&gt;To incorporate the ingress-related Makefile functionality using include, you can structure your Makefile to pull in external files that define various components like service accounts, roles, bindings, and other Kubernetes resources. This allows for better modularity, reusability, and clarity in your deployment pipeline.&lt;/p&gt;

&lt;p&gt;Here’s an example of how you can use include to organize and include different parts of your ingress stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── Makefile
├── global.param
├── ingress.mk
├── rbac.mk
├── configmap.mk
└── params/
    ├── dev.param
    ├── prod.param
    └── uat.param
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By structuring your Makefile this way, you can easily manage complex deployments while keeping everything modular and transparent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bundle and Archive Targets
&lt;/h2&gt;

&lt;p&gt;When managing Kubernetes deployments, it's often necessary to create portable archives or bundles of your deployment artifacts. These can be used for backups, sharing configurations, or deploying to environments without direct access to your Git repository. The Makefile includes targets like archive and bundle to simplify this process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Archive Target
&lt;/h3&gt;

&lt;p&gt;The archive target creates a compressed .tar.gz file containing all the necessary files for your deployment. This is especially useful for creating snapshots of your configuration at a specific point in time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundle Target
&lt;/h3&gt;

&lt;p&gt;The bundle target creates a Git bundle, which is a single file containing the entire Git repository history up to a specific commit. This is particularly useful for transferring repositories between systems without direct network access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use Archive and Bundle?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Portability : Both archive and bundle produce single files that are easy to transfer and store.&lt;/li&gt;
&lt;li&gt;Version Control : The bundle target preserves Git history, allowing you to track changes even when offline.&lt;/li&gt;
&lt;li&gt;Reproducibility : The archive target ensures that you can reproduce the exact state of your deployment at any time.&lt;/li&gt;
&lt;li&gt;Backup : These targets are excellent for creating backups of your deployment configuration.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Sometimes, the simplest way of using just Unix tools is the best way. By relying on basic utilities like &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;yq&lt;/code&gt;, and Make, you can create powerful, customizable workflows without the need for heavyweight tools like Helm. These simple, straightforward scripts offer greater control and flexibility. Plus, with LLMs (large language models) like this one, generating and refining code has become inexpensive and easy, making automation accessible. However, when things go wrong, debugging complex tools like Helm can become exponentially more expensive in terms of time and effort. Using minimal tools lets you stay in control, reduce complexity, and make it easier to fix issues when they arise. Sometimes, less really is more.&lt;/p&gt;




&lt;p&gt;Other examples of usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hackernoon.com/fine-tuning-models-with-your-own-data-effortlessly" rel="noopener noreferrer"&gt;Fine Tuning Models Effortlessly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
