<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nikolai Emil | Devantler | Blog</title><description>Personal site of Nikolai Emil Damm — software engineer, open-source advocate, and Kubernetes enthusiast.</description><link>https://devantler.tech/</link><language>en</language><item><title>How I Run KSail Autonomously with GitHub Agentic Workflows</title><link>https://devantler.tech/blog/autonomous-oss-with-github-agentic-workflows/</link><guid isPermaLink="true">https://devantler.tech/blog/autonomous-oss-with-github-agentic-workflows/</guid><description>A Mac Mini runs 24/7 at home, firing scheduled prompts that open PRs against KSail. Here&apos;s how I&apos;ve set it up and what I&apos;ve learned running it.

</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Mac Mini runs 24/7 in my house on Funen. It doesn’t serve media, it doesn’t compile code, it doesn’t host a website. Its only job is to fire scheduled prompts at GitHub agents so they can work on &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt; in the background.&lt;/p&gt;
&lt;p&gt;Left unsupervised, autonomous agents tend to produce a lot of &lt;em&gt;output&lt;/em&gt; and not a lot of &lt;em&gt;outcomes&lt;/em&gt; — PRs that don’t compile, issues that duplicate each other, roadmaps that drift from reality. I’ve hit all of those failure modes running this setup.&lt;/p&gt;
&lt;p&gt;The thing that keeps it usable is that autonomy isn’t really the goal. The goal is narrowing the agents’ scope enough, and layering enough checks, that the failure modes get caught before they reach me. I stay in the loop at one place: the Draft → In Review transition. Everything else is automated.&lt;/p&gt;
&lt;p&gt;This post is about how I’ve arranged that.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-i-think-about-autonomy&quot;&gt;How I Think About Autonomy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An agent that’s free to do anything will eventually do something I don’t want. I don’t try to stop that from happening — I try to make sure it gets caught.&lt;/p&gt;
&lt;p&gt;That shapes the setup in three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Each workflow has a narrow scope&lt;/strong&gt; so there’s a clear definition of “done” and not much room to improvise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deterministic guardrails sit between agent output and main&lt;/strong&gt; — lint, tests, security scans. The kinds of checks that don’t negotiate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One human decision stays in the loop&lt;/strong&gt;: promoting a draft PR to In Review. That’s where I can still veto.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nothing here is novel; it’s just what I’ve ended up with after iterating.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-pipeline-six-agentic-workflows&quot;&gt;The Pipeline: Six Agentic Workflows&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail runs six &lt;a href=&quot;https://github.com/githubnext/agentics&quot;&gt;GitHub Agentic Workflows&lt;/a&gt;, each with a narrow scope and its own schedule. They’re implemented as Markdown prompt files that the &lt;code dir=&quot;auto&quot;&gt;gh-aw&lt;/code&gt; extension compiles into GitHub Actions workflows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Weekly Strategy&lt;/strong&gt; — Runs Monday and Wednesday. On Monday it analyzes recent issues, discussions, and competitor tools (Tilt, Skaffold, DevSpace, and the rest) and publishes a Now / Next / Later roadmap. On Wednesday it turns the roadmap into promotional content — a Reddit post, a LinkedIn snippet, or a blog draft.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repo Assist&lt;/strong&gt; — Runs every 12 hours. This is the busiest workflow. It picks a task via weighted random selection from 12 categories (labelling issues, investigating bugs, cleaning up stale PRs, translating roadmap items into backlog issues, writing small code improvements). Weights adjust based on repo state — if there are a lot of unlabelled issues, labelling gets heavier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daily Docs&lt;/strong&gt; — Runs daily and on every push to &lt;code dir=&quot;auto&quot;&gt;main&lt;/code&gt;. Syncs documentation with code changes, and on a separate schedule scans for bloat and simplifies redundant pages. Knows which files are auto-generated and refuses to edit them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daily Workflow Maintenance&lt;/strong&gt; — Runs daily. Updates action versions, applies &lt;a href=&quot;https://github.com/githubnext/agentics&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;gh-aw&lt;/code&gt;&lt;/a&gt; codemods, recompiles workflow lock files. If there’s nothing to update, it switches into a deeper mode that analyzes CI metrics and proposes optimizations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI Doctor&lt;/strong&gt; — Runs on failure. When any monitored workflow fails, CI Doctor pulls the logs, runs pattern matching against previous investigations, categorizes the root cause, and files an issue with a recommended fix.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agentics Maintenance&lt;/strong&gt; — Runs every two hours. Closes expired discussions, issues, and PRs; keeps labels in sync. Mostly janitorial.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each workflow has a single sentence that describes what it’s allowed to do. None of them can merge. None of them can close another workflow’s PR. Most of them can only open drafts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-autonomy-is-obtained&quot;&gt;How Autonomy Is Obtained&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The workflows run on GitHub-hosted runners, but the &lt;em&gt;prompts&lt;/em&gt; that kick them off are scheduled on my Mac Mini via a simple cron-like setup. Why a physical machine and not just GitHub’s built-in schedule triggers? Two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;I can dispatch prompts on demand.&lt;/strong&gt; When I’m drafting an idea and want Repo Assist to pick it up immediately, I trigger it from the Mac Mini rather than waiting for the next 12-hour window.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The prompts are themselves versioned locally&lt;/strong&gt;, so I can iterate on them the same way I iterate on code — edit, test, commit, push. Keeping them on a machine I can see keeps the feedback loop tight.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The flow from idea to merged PR looks like this:&lt;/p&gt;
&lt;pre&gt;flowchart LR
    A[&quot;🗺️ Weekly Strategy&amp;#x3C;br/&gt;Roadmap + content&quot;] --&gt; B[&quot;📋 Repo Assist&amp;#x3C;br/&gt;Issues + draft PRs&quot;]
    B --&gt; C[&quot;👨‍💻 Me&amp;#x3C;br/&gt;Promote Draft → In Review&quot;]
    C --&gt; D[&quot;⚙️ CI Pipeline&amp;#x3C;br/&gt;Lint, build, test, bench&quot;]
    D --&gt; E[&quot;🤖 Agent Merge&amp;#x3C;br/&gt;Rebase, fix, merge&quot;]

    classDef agent fill:#1f6feb,stroke:#58a6ff,color:#fff;
    classDef human fill:#f0883e,stroke:#f0883e,color:#000;
    classDef ci fill:#238636,stroke:#3fb950,color:#fff;
    class A,B,E agent;
    class C human;
    class D ci;&lt;/pre&gt;
&lt;p&gt;Weekly Strategy produces the “what to work on.” Repo Assist turns that into issues and, where appropriate, draft PRs. Everything sits in Draft until I promote it. CI runs on every change. Agent Merge (implemented as a &lt;a href=&quot;https://github.com/features/copilot-skills&quot;&gt;Skill&lt;/a&gt;) rebases and addresses final review feedback.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-guardrails&quot;&gt;The Guardrails&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Scheduled prompts are the easy part. The guardrails are what make the output something I’m willing to merge. Every PR — agent or human — goes through the same stack.&lt;/p&gt;
&lt;pre&gt;flowchart LR
    A[&quot;🚨 Agent opens PR&quot;] --&gt; B[&quot;🛡️ GHAS&amp;#x3C;br/&gt;CodeQL + secret scanning&quot;]
    B --&gt; C[&quot;🔒 StepSecurity&amp;#x3C;br/&gt;Runner egress auditing&quot;]
    C --&gt; D[&quot;🧹 Linting&amp;#x3C;br/&gt;MegaLinter + golangci-lint&quot;]
    D --&gt; E[&quot;🧪 Unit tests&amp;#x3C;br/&gt;go test ./...&quot;]
    E --&gt; F[&quot;🚀 E2E matrix&amp;#x3C;br/&gt;Kind × K3d × Talos × VCluster&quot;]
    F --&gt; G[&quot;✅ Agent Merge&amp;#x3C;br/&gt;via Skills&quot;]

    classDef sec fill:#da3633,stroke:#f85149,color:#fff;
    classDef quality fill:#1f6feb,stroke:#58a6ff,color:#fff;
    classDef gate fill:#238636,stroke:#3fb950,color:#fff;
    class B,C sec;
    class D,E,F quality;
    class A,G gate;&lt;/pre&gt;
&lt;p&gt;What each layer does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GHAS + CodeQL&lt;/strong&gt; catches the class of bugs where an agent confidently introduces an injection or an unvalidated input. Cheap to enable and it has caught real issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StepSecurity&lt;/strong&gt; hardens the runners. &lt;code dir=&quot;auto&quot;&gt;egress-policy: audit&lt;/code&gt; on every job means I can see which hosts a workflow is talking to, which matters when agents occasionally try to fetch from somewhere unexpected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MegaLinter and golangci-lint&lt;/strong&gt; keep the stylistic and correctness conventions consistent. Agents will write idiomatic Go for a while and then forget &lt;code dir=&quot;auto&quot;&gt;errcheck&lt;/code&gt; on one function; linters notice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; run on every PR via &lt;code dir=&quot;auto&quot;&gt;go test ./...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E2E / system tests&lt;/strong&gt; run on the merge queue across a matrix of distributions (Kind, K3d, Talos, VCluster) and providers (Docker, Hetzner, Omni). This is the slow and expensive layer, and also where most agent-introduced regressions actually get caught.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Merge via Skills&lt;/strong&gt; handles the final rebase and review-feedback dance. It can’t bypass any of the above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these layers take the agent’s word for anything — they check independently. That’s the whole point.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;my-role&quot;&gt;My Role&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most of what I do on KSail day-to-day is promote draft PRs to In Review. I read the diff, read the linked issue, decide whether the change actually fits the roadmap, and either promote it or close it.&lt;/p&gt;
&lt;p&gt;Beyond that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;👀 &lt;strong&gt;Occasional check-ins&lt;/strong&gt; to sanity-check direction — am I comfortable with where the roadmap is going? Are there issues getting closed that shouldn’t be?&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Jumping in to build something myself&lt;/strong&gt; when I feel like coding. I use the same workflow — draft PR, CI, agent merge — so nothing is bypassed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing merges without me promoting it. Agent Merge waits for &lt;code dir=&quot;auto&quot;&gt;In Review&lt;/code&gt;; &lt;code dir=&quot;auto&quot;&gt;In Review&lt;/code&gt; only happens when I click the button.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-ive-learned&quot;&gt;What I’ve Learned&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A few observations from running this for a while. These are my experiences, not prescriptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The model matters more than the prompt.&lt;/strong&gt; A weaker model produces worse initial output and struggles with anything that has a larger scope. Running the same workflow against a mid-tier model versus a frontier model, the frontier model is the one that ends up shipping usable code. The prompt helps, but it isn’t going to rescue a weak base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before Agentic Workflows, working with AI was tedious.&lt;/strong&gt; I’d open a chat, paste context, iterate, copy results back, paste into a PR. The overhead per task was high enough that I used AI sparingly. Scheduled workflows changed that shape — the AI runs whether I’m paying attention or not, and I review in batch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don’t really trust AI to do a good job.&lt;/strong&gt; That sounds harsh, but it’s the honest frame I operate from. I don’t assume a PR is correct; I assume it needs to prove itself against the checks. When CI, lint, and the E2E matrix all go green, that’s something closer to evidence. Without that stack I don’t think I’d run any of this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The work shifts from coding to validation.&lt;/strong&gt; My days look different now. Less time writing code, more time reading diffs, promoting drafts, and deciding whether a change fits the roadmap. The work didn’t disappear, it just moved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Autonomous AI leans hard on good tooling.&lt;/strong&gt; Without GHAS and StepSecurity I’d be more anxious about letting agents open PRs on their own. I’m not saying nobody should run autonomous workflows without them — I’m saying I wouldn’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Working code beats sexy code, and I had to learn that the hard way.&lt;/strong&gt; My instinct is to factor, generalize, make things elegant. Newer models scratch that same itch when they get it right, which feels great. But when they duplicate code that didn’t need to be duplicated, it feels &lt;em&gt;worse&lt;/em&gt; than if I’d written the duplication myself. Where I’ve landed: let the agent ship working code first, and treat refactoring as a separate, deliberate task.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding signals to the feedback loop helps.&lt;/strong&gt; CI Doctor is a good example. A failing workflow can churn for days before an agent converges on a fix — often on things I could probably debug faster if I sat down with it myself. The trade-off is autonomy: it eventually gets there without me. New signals I’ve added to the loop (test coverage reports, benchmark regressions, lint summaries) have all made that convergence more reliable, even when they don’t make it faster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scheduling from a physical machine isn’t strictly necessary.&lt;/strong&gt; Most of these schedules could move to GitHub’s built-in cron. I keep the Mac Mini because it’s one place where I can see what’s running, what’s queued, and what I’m iterating on — more of a dashboard than a trigger. That framing is worth more to me than the technical utility.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The goal of this setup isn’t to remove myself from the project. It’s to move the tedious parts (triage, labelling, dependency updates, docs drift) onto agents, while keeping the parts that benefit from judgment (scope, design, what merges) with me.&lt;/p&gt;
&lt;p&gt;If you want to see the workflow files, they live in &lt;a href=&quot;https://github.com/devantler-tech/ksail/tree/main/.github/workflows&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;.github/workflows/&lt;/code&gt;&lt;/a&gt; in the KSail repo — every &lt;code dir=&quot;auto&quot;&gt;*.md&lt;/code&gt; file in that folder is one of the agentic workflows described here. The compiled &lt;code dir=&quot;auto&quot;&gt;*.lock.yml&lt;/code&gt; files are the GitHub Actions that actually run. The setup is built on top of &lt;a href=&quot;https://github.com/githubnext/agentics&quot;&gt;githubnext/agentics&lt;/a&gt;, which provides the &lt;code dir=&quot;auto&quot;&gt;gh-aw&lt;/code&gt; CLI and the workflow prompt framework.&lt;/p&gt;</content:encoded><category>ai</category><category>github</category><category>copilot</category><category>agentic-workflows</category><category>automation</category><category>ksail</category><category>developer-experience</category></item><item><title>I Built an MCP Server into My Kubernetes CLI</title><link>https://devantler.tech/blog/mcp-server-for-kubernetes-cluster-management/</link><guid isPermaLink="true">https://devantler.tech/blog/mcp-server-for-kubernetes-cluster-management/</guid><description>KSail now ships with a built-in MCP server that lets Claude, Cursor, and other AI assistants manage Kubernetes clusters directly. Here&apos;s how it works and what you can do with it.

</description><pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There’s a growing list of tools adopting the Model Context Protocol — the standard that lets AI assistants call external capabilities as structured tools rather than guessing from context. Most of the MCP servers I’ve seen are wrappers around databases, APIs, or SaaS products.&lt;/p&gt;
&lt;p&gt;I added one to a Kubernetes CLI.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt; is a tool I’ve been building for a while — it bundles kubectl, helm, flux, argocd, kind, k3d, talos, and vcluster into one binary and wraps them with a consistent CLI. The idea is that you shouldn’t need seven tools configured and version-matched just to spin up a local cluster and deploy some workloads. &lt;code dir=&quot;auto&quot;&gt;ksail cluster create&lt;/code&gt; handles Kind, K3d, Talos, or VCluster with the same workflow.&lt;/p&gt;
&lt;p&gt;Running &lt;code dir=&quot;auto&quot;&gt;ksail mcp&lt;/code&gt; starts a local MCP server that exposes all of that to any MCP-compatible client.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-actually-exposes&quot;&gt;What It Actually Exposes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The server surfaces five tool groups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cluster_read&lt;/strong&gt; — list clusters, get cluster info, inspect configuration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cluster_write&lt;/strong&gt; — create, update, delete, start, stop, backup, restore&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;workload_read&lt;/strong&gt; — get, describe, logs, explain, validate manifests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;workload_write&lt;/strong&gt; — apply, delete, scale, rollout, reconcile GitOps, push to registry&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cipher_write&lt;/strong&gt; — encrypt and decrypt secrets with SOPS/Age&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s roughly 30 underlying CLI commands organized into read/write pairs. The read tools are lower-risk; the write tools are the ones that actually change cluster state.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;connecting-it-to-claude-desktop&quot;&gt;Connecting It to Claude Desktop&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;ksail&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add that to &lt;code dir=&quot;auto&quot;&gt;claude_desktop_config.json&lt;/code&gt;, restart Claude Desktop, and it gains access to the full KSail tool surface. You can do things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Create a K3s cluster called dev with Cilium CNI and Flux GitOps”&lt;/li&gt;
&lt;li&gt;“Show me what’s deployed in the workload namespace”&lt;/li&gt;
&lt;li&gt;“Scale the nginx deployment to 3 replicas”&lt;/li&gt;
&lt;li&gt;“Encrypt this secret file before I commit it”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For Cursor, the setup is similar — add the MCP server in settings and point it at the &lt;code dir=&quot;auto&quot;&gt;ksail mcp&lt;/code&gt; command.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-tools-are-generated&quot;&gt;How the Tools Are Generated&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the part I find genuinely interesting to think about.&lt;/p&gt;
&lt;p&gt;KSail’s CLI is built with &lt;a href=&quot;https://github.com/spf13/cobra&quot;&gt;Cobra&lt;/a&gt;. Every command — &lt;code dir=&quot;auto&quot;&gt;ksail cluster create&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ksail workload apply&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ksail cipher encrypt&lt;/code&gt; — is defined as a Cobra command with flags, arguments, and descriptions.&lt;/p&gt;
&lt;p&gt;Rather than manually writing MCP tool definitions for each command, I built a code generator that walks the Cobra command tree and auto-generates the tool definitions. The generator reads each command’s description, flags, and argument patterns, and produces the corresponding MCP tool schema.&lt;/p&gt;
&lt;p&gt;The consolidation step is where it gets interesting. Exposing 30 individual tools to an AI assistant is noisy — you end up with too many choices and the model spends time deciding which narrow tool applies rather than doing the work. Instead, parent commands annotated with &lt;code dir=&quot;auto&quot;&gt;ai.toolgen.consolidate&lt;/code&gt; aggregate their subcommands into a single tool that accepts a subcommand name as an argument. The &lt;code dir=&quot;auto&quot;&gt;ai.toolgen.permission&lt;/code&gt; annotation then splits that into read and write variants.&lt;/p&gt;
&lt;p&gt;The result: adding a new CLI command under a consolidated parent automatically makes it available as an MCP tool. No manual registration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;ksail-is-now-in-the-mcp-registry&quot;&gt;KSail Is Now in the MCP Registry&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The server is registered in the &lt;a href=&quot;https://registry.modelcontextprotocol.io/?q=ksail&quot;&gt;MCP Registry&lt;/a&gt; as &lt;code dir=&quot;auto&quot;&gt;io.github.devantler-tech/ksail&lt;/code&gt;. This means MCP clients that support registry-based discovery can find and configure it without manual setup.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-practical-trade-off&quot;&gt;The Practical Trade-Off&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The write tools (cluster_write, workload_write, cipher_write) are the ones that require thought. Giving an AI assistant the ability to &lt;code dir=&quot;auto&quot;&gt;ksail cluster delete&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;ksail workload apply&lt;/code&gt; against a running cluster is genuinely useful for routine tasks — and genuinely risky if the model misunderstands the context.&lt;/p&gt;
&lt;p&gt;The read/write split exists for this reason. If you’re using an assistant primarily for inspection and debugging, you can expose only the read tools. If you’re using it for fully autonomous cluster setup (a documented cluster-as-code pattern KSail supports), you give it write access and let it run.&lt;/p&gt;
&lt;p&gt;KSail also has a built-in &lt;code dir=&quot;auto&quot;&gt;ksail chat&lt;/code&gt; command with three modes — Interactive (write ops require approval), Plan (no execution, description only), and Autopilot (full autonomous execution) — that gives you the same control in a terminal TUI rather than an external AI client.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-find-it&quot;&gt;Where to Find It&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start the MCP server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;github.com/devantler-tech/ksail&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The MCP server is part of the main binary — no separate installation. If you’re already using KSail, &lt;code dir=&quot;auto&quot;&gt;ksail mcp&lt;/code&gt; is already there.&lt;/p&gt;</content:encoded><category>kubernetes</category><category>ai</category><category>mcp</category><category>devtools</category><category>golang</category></item><item><title>GitOps Without the Git Server: OCI Registries as a Flux Source with KSail</title><link>https://devantler.tech/blog/gitops-without-the-git-server-oci-registries-as-a-flux-source-with-ksail/</link><guid isPermaLink="true">https://devantler.tech/blog/gitops-without-the-git-server-oci-registries-as-a-flux-source-with-ksail/</guid><description>Most local Flux setups point at a Git repo. OCI registries are cleaner — here&apos;s a full walkthrough using KSail&apos;s local registry and workload push command, plus how to extend the same workflow to GHCR for CI.

</description><pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The standard advice for running Flux locally is to point it at a Git repository. In practice that means either pushing to a remote repo and waiting for Flux to poll it, maintaining a local Git server, or wrestling with Flux’s lack of support for local file paths. None of these are great for a tight development loop.&lt;/p&gt;
&lt;p&gt;Flux has supported OCI repositories as sources since v2.0.0, and this is a much cleaner approach for local development. Instead of a Git repo, Flux watches an OCI artifact in a container registry. You push your manifests as a versioned artifact, Flux pulls and applies it. KSail takes care of the registry for you — when you create a local cluster with a GitOps engine, KSail automatically provisions a local Docker-based OCI registry alongside the cluster. No external accounts, no tokens, no network dependency. The workflow after editing manifests comes down to two commands.&lt;/p&gt;
&lt;p&gt;Here’s a complete walkthrough.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You need Docker installed and running. Verify with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;KSail itself only needs Docker — it bundles kubectl, helm, flux, kustomize, kind, and more into a single binary.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it for local development. No registry accounts or tokens needed — KSail provisions a local registry automatically.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;setting-up-a-cluster-with-flux&quot;&gt;Setting Up a Cluster with Flux&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Vanilla&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--gitops-engine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This scaffolds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt; — KSail configuration&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;kind.yaml&lt;/code&gt; — Kind cluster config (usable directly with &lt;code dir=&quot;auto&quot;&gt;kind&lt;/code&gt; if you ever want to bypass KSail)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;k8s/kustomization.yaml&lt;/code&gt; — root Kustomization manifest&lt;/li&gt;
&lt;li&gt;Flux HelmRelease and OCI source configs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;During &lt;code dir=&quot;auto&quot;&gt;cluster create&lt;/code&gt;, KSail:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Provisions a local OCI registry as a Docker container&lt;/li&gt;
&lt;li&gt;Creates the Kind cluster and attaches it to the registry&lt;/li&gt;
&lt;li&gt;Bootstraps Flux and configures it to watch the local registry as an OCI source&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By the time the command finishes, Flux is running and watching the local registry. No manual registry configuration needed.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;pushing-manifests&quot;&gt;Pushing Manifests&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Add something to &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; — a namespace, a Deployment, whatever you’re working on:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;k8s/my-app/deployment.yaml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;apiVersion&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;apps/v1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Deployment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;replicas&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;selector&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;matchLabels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;spec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;containers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nginx:stable-alpine&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;ports&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;containerPort&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Push and reconcile:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reconcile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;workload push&lt;/code&gt; packages your &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; directory as an OCI artifact and pushes it to the local registry. It auto-detects the registry from the running cluster — no flags or configuration needed. &lt;code dir=&quot;auto&quot;&gt;workload reconcile&lt;/code&gt; triggers Flux to pull the latest artifact and waits for all Kustomizations to report Ready.&lt;/p&gt;
&lt;p&gt;The round trip from &lt;code dir=&quot;auto&quot;&gt;workload push&lt;/code&gt; to reconciliation complete is typically under 10 seconds since everything stays on localhost.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-registry-and-tag-resolution-works&quot;&gt;How Registry and Tag Resolution Works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you run &lt;code dir=&quot;auto&quot;&gt;ksail workload push&lt;/code&gt;, the registry is resolved using this priority chain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An explicit &lt;code dir=&quot;auto&quot;&gt;oci://&lt;/code&gt; ref on the command line: &lt;code dir=&quot;auto&quot;&gt;ksail workload push oci://localhost:5050/k8s:v1.2.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CLI flag or environment variable (&lt;code dir=&quot;auto&quot;&gt;--registry&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;KSAIL_REGISTRY&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;spec.cluster.localRegistry&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cluster GitOps resources (FluxInstance or ArgoCD Application)&lt;/li&gt;
&lt;li&gt;Auto-detection from Docker containers matching the cluster name&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For local development, option 5 handles everything — you don’t need to configure anything.&lt;/p&gt;
&lt;p&gt;The tag follows a similar priority:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tag from the &lt;code dir=&quot;auto&quot;&gt;oci://&lt;/code&gt; ref on the command line&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;spec.workload.tag&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tag embedded in the registry URL from config&lt;/li&gt;
&lt;li&gt;The default: &lt;code dir=&quot;auto&quot;&gt;dev&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Locally the &lt;code dir=&quot;auto&quot;&gt;dev&lt;/code&gt; tag is convenient — every push overwrites the same tag, and Flux detects changes via digest rather than tag, so reconciliation still fires correctly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;extending-to-ghcr-for-ci&quot;&gt;Extending to GHCR for CI&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The same OCI workflow translates directly to CI pipelines by pointing at an external registry like GitHub Container Registry (GHCR). This gives you immutable versioned artifacts tied to specific commits.&lt;/p&gt;
&lt;p&gt;Override the registry at the command line to tie the artifact to a commit SHA:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;oci://ghcr.io/YOUR_ORG/my-manifests:&lt;/span&gt;&lt;span&gt;${{ &lt;/span&gt;&lt;span&gt;github&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sha&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each commit produces an immutable artifact. Flux picks up changes on its regular polling interval — &lt;code dir=&quot;auto&quot;&gt;workload reconcile&lt;/code&gt; is not needed in CI since it requires cluster access (kubeconfig) which is typically unavailable on GitHub Actions runners. If a deployment goes wrong you can point Flux at an earlier digest and it will reconcile back to a known-good state automatically.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;workload push&lt;/code&gt; command includes retry logic specifically for GHCR (added in v5.67.0), which handles the transient authentication errors that GHCR occasionally returns under load. For pipelines that push during high-traffic windows this meaningfully improves reliability.&lt;/p&gt;
&lt;p&gt;A minimal GitHub Actions workflow step:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Push manifests to GHCR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;echo &quot;${{ secrets.GITHUB_TOKEN }}&quot; | \&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;docker login ghcr.io -u ${{ github.actor }} --password-stdin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ksail workload push \&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;oci://ghcr.io/${{ github.repository_owner }}/my-cluster-manifests:${{ github.sha }}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;KSAIL_CLUSTER_NAME&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;KSAIL_CLUSTER_NAME&lt;/code&gt; environment variable tells KSail which cluster configuration to load when you have multiple clusters defined.&lt;/p&gt;
&lt;p&gt;For cloud clusters (e.g., Hetzner) where there’s no local Docker daemon, an external registry is required. Use the &lt;code dir=&quot;auto&quot;&gt;--local-registry&lt;/code&gt; flag during &lt;code dir=&quot;auto&quot;&gt;cluster init&lt;/code&gt; to configure it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Talos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Hetzner&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--gitops-engine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--local-registry&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;${GITHUB_USER}:${GITHUB_TOKEN}@ghcr.io/your-org/your-cluster&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--local-registry&lt;/code&gt; flag accepts the format &lt;code dir=&quot;auto&quot;&gt;[user:pass@]host[:port][/path]&lt;/code&gt;. Environment variable placeholders like &lt;code dir=&quot;auto&quot;&gt;${GITHUB_TOKEN}&lt;/code&gt; are expanded at runtime, keeping credentials out of your config files.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-about-workload-watch&quot;&gt;What About &lt;code dir=&quot;auto&quot;&gt;workload watch&lt;/code&gt;?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For purely local iteration where you don’t want to think about pushing at all, &lt;code dir=&quot;auto&quot;&gt;ksail workload watch&lt;/code&gt; is even lower friction:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;watch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--path&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This watches the &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; directory and applies changes directly with kubectl, bypassing the OCI push step entirely. If Flux is running it will also trigger selective Kustomization reconciliation on affected CRs automatically.&lt;/p&gt;
&lt;p&gt;The OCI push workflow is the better choice when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You want local and CI behavior to be identical&lt;/li&gt;
&lt;li&gt;You’re testing Flux OCI source configuration itself&lt;/li&gt;
&lt;li&gt;You need immutable versioned artifacts (each push gets a content digest)&lt;/li&gt;
&lt;li&gt;You’re sharing manifests across multiple clusters or environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;workload watch&lt;/code&gt; is better when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You’re iterating fast and CI parity doesn’t matter right now&lt;/li&gt;
&lt;li&gt;The push/reconcile round trip feels slow for your current task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both workflows coexist without conflict. It’s a per-session choice.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-note-on-secret-management&quot;&gt;A Note on Secret Management&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Regardless of whether you use a local registry or GHCR, secrets shouldn’t be in your manifests in plain text. KSail has built-in SOPS integration (&lt;code dir=&quot;auto&quot;&gt;ksail cipher encrypt&lt;/code&gt;) for secret management — encrypt secrets before they land in &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you use GHCR, packages inherit from your repository’s visibility by default. For anything sensitive, set the package visibility explicitly in GitHub’s package settings, or use a private repository.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;KSail is open source under Apache-2.0. Full documentation including the OCI push workflow, Flux source configuration, and the composite GitHub Action for CI is at &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;ksail.devantler.tech&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of AI. The content reflects genuine experiences and opinions; the AI helped structure and articulate them.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>kubernetes</category><category>gitops</category><category>flux</category><category>oci</category><category>ksail</category></item><item><title>Storing Sensitive Values in .zshrc with macOS Keychain</title><link>https://devantler.tech/blog/storing-secrets-in-zshrc-with-macos-keychain/</link><guid isPermaLink="true">https://devantler.tech/blog/storing-secrets-in-zshrc-with-macos-keychain/</guid><description>A quick guide on using macOS Keychain to avoid storing secrets in plaintext in your .zshrc.

</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Storing tokens and passwords directly in &lt;code dir=&quot;auto&quot;&gt;~/.zshrc&lt;/code&gt; means they sit on disk in plaintext. macOS Keychain provides a built-in, encrypted alternative. Here’s how to use it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;store-a-secret&quot;&gt;Store a Secret&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Use the &lt;code dir=&quot;auto&quot;&gt;security&lt;/code&gt; CLI to add a value to your Keychain:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;security&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add-generic-password&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$USER&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;my_secret_name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;SECRET_VALUE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;aside aria-label=&quot;Tip&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Tip&lt;/p&gt;&lt;div&gt;&lt;p&gt;Prepend the command with a space (note the leading space above) to prevent it from being saved in your &lt;code dir=&quot;auto&quot;&gt;.zsh_history&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;-a &quot;$USER&quot;&lt;/code&gt; — associates the entry with your macOS user account.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;-s &apos;my_secret_name&apos;&lt;/code&gt; — a label to identify the secret (e.g., &lt;code dir=&quot;auto&quot;&gt;GITHUB_TOKEN&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;-w &apos;SECRET_VALUE&apos;&lt;/code&gt; — the actual secret value.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;retrieve-a-secret&quot;&gt;Retrieve a Secret&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To retrieve the value later:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;security&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;find-generic-password&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$USER&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;my_secret_name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This prints the secret to stdout, making it easy to capture in a variable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;use-it-in-zshrc&quot;&gt;Use It in .zshrc&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Export the secret as an environment variable by adding this to &lt;code dir=&quot;auto&quot;&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;security&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;find-generic-password&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$USER&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;&lt;/div&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Task&lt;/th&gt;&lt;th&gt;Command&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Store&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;security add-generic-password -a &quot;$USER&quot; -s &apos;name&apos; -w &apos;value&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Retrieve&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;security find-generic-password -a &quot;$USER&quot; -s &apos;name&apos; -w&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Delete and re-add, or use &lt;code dir=&quot;auto&quot;&gt;-U&lt;/code&gt; flag to update in place&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Delete&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;security delete-generic-password -a &quot;$USER&quot; -s &apos;name&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;That’s it — no more plaintext secrets in your &lt;code dir=&quot;auto&quot;&gt;.zshrc&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>macos</category><category>security</category><category>zsh</category><category>developer-experience</category></item><item><title>Building an AI Assistant for Kubernetes with GitHub Copilot SDK</title><link>https://devantler.tech/blog/building-an-ai-assistant-for-kubernetes-with-github-copilot-sdk/</link><guid isPermaLink="true">https://devantler.tech/blog/building-an-ai-assistant-for-kubernetes-with-github-copilot-sdk/</guid><description>How I built KSail&apos;s interactive AI chat assistant using Go, GitHub Copilot SDK, and Bubbletea TUI framework.

</description><pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What if managing Kubernetes clusters was as simple as having a conversation?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt; has always been about reducing overhead. It started as shell scripts, became a &lt;a href=&quot;https://devantler.tech/blog/building-ksail-from-shell-to-dotnet-to-go/&quot;&gt;.NET tool with embedded binaries&lt;/a&gt;, and is now a pure Go SDK that bundles kubectl, helm, kind, k3d, flux, and argocd into a single binary. Each iteration removed friction: first installation overhead (one binary instead of six tools), then workflow overhead (consistent commands across distributions).&lt;/p&gt;
&lt;p&gt;The chat feature is the next step: &lt;strong&gt;removing cognitive overhead&lt;/strong&gt;. Instead of memorizing that &lt;code dir=&quot;auto&quot;&gt;ksail cluster init --distribution Talos --provider Hetzner --cni Cilium --gitops-engine Flux --local-registry &apos;${GITHUB_USER}:${GITHUB_TOKEN}@ghcr.io/devantler-tech/ksail/system-test-manifests&apos; --control-planes 3 --workers 2&lt;/code&gt; is the syntax to create a Talos cluster on Hetzner with three control plane nodes, two worker nodes, Cilium as a CNI, and Flux as your GitOps engine set up to sync with an OCI image in GHCR, you can just say: “Create a HA Talos cluster on Hetzner with Cilium and Flux set up to sync with ghcr.io/devantler-tech/ksail/system-test-manifests.” (pheeew).&lt;/p&gt;
&lt;p&gt;This post covers the technical journey of building &lt;code dir=&quot;auto&quot;&gt;ksail chat&lt;/code&gt; using the &lt;a href=&quot;https://github.com/github/copilot-sdk-go&quot;&gt;GitHub Copilot SDK for Go&lt;/a&gt;, &lt;a href=&quot;https://github.com/charmbracelet/bubbletea&quot;&gt;Bubbletea&lt;/a&gt; TUI framework, and &lt;a href=&quot;https://github.com/charmbracelet/glamour&quot;&gt;glamour&lt;/a&gt; for markdown rendering.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;see-it-in-action&quot;&gt;See It in Action&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before diving into the implementation, let’s see what we’re building. Here’s &lt;code dir=&quot;auto&quot;&gt;ksail chat&lt;/code&gt; creating a cluster on Hetzner Cloud from a single natural language request:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Start the conversation&lt;/strong&gt; — ask to create a cluster on Hetzner&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Starting the chat with a Hetzner cluster request&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1598&quot; height=&quot;1574&quot; src=&quot;https://devantler.tech/_astro/ksail-copilot-chat-hetzner-start.C7kLY5vc_132Hjp.webp&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Task complete&lt;/strong&gt; — the assistant finishes provisioning the cluster&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Cluster creation finished in chat&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1602&quot; height=&quot;2044&quot; src=&quot;https://devantler.tech/_astro/ksail-copilot-chat-hetzner-finished.B2Zo_hNl_k90RJ.webp&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Verification&lt;/strong&gt; — the cluster appears in Hetzner Cloud&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Verification in Hetzner Cloud dashboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2784&quot; height=&quot;308&quot; src=&quot;https://devantler.tech/_astro/ksail-copilot-chat-hetzner-verificiation.C1CBICrj_bedxp.webp&quot;&gt;&lt;/p&gt;
&lt;p&gt;The entire workflow happens in the terminal — no context switching, no remembering flags.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail already makes Kubernetes development easier by bundling common tools into a single Go binary. But users still need to remember commands, flags, and workflows. The chat feature completes the progression:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before KSail&lt;/th&gt;&lt;th&gt;With KSail CLI&lt;/th&gt;&lt;th&gt;With KSail Chat&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;5+ tools to install&lt;/td&gt;&lt;td&gt;1 binary&lt;/td&gt;&lt;td&gt;1 binary&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Memorize each tool’s flags&lt;/td&gt;&lt;td&gt;Read &lt;code dir=&quot;auto&quot;&gt;--help&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Just describe what you want&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Copy commands from docs&lt;/td&gt;&lt;td&gt;Run unified commands&lt;/td&gt;&lt;td&gt;”Create a cluster”&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;KSail already knows &lt;em&gt;how&lt;/em&gt; to do everything — now it understands &lt;em&gt;what&lt;/em&gt; you want to do. The assistant needs to understand KSail’s capabilities, execute real commands (not just explain them), work entirely in the terminal, and respect security boundaries. Building that required three key pieces working together.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-tech-stack&quot;&gt;The Tech Stack&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Each requirement above pointed to a specific technical choice: an AI backend for understanding and action, a TUI framework for terminal-native interaction, and a markdown renderer for readable output.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;github-copilot-sdk-for-go&quot;&gt;GitHub Copilot SDK for Go&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/github/copilot-sdk-go&quot;&gt;Copilot SDK for Go&lt;/a&gt; provides the AI backbone. At time of writing, I used v0.1.18. It handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Streaming responses&lt;/strong&gt; — tokens arrive as they’re generated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; — the model can invoke functions you define&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversation history&lt;/strong&gt; — multi-turn dialogues with context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The SDK abstracts away the complexity of the Copilot API, letting me focus on building the experience.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bubbletea-tui-framework&quot;&gt;Bubbletea TUI Framework&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/charmbracelet/bubbletea&quot;&gt;Bubbletea&lt;/a&gt; is an Elm-inspired framework for building terminal apps in Go. It uses a functional model where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt; holds the application state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update&lt;/strong&gt; handles messages and returns a new model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt; renders the model to a string&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This architecture makes state management predictable — every state transition is explicit. See the &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5/pkg/cli/ui/chat#Model&quot;&gt;chat TUI model&lt;/a&gt; for the full implementation.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;glamour-for-markdown-rendering&quot;&gt;Glamour for Markdown Rendering&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;AI responses often include markdown formatting. &lt;a href=&quot;https://github.com/charmbracelet/glamour&quot;&gt;glamour&lt;/a&gt; renders it beautifully in the terminal — code blocks get syntax highlighting, headers become bold, and lists render cleanly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The SDK, TUI, and renderer handle &lt;em&gt;how&lt;/em&gt; the interface works. The harder question: how do we make the AI actually &lt;em&gt;useful&lt;/em&gt;? An LLM that just talks is nice, but we need it to execute real commands and stay within security boundaries.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;auto-generated-tools-from-cobra-commands&quot;&gt;Auto-Generated Tools from Cobra Commands&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;img alt=&quot;ksail-copilot-chat-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;2008&quot; height=&quot;2258&quot; src=&quot;https://devantler.tech/_astro/ksail-copilot-chat-2.BDBzC6oX_VT9VU.webp&quot;&gt;&lt;/p&gt;
&lt;p&gt;KSail has 92+ CLI commands, all built with &lt;a href=&quot;https://github.com/spf13/cobra&quot;&gt;Cobra&lt;/a&gt;. This consistency is what makes auto-generation possible — every command has the same structure: name, description, flags with types and defaults.&lt;/p&gt;
&lt;p&gt;I built a &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5/pkg/svc/chat/generator&quot;&gt;generator&lt;/a&gt; that walks KSail’s command tree, converts each command’s flags to JSON Schema, and creates tool handlers that invoke the actual KSail logic. The AI doesn’t shell out to &lt;code dir=&quot;auto&quot;&gt;ksail&lt;/code&gt; — it calls the same Go functions the CLI uses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lesson learned&lt;/strong&gt;: auto-discovering all 92 commands broke things. LLMs have a practical limit on how many tools they can reason about effectively — this is a known issue called &lt;em&gt;tool overload&lt;/em&gt;. When presented with too many options, the model’s ability to select the right tool degrades significantly. Some commands (like internal debug utilities) also shouldn’t be exposed at all. The solution was to keep auto-discovery but add an exclusion filter — now only ~25 curated commands become tools. Best of both worlds: no manual sync, but a focused toolset the model can actually use well.&lt;/p&gt;
&lt;p&gt;Tools let the assistant &lt;em&gt;act&lt;/em&gt;. But to use them well, it needs context about KSail itself.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;embedded-documentation-context&quot;&gt;Embedded Documentation Context&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The assistant needs to understand KSail’s concepts. I use &lt;code dir=&quot;auto&quot;&gt;go:embed&lt;/code&gt; to bundle 100+ documentation files at compile time, giving the model deep knowledge of KSail’s architecture, configuration options, and best practices — without requiring network requests. See &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5/pkg/svc/chat#BuildSystemContext&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;BuildSystemContext&lt;/code&gt;&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;This embedded context includes KSail’s distribution and provider abstractions. The same chat conversation can create a local Kind cluster for development or a Talos cluster on Hetzner for production — the AI uses the same tools, just with different flags. Users don’t need to know which underlying tool handles which provider.&lt;/p&gt;
&lt;p&gt;With tools and context in place, the assistant can execute commands. But power needs guardrails.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;security-boundaries-for-file-operations&quot;&gt;Security Boundaries for File Operations&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The assistant can read and write files, but only within the current working directory. The &lt;code dir=&quot;auto&quot;&gt;securePath&lt;/code&gt; function in the &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5/pkg/svc/chat&quot;&gt;tools package&lt;/a&gt; validates all paths by resolving symlinks and verifying the target stays within the workspace — preventing symlink escape attacks where a malicious symlink could point outside the workspace.&lt;/p&gt;
&lt;p&gt;With the core architecture solid — tools, context, and security — the final layer is polish.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;user-experience-polish&quot;&gt;User Experience Polish&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Beyond the core AI integration, the TUI includes thoughtful UX details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prompt history&lt;/strong&gt; — press ↑/↓ to recall previous messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time tool streaming&lt;/strong&gt; — see command output as it runs, not just when complete&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collapsible tool output&lt;/strong&gt; — Tab toggles individual tools, Ctrl+T toggles all&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mouse scrolling&lt;/strong&gt; — wheel scrolls the conversation viewport&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adaptive rendering&lt;/strong&gt; — markdown re-renders when terminal width changes&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;building-with-ai-assistance&quot;&gt;Building with AI Assistance&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s the meta part: an AI assistant built &lt;em&gt;using&lt;/em&gt; AI assistance. The same pattern — removing cognitive overhead — applied to the development process itself.&lt;/p&gt;
&lt;p&gt;VS Code’s agent mode with Claude Opus 4.5 was invaluable throughout this project. But TUI development presents a unique challenge — the AI can’t “see” what renders in your terminal. How do you debug visual issues with a text-based assistant?&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-image-snapshot-workflow&quot;&gt;The Image Snapshot Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;My solution: image snapshots. The workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Run the TUI&lt;/strong&gt; with test inputs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screenshot&lt;/strong&gt; the terminal output&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share the image&lt;/strong&gt; with Claude via VS Code’s attachment feature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Describe what’s wrong&lt;/strong&gt; — “The viewport is overlapping the input field”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply the suggested fix&lt;/strong&gt; and iterate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This visual feedback loop was essential for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debugging layout issues&lt;/li&gt;
&lt;li&gt;Fine-tuning the viewport scroll behavior&lt;/li&gt;
&lt;li&gt;Aligning the collapsible tool output sections&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;iterating-on-tui-design&quot;&gt;Iterating on TUI Design&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The TUI went through several iterations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 1&lt;/strong&gt;: Simple streaming text, no viewport — text would scroll off screen&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 2&lt;/strong&gt;: Added viewport with scroll handling — but input field position was wrong&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 3&lt;/strong&gt;: Proper layout with input at bottom, content scrolling above — working!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 4&lt;/strong&gt;: Added collapsible tool invocations with Tab toggle — polished experience&lt;/p&gt;
&lt;p&gt;Each iteration was a conversation with Claude, showing screenshots and describing what felt wrong.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;technical-decisions-and-trade-offs&quot;&gt;Technical Decisions and Trade-offs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every project has its thorny edge cases. Here are the three that taught me the most.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;per-turn-subscription-pattern&quot;&gt;Per-Turn Subscription Pattern&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Bubbletea uses subscriptions for async events (like streaming tokens). A naive approach subscribes once at initialization, but this causes issues when starting new turns — old subscriptions conflict with new ones. The solution was to subscribe fresh for each AI turn, creating a new channel and subscription tied to that specific conversation turn. See the &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5/pkg/cli/ui/chat#Model.Update&quot;&gt;Model.Update&lt;/a&gt; method for the implementation.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;context-cancellation-challenges&quot;&gt;Context Cancellation Challenges&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Copilot SDK doesn’t support mid-stream cancellation. If you cancel the context, the SDK panics rather than gracefully stopping. My workaround: use a context that’s never cancelled, and track “stop requested” via a separate flag. This isn’t ideal, but it’s documented in the code for future improvement when the SDK adds support.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;tool-result-ordering&quot;&gt;Tool Result Ordering&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Tools can execute in any order, but results should appear in the order the model requested them. The SDK’s channel-based output didn’t guarantee this, so I implemented a mutex-protected FIFO buffer to ensure consistent ordering in the UI.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The chat assistant is functional but still in its infancy. Each improvement targets a specific friction point — continuing the “remove overhead” theme:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Smarter clarifications&lt;/strong&gt; — remove the overhead of being precise upfront; let vague requests trigger smart follow-up questions, or intelligent inferences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More tools&lt;/strong&gt; — reduce the gap between what KSail can do and what the assistant can do&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permission prompts&lt;/strong&gt; — guard write operations with user confirmation to increase safety, and allow configurable auto-approvals for power users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversation persistence&lt;/strong&gt; — remove the overhead of re-explaining context across sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better context&lt;/strong&gt; — understand the user’s specific cluster state, not just general KSail knowledge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming cancellation&lt;/strong&gt; — more responsive interaction when the SDK supports it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to try it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Install KSail v5+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start a chat session (requires GitHub Copilot access)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chat&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Or use non-TUI mode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--tui=false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The full implementation is in the &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail repository&lt;/a&gt; — the TUI lives in &lt;code dir=&quot;auto&quot;&gt;pkg/cli/ui/chat/&lt;/code&gt; and the tools generator in &lt;code dir=&quot;auto&quot;&gt;pkg/svc/chat/generator/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Feedback and contributions welcome!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude Opus 4.5 via VS Code), following my outline and intent.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>ai</category><category>ksail</category><category>go</category><category>copilot</category><category>tui</category></item><item><title>Creating Kubernetes Clusters on Hetzner with KSail and Talos</title><link>https://devantler.tech/blog/creating-development-kubernetes-clusters-on-hetzner-with-ksail-and-talos/</link><guid isPermaLink="true">https://devantler.tech/blog/creating-development-kubernetes-clusters-on-hetzner-with-ksail-and-talos/</guid><description>A step-by-step guide to creating Talos Linux Kubernetes clusters on Hetzner Cloud using KSail.

</description><pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Setting up Kubernetes environments doesn’t have to be expensive or complicated. With &lt;a href=&quot;https://www.hetzner.com/cloud/&quot;&gt;Hetzner Cloud&lt;/a&gt;’s affordable pricing, &lt;a href=&quot;https://www.talos.dev/&quot;&gt;Talos Linux&lt;/a&gt;’s security-focused immutable OS, and &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt;’s unified tooling, you can have a cluster running in minutes. This post walks through the complete setup.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-hetzner--talos--ksail&quot;&gt;Why Hetzner + Talos + KSail?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Hetzner Cloud&lt;/strong&gt; offers some of the most affordable cloud VPS servers in the industry. A capable &lt;code dir=&quot;auto&quot;&gt;cx23&lt;/code&gt; server (2 vCPU, 4GB RAM) costs around €3.74/month — significantly cheaper than equivalent offerings from AWS, GCP, or Azure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Talos Linux&lt;/strong&gt; is a minimal, immutable operating system designed specifically for Kubernetes. There’s no SSH, no shell, no package manager — just the Talos API and Kubernetes. This dramatically reduces the attack surface and eliminates configuration drift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KSail&lt;/strong&gt; brings it all together with a single binary that handles cluster provisioning, GitOps setup, and workload management. Instead of juggling &lt;code dir=&quot;auto&quot;&gt;hcloud&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;talosctl&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;helm&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;flux&lt;/code&gt; commands, you use one consistent interface.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-create-a-hetzner-account&quot;&gt;Step 1: Create a Hetzner Account&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you don’t already have a Hetzner account:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href=&quot;https://console.hetzner.cloud/&quot;&gt;Hetzner Cloud Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click “Register” and complete the signup process&lt;/li&gt;
&lt;li&gt;Verify your email and complete any required identity verification&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hetzner has documentation for getting started: &lt;a href=&quot;https://docs.hetzner.com/general/billing-and-account-management/account-getting-started/&quot;&gt;Account Getting Started Guide&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-create-a-hetzner-project&quot;&gt;Step 2: Create a Hetzner Project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Projects in Hetzner Cloud are organizational units that contain your resources (servers, networks, load balancers, etc.). Each project has its own API tokens and billing.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log into the &lt;a href=&quot;https://console.hetzner.cloud/&quot;&gt;Hetzner Cloud Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click “New Project” in the sidebar&lt;/li&gt;
&lt;li&gt;Name your project (e.g., “kubernetes-dev” or “my-homelab”)&lt;/li&gt;
&lt;li&gt;Click “Create”&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-generate-an-api-token&quot;&gt;Step 3: Generate an API Token&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail needs an API token to provision and manage Hetzner Cloud resources.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In your project, go to &lt;strong&gt;Security&lt;/strong&gt; → &lt;strong&gt;API Tokens&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate API Token&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Name the token (e.g., “ksail-cluster-management”)&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Read &amp;#x26; Write&lt;/strong&gt; permissions — KSail needs to create and delete servers&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate API Token&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copy the token immediately&lt;/strong&gt; — you won’t be able to see it again!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For more details, see Hetzner’s &lt;a href=&quot;https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/&quot;&gt;Generating API Token Guide&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-export-the-api-token&quot;&gt;Step 4: Export the API Token&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail reads the Hetzner API token from the &lt;code dir=&quot;auto&quot;&gt;HCLOUD_TOKEN&lt;/code&gt; environment variable. Add it to your shell configuration:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# For the current session&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HCLOUD_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;your-api-token-here&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# To persist across sessions, add to your shell config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;export HCLOUD_TOKEN=&quot;your-api-token-here&quot;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.zshrc&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# or ~/.bashrc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.zshrc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can verify the token is set:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$HCLOUD_TOKEN&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Should show first 10 characters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;step-5-install-ksail&quot;&gt;Step 5: Install KSail&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail is distributed as a single binary. The easiest installation method is via Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Alternatively, if you have Go installed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/devantler-tech/ksail/v5@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Verify the installation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;step-6-scaffold-your-cluster-project&quot;&gt;Step 6: Scaffold Your Cluster Project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail’s &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; command scaffolds a complete project structure. For a basic Talos cluster on Hetzner with Cilium CNI:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Talos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Hetzner&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cni&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cilium&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This creates &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt; (cluster configuration), &lt;code dir=&quot;auto&quot;&gt;talos/&lt;/code&gt; (Talos configs), and &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; (your Kubernetes manifests).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For GitOps workflows&lt;/strong&gt;, add a GitOps engine and external registry:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Talos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Hetzner&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--cni&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cilium&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--gitops-engine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--local-registry&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;${GITHUB_USER}:${GITHUB_TOKEN}@ghcr.io/your-org/your-cluster&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This configures Flux to sync manifests from GitHub Container Registry. See &lt;a href=&quot;#step-9-deploying-workloads&quot;&gt;Step 9: Deploying Workloads&lt;/a&gt; for the full GitOps workflow.&lt;/p&gt;
&lt;p&gt;For all available flags and configuration options, see the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-init&quot;&gt;CLI flags reference&lt;/a&gt; — All &lt;code dir=&quot;auto&quot;&gt;cluster init&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/declarative-configuration&quot;&gt;ksail.yaml reference&lt;/a&gt; — Configuration file schema&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/features&quot;&gt;Features overview&lt;/a&gt; — CNI, CSI, GitOps, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;step-7-create-the-cluster&quot;&gt;Step 7: Create the Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With your configuration ready, create the cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates servers in Hetzner Cloud&lt;/li&gt;
&lt;li&gt;Configures the private network&lt;/li&gt;
&lt;li&gt;Bootstraps Talos Linux on each node&lt;/li&gt;
&lt;li&gt;Initializes the Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Installs your selected CNI, CSI, and other components&lt;/li&gt;
&lt;li&gt;Configures your local kubeconfig&lt;/li&gt;
&lt;li&gt;Configures your local talosconfig&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process takes 3-5 minutes depending on your cluster size.&lt;/p&gt;
&lt;p&gt;You can watch the progress as KSail outputs status updates for each stage.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-8-working-with-your-cluster&quot;&gt;Step 8: Working with Your Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once your cluster is running, KSail provides commands for common operations:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Show cluster status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# List all KSail-managed clusters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Open K9s for interactive management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Stop the cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# Start a stopped cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your kubeconfig is automatically configured, so standard &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt; commands work too.&lt;/p&gt;
&lt;p&gt;For the full command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-root&quot;&gt;Cluster Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-9-deploying-workloads&quot;&gt;Step 9: Deploying Workloads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail wraps kubectl and GitOps operations under the &lt;code dir=&quot;auto&quot;&gt;workload&lt;/code&gt; command. For cloud clusters like Hetzner, you have two main options for deploying workloads.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;option-a-direct-kubectl-workflow&quot;&gt;Option A: Direct kubectl Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The simplest approach applies manifests directly to the cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-k&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Apply Kustomize manifests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pods&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# Check pod status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;logs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deployment/my-app&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# View logs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This works well for quick iterations but doesn’t provide GitOps benefits like drift detection and automatic reconciliation.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;option-b-gitops-with-external-registry&quot;&gt;Option B: GitOps with External Registry&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For cloud clusters without a local Docker registry, you can use an external OCI registry like GitHub Container Registry (ghcr.io). This enables full GitOps workflows with Flux or ArgoCD.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Create a GitHub Personal Access Token&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)&lt;/li&gt;
&lt;li&gt;Create a token with &lt;code dir=&quot;auto&quot;&gt;write:packages&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;read:packages&lt;/code&gt; scopes&lt;/li&gt;
&lt;li&gt;Export the credentials:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GITHUB_USER&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;your-username&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ghp_your-token-here&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Initialize with GitOps and External Registry&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When scaffolding your cluster, specify the GitOps engine and external registry:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Talos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Hetzner&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--cni&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cilium&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--gitops-engine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--local-registry&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;${GITHUB_USER}:${GITHUB_TOKEN}@ghcr.io/your-org/your-cluster&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--local-registry&lt;/code&gt; flag accepts the format &lt;code dir=&quot;auto&quot;&gt;[user:pass@]host[:port][/path]&lt;/code&gt;. Environment variable placeholders like &lt;code dir=&quot;auto&quot;&gt;${GITHUB_TOKEN}&lt;/code&gt; are expanded at runtime, keeping credentials out of your config files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Create the Cluster&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This installs Flux and configures it to sync from your external registry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Push and Reconcile Workloads&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Package manifests and push to registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Trigger GitOps reconciliation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reconcile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;push&lt;/code&gt; command packages your &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; directory as an OCI artifact and pushes it to ghcr.io. Flux then pulls and applies the manifests automatically.&lt;/p&gt;
&lt;aside aria-label=&quot;Tip&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Tip&lt;/p&gt;&lt;div&gt;&lt;p&gt;You can also set the registry via environment variable: &lt;code dir=&quot;auto&quot;&gt;KSAIL_REGISTRY=&apos;ghcr.io/org/repo&apos; ksail workload push&lt;/code&gt;&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;For the full workload command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/workload/workload-root&quot;&gt;Workload Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cleaning-up&quot;&gt;Cleaning Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you’re done with the cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This removes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All Hetzner Cloud servers&lt;/li&gt;
&lt;li&gt;The private network&lt;/li&gt;
&lt;li&gt;Placement groups&lt;/li&gt;
&lt;li&gt;Local kubeconfig entries&lt;/li&gt;
&lt;li&gt;Local talosconfig entries&lt;/li&gt;
&lt;/ul&gt;
&lt;aside aria-label=&quot;Caution&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Caution&lt;/p&gt;&lt;div&gt;&lt;p&gt;This is destructive and cannot be undone. All data in the cluster will be lost.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;cost-considerations&quot;&gt;Cost Considerations&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A minimal development setup (1 control plane) using a &lt;code dir=&quot;auto&quot;&gt;cx23&lt;/code&gt; server costs under €4/month.&lt;/p&gt;
&lt;p&gt;For a more robust setup (3 control planes + 2 workers) using &lt;code dir=&quot;auto&quot;&gt;cx23&lt;/code&gt; servers:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;/th&gt;&lt;th&gt;Monthly Cost&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;3x cx23 control planes&lt;/td&gt;&lt;td&gt;€11.22&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2x cx23 workers&lt;/td&gt;&lt;td&gt;€7.48&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Private network&lt;/td&gt;&lt;td&gt;Free&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;~€18.70/month&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This is remarkably affordable for a Kubernetes development cluster.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Explore the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt; for advanced topics including secret management with SOPS, mirror registries, and GitOps workflows.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;planned-production-grade-features&quot;&gt;Planned: Production-Grade Features&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Integration with &lt;a href=&quot;https://github.com/hetznercloud/hcloud-cloud-controller-manager&quot;&gt;Hetzner Cloud Controller Manager&lt;/a&gt; and &lt;a href=&quot;https://github.com/hetznercloud/csi-driver&quot;&gt;Hetzner Cloud CSI Driver&lt;/a&gt; is planned for future releases. This will enable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Load Balancers&lt;/strong&gt;: Automatic provisioning of Hetzner Load Balancers for Kubernetes Services of type &lt;code dir=&quot;auto&quot;&gt;LoadBalancer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent Storage&lt;/strong&gt;: Dynamic provisioning of Hetzner Cloud Volumes for PersistentVolumeClaims&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;feedback-welcome&quot;&gt;Feedback Welcome&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is the first iteration of Hetzner Cloud support in KSail. If you encounter bugs or find missing features, please &lt;a href=&quot;https://github.com/devantler-tech/ksail/issues&quot;&gt;open an issue on GitHub&lt;/a&gt;. Your feedback helps improve the tool for everyone.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;a-note-on-cloud-provider-support&quot;&gt;A Note on Cloud Provider Support&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Testing and maintaining cloud provider integrations comes with ongoing infrastructure costs. Hetzner is supported because I use it for my own homelab and want to manage it via KSail. Additional cloud providers (AWS, GCP, Azure, etc.) will not be added without sponsorship to cover testing costs.&lt;/p&gt;
&lt;p&gt;If you’d like to see your preferred cloud provider supported, consider &lt;a href=&quot;https://github.com/sponsors/devantler&quot;&gt;sponsoring the project on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5. The content is based on real-world experience running Talos development clusters on Hetzner Cloud.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>kubernetes</category><category>ksail</category><category>talos</category><category>hetzner</category><category>cloud</category></item><item><title>AI-Powered GitHub Issues: How I Use Copilot with Claude Opus 4.5 to Create Impactful Issues</title><link>https://devantler.tech/blog/ai-powered-github-issues-with-copilot-and-claude-opus/</link><guid isPermaLink="true">https://devantler.tech/blog/ai-powered-github-issues-with-copilot-and-claude-opus/</guid><description>How I leverage GitHub Copilot with Claude Opus 4.5 to analyze codebases, investigate context, and create well-structured GitHub issues that save time for myself and my team.

</description><pubDate>Fri, 09 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Creating good GitHub issues is an underrated skill. A well-written issue saves hours of back-and-forth during refinement, reduces misunderstandings, and helps developers focus on solving problems rather than deciphering vague descriptions. But writing those issues takes time — time spent investigating code, formulating problems clearly, and ensuring the description meets team expectations.&lt;/p&gt;
&lt;p&gt;I’ve found a workflow that dramatically reduces this overhead: using &lt;a href=&quot;https://docs.github.com/en/copilot&quot;&gt;GitHub Copilot&lt;/a&gt; with &lt;a href=&quot;https://www.anthropic.com/news/claude-opus-4-5&quot;&gt;Claude Opus 4.5&lt;/a&gt; to analyze codebases and generate structured, impactful issues.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem-with-traditional-issue-creation&quot;&gt;The Problem with Traditional Issue Creation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before adopting this workflow, creating a proper issue typically involved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Investigation&lt;/strong&gt;: Digging through code to understand the current state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analysis&lt;/strong&gt;: Identifying what’s wrong or what’s missing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formulation&lt;/strong&gt;: Translating technical findings into clear descriptions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structuring&lt;/strong&gt;: Ensuring the issue follows team conventions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review&lt;/strong&gt;: Double-checking that nothing important was missed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a complex feature or bug, this process could easily take multiple hours — and that’s assuming you’re already familiar with the codebase.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;my-ai-assisted-workflow&quot;&gt;My AI-Assisted Workflow&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My approach flips the script. Instead of manually investigating and writing, I give GitHub Copilot a clear but short description of the expected outcome or problem, and let Claude Opus 4.5 do the heavy lifting.&lt;/p&gt;
&lt;pre&gt;flowchart LR
    A[&quot;📁 Select Context&quot;] --&gt; B[&quot;💬 Prompt with Intent&quot;]
    B --&gt; C[&quot;📋 Reference Templates&quot;]
    C --&gt; D[&quot;🤖 AI Analysis&quot;]
    D --&gt; E[&quot;🎫 Create Issue&quot;]
    E --&gt; F[&quot;✅ Review &amp;#x26; Refine&quot;]&lt;/pre&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;step-1-select-the-context&quot;&gt;Step 1: Select the Context&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I attach the relevant folders, repositories, or files in &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode&quot;&gt;VS Code Chat Agent mode&lt;/a&gt;. This might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A specific service or module where I’ve noticed a problem&lt;/li&gt;
&lt;li&gt;Multiple related files that need coordinated changes&lt;/li&gt;
&lt;li&gt;An entire project when planning a new feature&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;step-2-prompt-with-intent-and-reference-templates&quot;&gt;Step 2: Prompt with Intent and Reference Templates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I provide Copilot with a concise description of what I need, always referencing my issue templates. The key is being clear about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What to analyze&lt;/strong&gt; — the specific area of the codebase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What the problem or goal is&lt;/strong&gt; — the expected outcome&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which template to use&lt;/strong&gt; — this ensures the output follows team conventions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here are some example prompts I use:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For a bug:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Analyze the metrics-server installation in pkg/svc/installers/ and identify&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;why it&apos;s not working with the latest Kubernetes version. Use my bug issue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template from .github/ISSUE_TEMPLATE/BUG.yaml to create the issue.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;For a feature:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Investigate the current cluster provisioning flow in pkg/svc/provisioners/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;and create a feature issue for adding Hetzner Cloud support. Follow the&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;feature template from .github/ISSUE_TEMPLATE/FEATURE.yaml.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;For process improvements:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Our team&apos;s refinement sessions often run over time because issues lack context.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Create an improvement kata issue to improve how we write and prepare issues&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;before refinement, using the KATA.yaml template.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;step-3-review-and-refine&quot;&gt;Step 3: Review and Refine&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Opus 4.5 generates a complete issue that typically requires minimal editing. The model is remarkably good at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understanding codebases and their architecture&lt;/li&gt;
&lt;li&gt;Identifying the actual problem, not just symptoms&lt;/li&gt;
&lt;li&gt;Writing clear, actionable descriptions&lt;/li&gt;
&lt;li&gt;Following the template structure precisely&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;structured-templates-are-key&quot;&gt;Structured Templates Are Key&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The secret sauce isn’t just the AI — it’s combining AI with well-designed issue templates. I use &lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms&quot;&gt;YAML-based GitHub issue forms&lt;/a&gt; stored in &lt;code dir=&quot;auto&quot;&gt;.github/ISSUE_TEMPLATE/&lt;/code&gt; that enforce structure and guide both humans and AI to provide the right information.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bug-reports&quot;&gt;Bug Reports&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For bugs, my template captures the essential debugging information:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;🐛 Bug&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;An unexpected problem or behavior.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[bug]: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Bug&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;expected_behavior&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Expected Behavior&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Describe what you expected to happen.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actual_behavior&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Actual Behavior&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Describe what actually happened.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;steps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Steps to Replicate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;List the steps to replicate the bug.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1. Step 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2. Step 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3. Step 3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When I prompt Copilot with something like &lt;em&gt;“The cluster fails to start when Cilium is enabled on ARM64 — create a bug issue”&lt;/em&gt;, it produces output that maps directly to these fields with accurate technical details.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;feature-requests&quot;&gt;Feature Requests&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For features, I use the standard agile user story format with acceptance criteria:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;🚀 Feature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Submit a new feature as a user story with acceptance criteria.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[feature]: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Feature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;user_story&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;User Story&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Use the standard agile story format.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;**As a** [role],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;**I want** [an action or feature],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;**So that** [a reason or benefit].&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;acceptance_criteria&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Acceptance Criteria&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Provide a task list of conditions that must be satisfied.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] Criteria 1: Describe the first acceptance criterion here.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] Criteria 2: Describe the second acceptance criterion here.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] Criteria 3: Describe additional criteria as needed.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The user story format forces clarity about &lt;em&gt;who&lt;/em&gt; benefits, &lt;em&gt;what&lt;/em&gt; they need, and &lt;em&gt;why&lt;/em&gt; it matters. Claude Opus 4.5 excels at filling this in with genuine user-centric thinking rather than developer-centric feature descriptions.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;improvement-katas&quot;&gt;Improvement Katas&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For continuous improvement initiatives, I use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Toyota_Kata&quot;&gt;Toyota Kata framework&lt;/a&gt;. Unlike bugs and features, katas focus on human processes rather than code — things like team communication, workflow bottlenecks, or development practices.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;🥋 Improvement Kata&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Use the Improvement Kata framework for structured problem-solving.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[kata]: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Kata&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;problem&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Problem&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Describe the current situation and why it is problematic.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Current situation:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Why this is problematic:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;definition-of-awesome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Definition of Awesome&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;How would we like it to be? Describe the ideal state.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;next-target-condition&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Next Target Condition&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;X weeks from now, what has changed?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Actions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;List of actions to take toward the target condition.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] **Action 1:** ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] **Action 2:** ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- [ ] **Action 3:** ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;validations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This template is particularly powerful with AI assistance. Opus 4.5 can help structure observations about team dynamics, identify process bottlenecks, and propose actionable improvement experiments — turning vague frustrations into measurable improvement initiatives.&lt;/p&gt;
&lt;p&gt;You can find all my issue templates in the &lt;a href=&quot;https://github.com/devantler-tech/.github/tree/main/.github/ISSUE_TEMPLATE&quot;&gt;devantler-tech/.github repository&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-real-example-from-prompt-to-issue&quot;&gt;A Real Example: From Prompt to Issue&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To show this workflow in action, here’s a real issue I created for KSail using this approach.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-i-typed&quot;&gt;What I Typed&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Analyze the existing addon patterns in pkg/svc/installer/ (cert-manager,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;metrics-server, CNI, CSI) and create a feature issue for adding a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;--policy-engine flag that supports Kyverno and Gatekeeper. Use the&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;FEATURE.yaml template from .github/ISSUE_TEMPLATE/.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it — one sentence describing what I wanted, plus a reference to existing patterns and my template.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-the-ai-produced&quot;&gt;What the AI Produced&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Copilot analyzed the codebase, identified all the integration points, and generated &lt;a href=&quot;https://github.com/devantler-tech/ksail/issues/1614&quot;&gt;this issue&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**[&lt;/span&gt;&lt;span&gt;feature&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;: Add &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;--policy-engine&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; flag to install Kyverno or Gatekeeper&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;User Story&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;As a&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; KSail user,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;I want&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; to install a policy engine (Kyverno or Gatekeeper) when creating a cluster,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;So that&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; I can enforce policies on my workloads from the start without manual post-provisioning steps.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;Background&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Policy engines are essential for enforcing security, compliance, and best practices in Kubernetes clusters. Adding first-class support for policy engines follows the same pattern as existing addons (cert-manager, metrics-server, CNI, CSI, GitOps engines).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The AI didn’t stop there. It analyzed the existing addon implementations and produced detailed implementation requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;### Implementation Requirements&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#### 1. API Types (&lt;/span&gt;&lt;span&gt;`pkg/apis/cluster/v1alpha1/`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Add &lt;/span&gt;&lt;span&gt;`PolicyEngine`&lt;/span&gt;&lt;span&gt; enum type with values &lt;/span&gt;&lt;span&gt;`PolicyEngineNone`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`PolicyEngineKyverno`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`PolicyEngineGatekeeper`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Add &lt;/span&gt;&lt;span&gt;`PolicyEngine PolicyEngine`&lt;/span&gt;&lt;span&gt; field to &lt;/span&gt;&lt;span&gt;`ClusterSpec`&lt;/span&gt;&lt;span&gt; struct&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Implement &lt;/span&gt;&lt;span&gt;`Set()`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`String()`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`Type()`&lt;/span&gt;&lt;span&gt; methods for pflags compatibility&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#### 2. Installer Service (&lt;/span&gt;&lt;span&gt;`pkg/svc/installer/`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Create &lt;/span&gt;&lt;span&gt;`pkg/svc/installer/kyverno/installer.go`&lt;/span&gt;&lt;span&gt; implementing &lt;/span&gt;&lt;span&gt;`installer.Installer`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Create &lt;/span&gt;&lt;span&gt;`pkg/svc/installer/gatekeeper/installer.go`&lt;/span&gt;&lt;span&gt; implementing &lt;/span&gt;&lt;span&gt;`installer.Installer`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#### 3. CLI Integration (&lt;/span&gt;&lt;span&gt;`cmd/cluster/`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [ ] Add &lt;/span&gt;&lt;span&gt;`--policy-engine`&lt;/span&gt;&lt;span&gt; flag to &lt;/span&gt;&lt;span&gt;`init.go`&lt;/span&gt;&lt;span&gt; and &lt;/span&gt;&lt;span&gt;`create.go`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It even included the correct Helm chart references:&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Engine&lt;/th&gt;&lt;th&gt;Repo URL&lt;/th&gt;&lt;th&gt;Chart&lt;/th&gt;&lt;th&gt;Default Namespace&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Kyverno&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;https://kyverno.github.io/kyverno/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kyverno/kyverno&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kyverno&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Gatekeeper&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;https://open-policy-agent.github.io/gatekeeper/charts&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;gatekeeper/gatekeeper&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;gatekeeper-system&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;the-result&quot;&gt;The Result&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The generated issue required minimal editing — just some polish to the acceptance criteria. And here’s the kicker: I implemented the entire feature using another single prompt:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Implement https://github.com/devantler-tech/ksail/issues/1614&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The issue was so well-structured that Copilot could follow it as a specification. Total time from idea to merged PR: about 2 hours of active work, and about 4 hours in total (with the time AI was working). Imagine how much work you could do if you parallelized this across multiple issues!&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;real-world-impact&quot;&gt;Real-World Impact&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I use this approach in two contexts:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;personal-projects-ksail&quot;&gt;Personal Projects (KSail)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt;, my tool for creating, maintaining and operating Kubernetes clusters with ease, this workflow has been invaluable. When I notice something that needs improvement, I can quickly generate a detailed issue without interrupting my flow. The AI understands the Go codebase, the embedded tool clients, and the overall architecture — producing issues that accurately describe both problems and solutions.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;professional-work-tv-2&quot;&gt;Professional Work (TV 2)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;At &lt;a href=&quot;https://tv2.dk&quot;&gt;TV 2&lt;/a&gt;, where I work, this approach has had an even bigger impact.&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Traditional&lt;/th&gt;&lt;th&gt;AI-Assisted&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Investigation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;1-2 hours&lt;/td&gt;&lt;td&gt;1 min (context selection)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Analysis&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;1-2 hour&lt;/td&gt;&lt;td&gt;1 min (prompting)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Writing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;0.5-1 hour&lt;/td&gt;&lt;td&gt;3 min (AI generation)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Structuring&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;10 min&lt;/td&gt;&lt;td&gt;— (template handles it)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Review&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;30 min&lt;/td&gt;&lt;td&gt;25 min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;3-6 hours&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;~30 minutes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Time saved on investigation&lt;/strong&gt;: Instead of spending hours digging through unfamiliar code, I can generate a comprehensive issue in under 30 minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Better refinement sessions&lt;/strong&gt;: When issues arrive at refinement already following our template and containing accurate technical details, the team can focus on alignment, estimation and planning rather than clarification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Senior-level quality&lt;/strong&gt;: Opus 4.5 is remarkably capable at writing issues that meet senior software engineers’ expectations. The descriptions are technical enough to be useful, clear enough to be actionable, and structured enough to fit our workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Every issue follows the same format, making it easier for team members to find the information they need.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-this-approach-doesnt-work&quot;&gt;When This Approach Doesn’t Work&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This workflow isn’t magic. There are situations where AI-assisted issue creation falls short:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Issues requiring stakeholder input&lt;/strong&gt;: If the issue depends on business decisions, user research, or information that isn’t in the codebase, the AI can’t help much. It can structure the issue, but it can’t interview your product owner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Highly sensitive or confidential context&lt;/strong&gt;: Be careful about what context you share with AI tools. If the issue involves security vulnerabilities, credentials, or proprietary business logic, you may need to write it manually or heavily redact the AI’s output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cross-repository or external dependencies&lt;/strong&gt;: When issues span multiple repositories or depend on external systems the AI can’t see, the generated context may be incomplete. Always verify external references.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vague problems without symptoms&lt;/strong&gt;: If you can’t articulate even a rough description of what’s wrong, the AI can’t investigate effectively. “Something feels slow” is too vague — “API response times have increased” gives the AI something to search for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Novel codebases&lt;/strong&gt;: The AI is remarkably good at pattern recognition. In well-structured codebases with consistent patterns (like KSail’s addon system), it excels. In chaotic legacy codebases with no clear patterns, results vary.&lt;/p&gt;
&lt;p&gt;For these situations, I fall back to traditional issue creation, or a mix of both — but I always use issue templates to ensure structure.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;tips-for-getting-the-best-results&quot;&gt;Tips for Getting the Best Results&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After a month of using this workflow, here’s what I’ve learned:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-be-specific-about-scope&quot;&gt;1. Be Specific About Scope&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Tell Copilot exactly which files or folders to analyze. The more focused the context, the more accurate the output.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-describe-the-outcome-not-the-solution&quot;&gt;2. Describe the Outcome, Not the Solution&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Say “users can’t access the metrics endpoint” rather than “fix the metrics-server RBAC”. Let the AI investigate and propose solutions.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-always-reference-your-templates&quot;&gt;3. Always Reference Your Templates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Explicitly mention your issue templates. This ensures the output follows your team’s conventions.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-review-with-fresh-eyes&quot;&gt;4. Review with Fresh Eyes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;While the generated issues are usually excellent, always read them as if you were a team member seeing them for the first time. Add context that only you know.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-iterate-on-complex-issues&quot;&gt;5. Iterate on Complex Issues&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For large features, start with a high-level issue, then generate sub-issues for specific components.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;GitHub Copilot with Claude Opus 4.5 has transformed how I create issues. What used to take 3-6 hours now takes around 30 minutes, and the quality is often higher than what I’d produce manually.&lt;/p&gt;
&lt;pre&gt;mindmap
  root((AI-Powered Issues))
    Fast
      30 min vs 3-6 hours
      No manual investigation
    Consistent
      Same template every time
      Team conventions followed
    Accurate
      Deep codebase analysis
      Real problems identified
    Team-Friendly
      Ready for refinement
      Clear acceptance criteria&lt;/pre&gt;
&lt;p&gt;The combination of AI analysis and structured templates creates a workflow that’s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast&lt;/strong&gt;: Minimal time spent on investigation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent&lt;/strong&gt;: Every issue follows the same format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accurate&lt;/strong&gt;: The AI understands codebases deeply&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team-friendly&lt;/strong&gt;: Issues arrive at refinement ready to discuss&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re spending too much time writing issues, or if your team’s refinement sessions are slowed down by unclear issue descriptions, give this workflow a try. The upfront investment in good issue templates pays dividends when combined with AI-powered generation, but also if you write issues manually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Want to try this yourself?&lt;/strong&gt; Use my &lt;a href=&quot;https://github.com/devantler-tech/.github/tree/main/.github/ISSUE_TEMPLATE&quot;&gt;issue templates&lt;/a&gt; and adapt them to your team’s conventions. The combination of structured templates and AI-powered generation might just change how you work.&lt;/p&gt;
&lt;p&gt;The future of software development isn’t AI replacing developers — it’s AI handling the tedious parts so developers can focus on what matters. I am personally ecstatic about how this workflow has improved my productivity and the quality of my work. I hope it helps you too!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5 — the motor of the workflow it describes. The content reflects my genuine experiences and opinions; the AI helped structure and articulate them.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>ai</category><category>github</category><category>copilot</category><category>productivity</category><category>developer-experience</category></item><item><title>Building KSail: From Shell Scripts to .NET to Go</title><link>https://devantler.tech/blog/building-ksail-from-shell-to-dotnet-to-go/</link><guid isPermaLink="true">https://devantler.tech/blog/building-ksail-from-shell-to-dotnet-to-go/</guid><description>The journey of building KSail through three major rewrites and what I learned along the way.

</description><pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Building a developer tool is rarely a straight path. &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt; started as a humble shell script in late 2023 and has evolved through three major rewrites to become what it is today: a Go-based CLI that bundles common Kubernetes tooling into a single binary. This post tells the story of that journey.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Setting up and operating Kubernetes clusters is a skill of its own. It often requires juggling multiple CLI tools (kubectl, helm, kind, k3d, flux, argocd, sops), writing bespoke scripts, and dealing with inconsistent developer workflows determined by specific projects.&lt;/p&gt;
&lt;p&gt;This complexity slows down development, makes Kubernetes intimidating for newcomers, and makes it difficult to maintain reproducible environments. I wanted a tool that would remove the tooling overhead so developers could focus on their workloads.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;phase-1-shell-scripts-december-2023&quot;&gt;Phase 1: Shell Scripts (December 2023)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The first commit to KSail was on December 31, 2023. It started as a Bash script called &lt;code dir=&quot;auto&quot;&gt;ksail.sh&lt;/code&gt; that wrapped Talos Linux commands to create local Kubernetes clusters.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Early KSail was literally just a shell script&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ksail.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;up&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Create a cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ksail.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;down&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# Destroy it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This worked, but shell scripts have significant limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No type safety&lt;/strong&gt;: Bugs only surface at runtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited error handling&lt;/strong&gt;: Bash’s error handling is notoriously awkward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform issues&lt;/strong&gt;: Scripts that work on macOS might fail on Linux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency management&lt;/strong&gt;: Users needed every tool installed separately&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Within the first week, I added K3d support alongside Talos, and the script was becoming unwieldy. It was clear that a more structured approach was needed.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;phase-2-the-net-rewrite-january-2024&quot;&gt;Phase 2: The .NET Rewrite (January 2024)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;On January 8, 2024 — just over a week after the initial commit — I rewrote KSail as a .NET console application with PR #25: “Rework KSail to .NET Project with wrapped binaries and libraries.”&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;why-net&quot;&gt;Why .NET?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;At the time, .NET was my primary language. I was comfortable with C#, and .NET 8 had just been released with great cross-platform support. The ecosystem had mature libraries for CLI development (&lt;code dir=&quot;auto&quot;&gt;System.CommandLine&lt;/code&gt;), and I could leverage NuGet for dependency management.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The .NET version took an interesting approach: it embedded the required binaries (kubectl, kind, k3d, talosctl, etc.) directly into the application and extracted them at runtime. This meant users only needed to install KSail itself — the dependencies came bundled.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Pseudo-code of the .NET approach&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;KindClient&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; _binaryPath;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;KindClient&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;_binaryPath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ExtractEmbeddedBinary&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; Task &lt;/span&gt;&lt;span&gt;CreateClusterAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; name, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; config)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProcessRunner&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;RunAsync&lt;/span&gt;&lt;span&gt;(_binaryPath, &lt;/span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;create cluster --name &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; --config &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Over 2024, the .NET version grew substantially. I presented it at KCD Denmark 2024 in a talk titled &lt;a href=&quot;https://youtu.be/Q-Hfn_-B7p8&quot;&gt;“KSail - a Kubernetes SDK for local GitOps development and CI”&lt;/a&gt;. By the end of 2024, the project had accumulated 788 commits and supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple distributions (Kind, K3d, Talos)&lt;/li&gt;
&lt;li&gt;GitOps engines (Flux, ArgoCD)&lt;/li&gt;
&lt;li&gt;Secret management with SOPS&lt;/li&gt;
&lt;li&gt;Declarative configuration via &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mirror registries for avoiding Docker Hub rate limits&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;growing-pains&quot;&gt;Growing Pains&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Despite its functionality, the .NET version had issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Binary size&lt;/strong&gt;: Bundling all those executables made the final binary quite large&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update complexity&lt;/strong&gt;: Every upstream tool update required downloading new binaries, embedding them, and releasing a new version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform coverage&lt;/strong&gt;: Supporting Linux amd64, Linux arm64, macOS arm64, and Windows meant managing multiple binary variants&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Startup time&lt;/strong&gt;: Extracting binaries on first run added latency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ecosystem mismatch&lt;/strong&gt;: Most Kubernetes tooling is written in Go, and calling out to external processes felt like fighting the ecosystem&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;phase-3-the-go-rewrite-december-2025&quot;&gt;Phase 3: The Go Rewrite (December 2025)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;On December 18, 2025, I merged PR #1448: “BREAKING CHANGE: Migrate ksail from .NET to Go.” This wasn’t a port — it was a complete rewrite that took advantage of Go’s unique position in the Kubernetes ecosystem.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;why-go&quot;&gt;Why Go?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Kubernetes ecosystem is built on Go. kubectl, kind, k3d, helm, flux — they’re all Go projects. This means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Import instead of wrap&lt;/strong&gt;: Instead of shelling out to external binaries, KSail can import these tools as Go libraries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single binary&lt;/strong&gt;: No embedded executables to extract; everything compiles into one binary&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast startup&lt;/strong&gt;: No extraction step, no JIT compilation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;First-class Kubernetes support&lt;/strong&gt;: client-go is the reference implementation for Kubernetes clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-compilation&lt;/strong&gt;: Go’s cross-compilation is trivial compared to .NET’s AOT publishing&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;the-new-architecture&quot;&gt;The New Architecture&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Go version embeds tool functionality directly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Instead of shelling out, we use the libraries directly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sigs.k8s.io/kind/pkg/cluster&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;helm.sh/helm/v4/pkg/action&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;github.com/fluxcd/flux2/v2/pkg/bootstrap&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;p &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;KindProvisioner) &lt;/span&gt;&lt;span&gt;Create&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context.Context, &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;KindConfig) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NewProvider&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CreateWithConfigFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CreateWithWaitForReady&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Timeout&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The project structure is clean and idiomatic:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pkg/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── apis/          # Configuration types&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── cli/           # Command implementations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── client/        # Embedded tool clients (kubectl, helm, flux, etc.)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── di/            # Dependency injection&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── io/            # File and config utilities&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── k8s/           # Kubernetes helpers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── svc/           # Services (installers, provisioners, reconcilers)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── utils/         # Shared utilities&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After three rewrites and over 2,200 commits, here’s what I’ve learned:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-choose-the-right-language-for-the-ecosystem&quot;&gt;1. Choose the Right Language for the Ecosystem&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Go rewrite wasn’t about Go being “better” than .NET — it was about Go being the right tool for Kubernetes development. When your tool integrates deeply with an ecosystem, speaking its native language matters.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-wrapping-vs-embedding&quot;&gt;2. Wrapping vs. Embedding&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The .NET version wrapped external binaries. The Go version embeds libraries. The difference is profound:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wrapping&lt;/strong&gt;: You’re at the mercy of the tool’s CLI interface, which can change between versions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding&lt;/strong&gt;: You have direct access to the tool’s internals, with type safety and proper error handling&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;3-start-simple-then-structure&quot;&gt;3. Start Simple, Then Structure&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The shell script taught me what the tool needed to do. The .NET version taught me how to structure it properly. The Go version combined those lessons with the right technology choice.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-dont-fear-the-rewrite&quot;&gt;4. Don’t Fear the Rewrite&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each rewrite made KSail better. The shell script validated the idea. The .NET version proved the concept and found users. The Go version delivered on the original vision of a single, fast, portable binary.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-ksail-is-today&quot;&gt;Where KSail Is Today&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The current Go version of KSail provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One binary&lt;/strong&gt; — No external dependencies except Docker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple distributions&lt;/strong&gt; — Kind, K3d, and Talos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable stack&lt;/strong&gt; — CNI (Cilium, Calico), CSI, metrics-server, cert-manager&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitOps native&lt;/strong&gt; — Built-in Flux and ArgoCD support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret management&lt;/strong&gt; — Integrated SOPS encryption&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative configuration&lt;/strong&gt; — Everything as code in &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Installation is simple:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# or&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/devantler-tech/ksail/v5@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And usage is straightforward:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Kind&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cni&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cilium&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--gitops-engine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Flux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-k&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Opens K9s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail is actively developed with a focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud providers&lt;/strong&gt;: Hetzner Cloud support is in progress&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced GitOps workflows&lt;/strong&gt;: Better reconciliation and debugging tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced SOPS integration&lt;/strong&gt;: More secret management features for e.g. GitOps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project is open source under the Apache 2.0 license. You can find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;github.com/devantler-tech/ksail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;ksail.devantler.tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go Package Docs&lt;/strong&gt;: &lt;a href=&quot;https://pkg.go.dev/github.com/devantler-tech/ksail/v5&quot;&gt;pkg.go.dev/github.com/devantler-tech/ksail/v5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re working with Kubernetes locally, give KSail a try. And if you’ve been through similar rewrites with your own projects, I’d love to hear your stories.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5. The content reflects my genuine experiences and journey; the AI helped structure and articulate it.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>ksail</category><category>go</category><category>dotnet</category><category>architecture</category><category>open-source</category></item><item><title>Local Kubernetes Development with KSail and Talos</title><link>https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-talos/</link><guid isPermaLink="true">https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-talos/</guid><description>A guide to creating local Kubernetes development clusters using KSail with Talos Linux in Docker.

</description><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.talos.dev/&quot;&gt;Talos Linux&lt;/a&gt; is a minimal, immutable operating system designed specifically for Kubernetes. While it’s often used in production environments, you can also run Talos locally in Docker for development. Combined with &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt;, you get a security-focused local development experience. This post shows you how.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-talos--ksail&quot;&gt;Why Talos + KSail?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Talos Linux&lt;/strong&gt; takes a radically different approach to running Kubernetes. There’s no SSH, no shell, no package manager — just the Talos API and Kubernetes. The entire OS is immutable and managed declaratively via configuration files.&lt;/p&gt;
&lt;p&gt;This approach provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Minimal attack surface&lt;/strong&gt; — No shell means no shell exploits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No configuration drift&lt;/strong&gt; — Immutable OS prevents unauthorized changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative everything&lt;/strong&gt; — OS configuration is versioned and reproducible&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production parity&lt;/strong&gt; — Your local environment matches production Talos clusters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;KSail&lt;/strong&gt; wraps Talos tooling into a single binary, handling cluster provisioning, GitOps setup, and workload management through one consistent interface.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You need Docker installed and running. Verify with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If this command works, you’re ready to go.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ksail&quot;&gt;Step 1: Install KSail&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail is distributed as a single binary. Install via Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Or with Go:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/devantler-tech/ksail/v5@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Verify the installation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-scaffold-your-cluster-project&quot;&gt;Step 2: Scaffold Your Cluster Project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail’s &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; command scaffolds a complete project structure. For a Talos cluster with Cilium CNI:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Talos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cni&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Cilium&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This creates &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt; (cluster configuration), &lt;code dir=&quot;auto&quot;&gt;talos/&lt;/code&gt; (Talos configs), and &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; (your Kubernetes manifests).&lt;/p&gt;
&lt;aside aria-label=&quot;Note&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Note&lt;/p&gt;&lt;div&gt;&lt;p&gt;Talos doesn’t include a default CNI, so you should specify one. Cilium is recommended for its eBPF-based networking and observability features.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;For all available flags and configuration options, see the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-init&quot;&gt;CLI flags reference&lt;/a&gt; — All &lt;code dir=&quot;auto&quot;&gt;cluster init&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/declarative-configuration&quot;&gt;ksail.yaml reference&lt;/a&gt; — Configuration file schema&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/features&quot;&gt;Features overview&lt;/a&gt; — CNI, CSI, GitOps, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-create-the-cluster&quot;&gt;Step 3: Create the Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Create and start your cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates Docker containers running Talos Linux&lt;/li&gt;
&lt;li&gt;Bootstraps the Talos control plane&lt;/li&gt;
&lt;li&gt;Initializes etcd and the Kubernetes API server&lt;/li&gt;
&lt;li&gt;Installs your selected CNI, CSI, and other components&lt;/li&gt;
&lt;li&gt;Configures your local kubeconfig and talosconfig&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process takes 1-2 minutes. Talos clusters take slightly longer than Kind or K3d because of the additional bootstrap steps for the immutable OS.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-working-with-your-cluster&quot;&gt;Step 4: Working with Your Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once your cluster is running, KSail provides commands for common operations:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Show cluster status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# List all KSail-managed clusters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Open K9s for interactive management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Stop the cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# Start a stopped cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your kubeconfig is automatically configured, so standard &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt; commands work too.&lt;/p&gt;
&lt;p&gt;For the full command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-root&quot;&gt;Cluster Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-5-deploying-workloads&quot;&gt;Step 5: Deploying Workloads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail wraps kubectl and GitOps operations under the &lt;code dir=&quot;auto&quot;&gt;workload&lt;/code&gt; command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-k&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Apply manifests (kubectl workflow)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;# Push to GitOps source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reconcile&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;# Trigger GitOps reconciliation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For the full workload command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/workload/workload-root&quot;&gt;Workload Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cleaning-up&quot;&gt;Cleaning Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you’re done:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This removes the Docker containers and cleans up kubeconfig and talosconfig entries.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Explore the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt; for advanced topics including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-node Talos clusters for testing HA scenarios&lt;/li&gt;
&lt;li&gt;Enabling GitOps with Flux or ArgoCD&lt;/li&gt;
&lt;li&gt;Secret management with SOPS&lt;/li&gt;
&lt;li&gt;Mirror registries to avoid Docker Hub rate limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you’re comfortable with Talos locally, you can deploy to cloud infrastructure. See &lt;a href=&quot;https://devantler.tech/blog/creating-development-kubernetes-clusters-on-hetzner-with-ksail-and-talos/&quot;&gt;Creating Development Clusters on Hetzner with KSail and Talos&lt;/a&gt; for a guide on running Talos in the cloud.&lt;/p&gt;
&lt;p&gt;For simpler local setups, check out &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-kind/&quot;&gt;Kind with KSail&lt;/a&gt; for vanilla Kubernetes or &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-k3d/&quot;&gt;K3s with KSail&lt;/a&gt; for a batteries-included experience.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;feedback-welcome&quot;&gt;Feedback Welcome&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;KSail is under active development. If you encounter bugs or find missing features, please &lt;a href=&quot;https://github.com/devantler-tech/ksail/issues&quot;&gt;open an issue on GitHub&lt;/a&gt;. Your feedback helps improve the tool for everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>kubernetes</category><category>ksail</category><category>talos</category><category>local-development</category></item><item><title>Local Kubernetes Development with KSail and K3d</title><link>https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-k3d/</link><guid isPermaLink="true">https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-k3d/</guid><description>A guide to creating local Kubernetes development clusters using KSail with K3d (K3s in Docker).

</description><pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;K3s is Rancher’s lightweight Kubernetes distribution, and when combined with &lt;a href=&quot;https://k3d.io/&quot;&gt;K3d&lt;/a&gt; and &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt;, you get a fast, batteries-included local development experience. This post shows you how to get started.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-k3d--ksail&quot;&gt;Why K3d + KSail?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;K3s&lt;/strong&gt; is a lightweight, certified Kubernetes distribution designed for resource-constrained environments. It comes with sensible defaults and includes components like Traefik ingress, local-path storage provisioner, and metrics-server out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K3d&lt;/strong&gt; wraps K3s to run it in Docker containers, making it perfect for local development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KSail&lt;/strong&gt; ties it all together with a single binary that handles cluster provisioning, GitOps setup, and workload management. You get one consistent interface regardless of the underlying distribution.&lt;/p&gt;
&lt;p&gt;Together, they provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast startup&lt;/strong&gt; — Clusters ready in about 30 seconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batteries included&lt;/strong&gt; — Storage, ingress, and metrics built in&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low resource usage&lt;/strong&gt; — K3s is optimized for minimal overhead&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative configuration&lt;/strong&gt; — Everything as code in &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You need Docker installed and running. Verify with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If this command works, you’re ready to go.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ksail&quot;&gt;Step 1: Install KSail&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail is distributed as a single binary. Install via Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Or with Go:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/devantler-tech/ksail/v5@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Verify the installation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-scaffold-your-cluster-project&quot;&gt;Step 2: Scaffold Your Cluster Project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail’s &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; command scaffolds a complete project structure:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;K3s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This creates &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt; (cluster configuration), &lt;code dir=&quot;auto&quot;&gt;k3d.yaml&lt;/code&gt; (K3d config), and &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; (your Kubernetes manifests).&lt;/p&gt;
&lt;p&gt;For all available flags and configuration options, see the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-init&quot;&gt;CLI flags reference&lt;/a&gt; — All &lt;code dir=&quot;auto&quot;&gt;cluster init&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/declarative-configuration&quot;&gt;ksail.yaml reference&lt;/a&gt; — Configuration file schema&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/features&quot;&gt;Features overview&lt;/a&gt; — CNI, CSI, GitOps, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-create-the-cluster&quot;&gt;Step 3: Create the Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Create and start your cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates Docker containers as K3s nodes&lt;/li&gt;
&lt;li&gt;Bootstraps the K3s control plane with built-in components&lt;/li&gt;
&lt;li&gt;Installs any additional components you configured (Cilium, Flux, etc.)&lt;/li&gt;
&lt;li&gt;Configures your local kubeconfig&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process takes about 30 seconds for a single-node cluster.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-working-with-your-cluster&quot;&gt;Step 4: Working with Your Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once your cluster is running, KSail provides commands for common operations:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Show cluster status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# List all KSail-managed clusters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Open K9s for interactive management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Stop the cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# Start a stopped cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your kubeconfig is automatically configured, so standard &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt; commands work too.&lt;/p&gt;
&lt;p&gt;For the full command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-root&quot;&gt;Cluster Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-5-deploying-workloads&quot;&gt;Step 5: Deploying Workloads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail wraps kubectl and GitOps operations under the &lt;code dir=&quot;auto&quot;&gt;workload&lt;/code&gt; command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-k&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Apply manifests (kubectl workflow)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;# Push to GitOps source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reconcile&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;# Trigger GitOps reconciliation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For the full workload command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/workload/workload-root&quot;&gt;Workload Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cleaning-up&quot;&gt;Cleaning Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you’re done:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This removes the Docker containers and cleans up kubeconfig entries.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Explore the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt; for advanced topics including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Swapping the default CNI for Cilium or Calico&lt;/li&gt;
&lt;li&gt;Enabling GitOps with Flux or ArgoCD&lt;/li&gt;
&lt;li&gt;Secret management with SOPS&lt;/li&gt;
&lt;li&gt;Mirror registries to avoid Docker Hub rate limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want a vanilla Kubernetes experience, check out the post on &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-kind/&quot;&gt;Kind with KSail&lt;/a&gt;. For an immutable, security-focused distribution, see &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-talos/&quot;&gt;Talos with KSail&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;feedback-welcome&quot;&gt;Feedback Welcome&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;KSail is under active development. If you encounter bugs or find missing features, please &lt;a href=&quot;https://github.com/devantler-tech/ksail/issues&quot;&gt;open an issue on GitHub&lt;/a&gt;. Your feedback helps improve the tool for everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>kubernetes</category><category>ksail</category><category>k3s</category><category>k3d</category><category>local-development</category></item><item><title>Local Kubernetes Development with KSail and Kind</title><link>https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-kind/</link><guid isPermaLink="true">https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-kind/</guid><description>A guide to creating local Kubernetes development clusters using KSail with Kind (vanilla Kubernetes) in Docker.

</description><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Getting started with Kubernetes development shouldn’t require cloud infrastructure or complex setup procedures. With &lt;a href=&quot;https://kind.sigs.k8s.io/&quot;&gt;Kind&lt;/a&gt; (Kubernetes in Docker) and &lt;a href=&quot;https://github.com/devantler-tech/ksail&quot;&gt;KSail&lt;/a&gt;, you can have a local cluster running in under a minute. This post shows you how.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-kind--ksail&quot;&gt;Why Kind + KSail?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Kind&lt;/strong&gt; (Kubernetes in Docker) runs Kubernetes clusters using Docker containers as nodes. It’s fast, lightweight, and provides a vanilla Kubernetes experience that closely matches production environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KSail&lt;/strong&gt; wraps Kind and other tools into a single binary, giving you one consistent interface for cluster provisioning, GitOps setup, and workload management. Instead of learning multiple CLIs, you use &lt;code dir=&quot;auto&quot;&gt;ksail cluster&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ksail workload&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;Together, they provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast iteration&lt;/strong&gt; — Clusters start in under 60 seconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero cost&lt;/strong&gt; — Everything runs locally on your machine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vanilla Kubernetes&lt;/strong&gt; — No distribution-specific quirks to learn&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative configuration&lt;/strong&gt; — Everything as code in &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You need Docker installed and running. Verify with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If this command works, you’re ready to go.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ksail&quot;&gt;Step 1: Install KSail&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail is distributed as a single binary. Install via Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Or with Go:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/devantler-tech/ksail/v5@latest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Verify the installation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-scaffold-your-cluster-project&quot;&gt;Step 2: Scaffold Your Cluster Project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail’s &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; command scaffolds a complete project structure:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--distribution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Vanilla&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This creates &lt;code dir=&quot;auto&quot;&gt;ksail.yaml&lt;/code&gt; (cluster configuration), &lt;code dir=&quot;auto&quot;&gt;kind.yaml&lt;/code&gt; (Kind config), and &lt;code dir=&quot;auto&quot;&gt;k8s/&lt;/code&gt; (your Kubernetes manifests).&lt;/p&gt;
&lt;p&gt;For all available flags and configuration options, see the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-init&quot;&gt;CLI flags reference&lt;/a&gt; — All &lt;code dir=&quot;auto&quot;&gt;cluster init&lt;/code&gt; options&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/configuration/declarative-configuration&quot;&gt;ksail.yaml reference&lt;/a&gt; — Configuration file schema&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ksail.devantler.tech/features&quot;&gt;Features overview&lt;/a&gt; — CNI, CSI, GitOps, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-create-the-cluster&quot;&gt;Step 3: Create the Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Create and start your cluster:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates Docker containers as Kubernetes nodes&lt;/li&gt;
&lt;li&gt;Bootstraps the Kubernetes control plane&lt;/li&gt;
&lt;li&gt;Installs your selected CNI, CSI, and other components&lt;/li&gt;
&lt;li&gt;Configures your local kubeconfig&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process takes under 60 seconds for a single-node cluster.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-working-with-your-cluster&quot;&gt;Step 4: Working with Your Cluster&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once your cluster is running, KSail provides commands for common operations:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Show cluster status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# List all KSail-managed clusters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Open K9s for interactive management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Stop the cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# Start a stopped cluster&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your kubeconfig is automatically configured, so standard &lt;code dir=&quot;auto&quot;&gt;kubectl&lt;/code&gt; commands work too.&lt;/p&gt;
&lt;p&gt;For the full command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/cluster/cluster-root&quot;&gt;Cluster Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-5-deploying-workloads&quot;&gt;Step 5: Deploying Workloads&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;KSail wraps kubectl and GitOps operations under the &lt;code dir=&quot;auto&quot;&gt;workload&lt;/code&gt; command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-k&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./k8s&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Apply manifests (kubectl workflow)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;# Push to GitOps source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reconcile&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;# Trigger GitOps reconciliation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For the full workload command reference, see &lt;a href=&quot;https://ksail.devantler.tech/configuration/cli-flags/workload/workload-root&quot;&gt;Workload Commands&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cleaning-up&quot;&gt;Cleaning Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you’re done:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ksail&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cluster&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This removes the Docker containers and cleans up kubeconfig entries.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Explore the &lt;a href=&quot;https://ksail.devantler.tech&quot;&gt;KSail documentation&lt;/a&gt; for advanced topics including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding Cilium or Calico as your CNI&lt;/li&gt;
&lt;li&gt;Enabling GitOps with Flux or ArgoCD&lt;/li&gt;
&lt;li&gt;Secret management with SOPS&lt;/li&gt;
&lt;li&gt;Mirror registries to avoid Docker Hub rate limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Kind’s vanilla Kubernetes feels too basic, check out the posts on &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-k3d/&quot;&gt;K3s with KSail&lt;/a&gt; for a more batteries-included experience, or &lt;a href=&quot;https://devantler.tech/blog/local-kubernetes-development-with-ksail-and-talos/&quot;&gt;Talos with KSail&lt;/a&gt; for an immutable, security-focused distribution.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;feedback-welcome&quot;&gt;Feedback Welcome&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;KSail is under active development. If you encounter bugs or find missing features, please &lt;a href=&quot;https://github.com/devantler-tech/ksail/issues&quot;&gt;open an issue on GitHub&lt;/a&gt;. Your feedback helps improve the tool for everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This blog post was written with the assistance of GitHub Copilot and Claude Opus 4.5.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>kubernetes</category><category>ksail</category><category>kind</category><category>local-development</category></item><item><title>MacOS as a Developer Machine</title><link>https://devantler.tech/blog/macos-as-a-developer-machine/</link><guid isPermaLink="true">https://devantler.tech/blog/macos-as-a-developer-machine/</guid><description>Setting up MacOS as a developer machine can be a daunting task. In this post, I will share my learnings and experiences to help you get started.

</description><pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post, I will share my experience setting up MacOS as a developer machine. I will cover the following topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#managing-packages&quot;&gt;Managing Packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#managing-dotfiles-and-system-configuration&quot;&gt;Managing Dotfiles and System Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#git-ssh-and-gpg-keys&quot;&gt;Git, SSH, and GPG Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#passwords-and-secrets&quot;&gt;Passwords and Secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#configuring-the-terminal&quot;&gt;Configuring the Terminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#setting-up-your-ide&quot;&gt;Setting Up Your IDE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#remote-development&quot;&gt;Remote Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;managing-packages&quot;&gt;Managing Packages&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There are a few options available for managing packages on MacOS. Notable package managers include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.macports.org&quot;&gt;MacPorts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Due to Homebrew seemingly having the most popularity and the largest community, I went with Homebrew, and I have been using it extensively, for a few years now. In my experience, Homebrew has been reliable and easy to use, and I have yet to encounter that it is missing a feature that I need, or that it is not working as expected.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;installing-and-using-homebrew&quot;&gt;Installing and Using Homebrew&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Installing Homebrew is straightforward, and is well documented on the &lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew website&lt;/a&gt;. Once installed, you can install packages using the &lt;code dir=&quot;auto&quot;&gt;brew install&lt;/code&gt; command. For example, to install Git, you can run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It can also be used to install applications (Homebrew calls these casks), such as Visual Studio Code:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visual-studio-code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Homebrew can also snapshot your installed packages and applications, which can be useful when migrating to a new machine, or when you own multiple machines. You will learn how I prefer to manage my system configuration including my packages and applications in the next section &lt;a href=&quot;#managing-dotfiles-and-system-configuration&quot;&gt;Managing Dotfiles and System Configuration&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-using-homebrew&quot;&gt;Benefits of Using Homebrew&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Easy to install and use&lt;/li&gt;
&lt;li&gt;Extensive library of packages&lt;/li&gt;
&lt;li&gt;Ability to install applications&lt;/li&gt;
&lt;li&gt;Ability to snapshot installed packages and applications&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;managing-dotfiles-and-system-configuration&quot;&gt;Managing Dotfiles and System Configuration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Many applications and packages require storing configuration files in your home directory. These files are often referred to as dotfiles, as the folders and files are prefixed with a dot (&lt;code dir=&quot;auto&quot;&gt;.&lt;/code&gt;). Examples of dotfiles include &lt;code dir=&quot;auto&quot;&gt;.bashrc&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.gitconfig&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;.vimrc&lt;/code&gt;, but there are many more.&lt;/p&gt;
&lt;p&gt;When you use valuable time configuring, for example, your Git settings, you probably want to keep these settings when you switch to a new machine. This is where dotfiles come in handy. By storing your dotfiles in a version-controlled repository, you can easily sync your configuration across multiple machines.&lt;/p&gt;
&lt;p&gt;However keeping stuff in sync is no simple task, and there are a multitude of ways to do it. I have personally tried a few different approaches, that varied in complexity and flexibility. It was not until I discovered &lt;a href=&quot;https://github.com/lra/mackup&quot;&gt;MackUp&lt;/a&gt; that I found a solution that worked well for me.&lt;/p&gt;
&lt;p&gt;MackUp is a simple utility that, when executed, will symlink your dotfiles and system configuration to a storage provider of your choice. It supports a variety of storage providers, such as Dropbox, Google Drive, OneDrive, or Git. As I prefer to keep most of my stuff in Git, I of course use the Git storage provider. As such the following examples will also assume that Git is used as the storage provider.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;installing-and-using-mackup&quot;&gt;Installing and Using MackUp&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;To get started with MackUp, you can install it using Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mackup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After installing MackUp, you can configure it by creating a &lt;code dir=&quot;auto&quot;&gt;.mackup.cfg&lt;/code&gt; file in your home directory. Here is an example configuration file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[storage]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt; = git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt; = ~/dotfiles&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This configuration tells MackUp to store dotfiles in a Git repository located at &lt;code dir=&quot;auto&quot;&gt;~/dotfiles&lt;/code&gt;. You can then run &lt;code dir=&quot;auto&quot;&gt;mackup backup&lt;/code&gt; to symlink your dotfiles to the Git repository. When you switch to a new machine, you can run &lt;code dir=&quot;auto&quot;&gt;mackup restore&lt;/code&gt; to restore your dotfiles.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;storing-brew-bundles-in-mackup&quot;&gt;Storing Brew Bundles in MackUp&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In addition to storing your dotfiles, you can also store your Homebrew packages and applications in MackUp. This can be done by creating a Brewfile, which can be used to snapshot all the packages and applications you have installed on a machine. You can create a Brewfile of your installed packages and applications by running:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dump&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This will create a Brewfile in your home directory that lists all your installed packages and applications. To install these packages and applications on a new machine, you can run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;aside aria-label=&quot;Note&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Note&lt;/p&gt;&lt;div&gt;&lt;p&gt;The Brewfile is automatically picked up by MackUp when you run &lt;code dir=&quot;auto&quot;&gt;mackup backup&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h3 id=&quot;convenience-scripts&quot;&gt;Convenience Scripts&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;To make it easier to manage your dotfiles and system configuration, you can create convenience scripts that automate the process. Here are the scripts I use for managing my dotfiles:&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;backup-script&quot;&gt;Backup Script&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;This script will backup your dotfiles and system configuration to a Git repository, and store your Homebrew packages and applications in a Brewfile. It supports both macOS and Linux. To run the script:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;backup.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# To make the script executable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./backup.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The script must be placed in the root of your dotfiles repository. Run it from your home directory.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#!/bin/bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;check_os&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Darwin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ] &amp;#x26;&amp;#x26; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Linux&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;This script is only for macOS and Linux&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;exit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;create_mackup_config&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;📝 Creating Mackup config&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.mackup.cfg&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[storage]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;engine = file_system&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;path = &lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/dotfiles&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.mackup.cfg&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_homebrew&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🍺 Installing Homebrew&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;/bin/bash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-fsSL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/.zprofile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;/opt/homebrew/bin/brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shellenv&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_rosetta&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;📄 Installing Rosetta 2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;softwareupdate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install-rosetta&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agree-to-license&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_mackup&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mackup&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;📦 Installing Mackup&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mackup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;stop_gpg_agent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🔒 Stopping GPG agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--kill&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-connect-agent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpg-connect-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;killagent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/bye&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GPG tools not found, skipping GPG agent stop&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;start_gpg_agent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🔑 Starting GPG agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--daemon&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--reload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GPG tools not found, skipping GPG agent start&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backup_homebrew&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🍻 Backing up Homebrew packages&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dump&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cleanup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upgrade&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;check_os&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;create_mackup_config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_homebrew&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Darwin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;install_rosetta&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_mackup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;stop_gpg_agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backup_homebrew&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mackup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;backup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;start_gpg_agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h4 id=&quot;restore-script&quot;&gt;Restore Script&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;This script will restore your dotfiles and system configuration from a Git repository, and install your Homebrew packages and applications from a Brewfile. It supports both macOS and Linux. To run the script:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;restore.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# To make the script executable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./restore.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Run this script from your home directory on a new machine to restore your complete configuration.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#!/bin/bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;check_os&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Darwin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ] &amp;#x26;&amp;#x26; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Linux&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;This script is only for macOS and Linux&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;exit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_homebrew&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🍺 Installing Homebrew&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;/bin/bash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-fsSL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/.zprofile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;/opt/homebrew/bin/brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shellenv&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_rosetta&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;📄 Installing Rosetta 2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;softwareupdate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install-rosetta&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agree-to-license&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$title&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sync_homebrew&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🍻 Sync Homebrew packages&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cleanup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bundle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upgrade&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;stop_gpg_agent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🔒 Stopping GPG agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--kill&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-connect-agent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpg-connect-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;killagent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/bye&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GPG tools not found, skipping GPG agent stop&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;start_gpg_agent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;echo_title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;🔑 Starting GPG agent&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--daemon&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--reload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GPG tools not found, skipping GPG agent start&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;check_os&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;install_homebrew&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Darwin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;install_rosetta&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;stop_gpg_agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sync_homebrew&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mackup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;start_gpg_agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-using-mackup&quot;&gt;Benefits of Using MackUp&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Easy to install and use&lt;/li&gt;
&lt;li&gt;Supports a variety of storage providers&lt;/li&gt;
&lt;li&gt;Supports symlinking dotfiles and system configuration&lt;/li&gt;
&lt;li&gt;Supports restoring dotfiles and system configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;git-ssh-and-gpg-keys&quot;&gt;Git, SSH, and GPG Keys&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Git is the de facto standard for version control, and it is essential for any developer. Setting up Git with SSH and GPG keys is important for secure and authenticated commits.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;installing-git&quot;&gt;Installing Git&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Git can be installed using Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;configuring-git&quot;&gt;Configuring Git&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;After installing Git, you can configure your user settings:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Your Name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.email&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;your-email@example.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;core.editor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nano&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;I also recommend enabling some useful Git settings:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Enable auto-pruning on fetch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch.prune&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Enable auto-stash on rebase&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git.autoStash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Enable colored diff output with moved lines detection&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff.colorMoved&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;zebra&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;setting-up-ssh-keys&quot;&gt;Setting Up SSH Keys&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;SSH keys are used to authenticate with remote Git repositories. To generate a new SSH key:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Generate an Ed25519 key (recommended)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-keygen&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ed25519&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;your-email@example.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start the SSH agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;ssh-agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Add your key to the agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ssh-add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.ssh/id_ed25519&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;To use macOS Keychain for SSH key management, create or edit &lt;code dir=&quot;auto&quot;&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Host *&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UseKeychain yes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This configuration ensures that your SSH passphrase is stored securely in the macOS Keychain, so you don’t have to enter it repeatedly.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;setting-up-gpg-keys-for-signed-commits&quot;&gt;Setting Up GPG Keys for Signed Commits&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;GPG signing adds an extra layer of trust to your commits. It verifies that commits are actually made by you.&lt;/p&gt;
&lt;p&gt;First, install GPG and Pinentry for macOS:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gnupg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pinentry-mac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Generate a new GPG key:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--full-generate-key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Configure GPG to use Pinentry for passphrase entry. Create or edit &lt;code dir=&quot;auto&quot;&gt;~/.gnupg/gpg-agent.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;default-cache-ttl 600&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;max-cache-ttl 7200&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pinentry-program /opt/homebrew/bin/pinentry-mac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Configure Git to use your GPG key:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Get your key ID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--list-secret-keys&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--keyid-format=long&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Configure Git to use your key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.signingKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;YOUR_KEY_ID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit.gpgsign&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg.program&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/opt/homebrew/bin/gpg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-ssh-and-gpg&quot;&gt;Benefits of SSH and GPG&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SSH Keys&lt;/strong&gt;: Secure, passwordless authentication to Git remotes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPG Signing&lt;/strong&gt;: Cryptographic proof that commits are made by you&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keychain Integration&lt;/strong&gt;: Secure storage of passphrases on macOS&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;passwords-and-secrets&quot;&gt;Passwords and Secrets&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Managing passwords and secrets securely is critical for any developer. I use a combination of tools to handle this.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;apple-notes-with-locked-notes&quot;&gt;Apple Notes with Locked Notes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For storing sensitive values, tokens, and keys, I use Apple’s built-in Notes app with locked notes. This approach is surprisingly effective and has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Built-in to macOS&lt;/strong&gt;: No additional software to install&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iCloud Sync&lt;/strong&gt;: Seamlessly syncs across all Apple devices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password or Touch ID Protection&lt;/strong&gt;: Notes can be locked and unlocked with your device password or biometrics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-End Encryption&lt;/strong&gt;: Locked notes are encrypted with your device passcode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To lock a note in Apple Notes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create or open a note&lt;/li&gt;
&lt;li&gt;Click the lock icon in the toolbar (or right-click and select “Lock Note”)&lt;/li&gt;
&lt;li&gt;Set a password if prompted (or use your device password)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This simple approach keeps my API tokens, SSH passphrases, and other sensitive values readily accessible but secure.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;sops-for-secret-management-in-code&quot;&gt;SOPS for Secret Management in Code&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For managing secrets in code and configuration files, I use &lt;a href=&quot;https://github.com/getsops/sops&quot;&gt;SOPS (Secrets OPerationS)&lt;/a&gt;. SOPS encrypts secrets in YAML, JSON, and other formats while keeping the structure readable.&lt;/p&gt;
&lt;p&gt;Install SOPS with Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sops&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;SOPS integrates well with GitOps workflows, allowing you to store encrypted secrets in Git repositories safely.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mkcert-for-local-development-certificates&quot;&gt;mkcert for Local Development Certificates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For local HTTPS development, &lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt; is invaluable. It creates locally-trusted development certificates.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mkcert&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Install the local CA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkcert&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Create certificates for localhost&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkcert&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;localhost&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;::1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;configuring-the-terminal&quot;&gt;Configuring the Terminal&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A well-configured terminal can significantly boost your productivity. Here’s how I set up mine.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;using-zsh&quot;&gt;Using Zsh&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;macOS comes with Zsh as the default shell. My &lt;code dir=&quot;auto&quot;&gt;.zshrc&lt;/code&gt; is kept simple and focused:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Source shell integrations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/opt/homebrew/opt/chruby/share/chruby/chruby.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/opt/homebrew/opt/chruby/share/chruby/auto.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chruby&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ruby-3.4.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# VS Code shell integration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$TERM_PROGRAM&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;vscode&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]] &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;code-insiders&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--locate-shell-integration-path&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;zsh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Initialize Starship prompt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;starship&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;zsh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;starship-prompt&quot;&gt;Starship Prompt&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://starship.rs&quot;&gt;Starship&lt;/a&gt; is a minimal, fast, and customizable prompt for any shell. It shows relevant information based on your current directory context (Git status, programming language versions, etc.).&lt;/p&gt;
&lt;p&gt;Install Starship:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;starship&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add to your &lt;code dir=&quot;auto&quot;&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;starship&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;zsh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Starship automatically detects your project type and shows relevant information without any additional configuration.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;chruby-for-ruby-version-management&quot;&gt;chruby for Ruby Version Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For Ruby version management, I use &lt;a href=&quot;https://github.com/postmodern/chruby&quot;&gt;chruby&lt;/a&gt; which is lightweight and simple:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chruby&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ruby-install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Install a Ruby version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ruby-install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ruby&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3.4.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-this-terminal-setup&quot;&gt;Benefits of This Terminal Setup&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Minimal Configuration&lt;/strong&gt;: Keep things simple and maintainable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast Startup&lt;/strong&gt;: No heavy frameworks slowing down terminal launch&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context-Aware Prompts&lt;/strong&gt;: Starship shows relevant info automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy Version Management&lt;/strong&gt;: chruby for Ruby, similar tools for other languages&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;setting-up-your-ide&quot;&gt;Setting Up Your IDE&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Visual Studio Code (or VS Code Insiders in my case) is my editor of choice. Here’s how I configure it for maximum productivity.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;installing-vs-code&quot;&gt;Installing VS Code&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visual-studio-code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Or for the Insiders build:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visual-studio-code@insiders&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;essential-settings&quot;&gt;Essential Settings&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here are some of the key settings from my configuration:&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;font-configuration&quot;&gt;Font Configuration&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;I use the &lt;a href=&quot;https://monaspace.githubnext.com&quot;&gt;Monaspace&lt;/a&gt; font family with font ligatures enabled:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.fontFamily&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&apos;Monaspace Krypton&apos;, monospace&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.fontLigatures&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&apos;ss01&apos;, &apos;ss02&apos;, &apos;ss03&apos;, &apos;ss04&apos;, &apos;ss05&apos;, &apos;ss06&apos;, &apos;ss07&apos;, &apos;ss08&apos;, &apos;calt&apos;, &apos;dlig&apos;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.fontSize&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;13&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;terminal.integrated.fontFamily&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&apos;Monaspace Krypton&apos;, monospace&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Install the font with Homebrew:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-monaspace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h4 id=&quot;editor-behavior&quot;&gt;Editor Behavior&lt;/h4&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.formatOnSave&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.formatOnSaveMode&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;modificationsIfAvailable&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.tabSize&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.rulers&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.minimap.enabled&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.guides.bracketPairs&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.smoothScrolling&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.cursorBlinking&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;smooth&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;editor.cursorSmoothCaretAnimation&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h4 id=&quot;git-integration&quot;&gt;Git Integration&lt;/h4&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.enableCommitSigning&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.alwaysSignOff&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.autofetch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.pruneOnFetch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.rebaseWhenSync&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;git.branchProtection&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;master&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;trunk&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h4 id=&quot;file-explorer&quot;&gt;File Explorer&lt;/h4&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;explorer.fileNesting.enabled&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;explorer.sortOrder&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;explorer.confirmDelete&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;files.autoSave&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;afterDelay&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;essential-extensions&quot;&gt;Essential Extensions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here are some of the extensions I use, categorized by purpose:&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;ai-and-productivity&quot;&gt;AI and Productivity&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Copilot&lt;/li&gt;
&lt;li&gt;GitHub Copilot Chat&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;languages-and-frameworks&quot;&gt;Languages and Frameworks&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Go (golang.go)&lt;/li&gt;
&lt;li&gt;C# Dev Kit (ms-dotnettools.csdevkit)&lt;/li&gt;
&lt;li&gt;Docker (ms-azuretools.vscode-docker)&lt;/li&gt;
&lt;li&gt;Kubernetes Tools (ms-kubernetes-tools.vscode-kubernetes-tools)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;git-and-github&quot;&gt;Git and GitHub&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Pull Requests and Issues&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;code-quality&quot;&gt;Code Quality&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Prettier&lt;/li&gt;
&lt;li&gt;EditorConfig&lt;/li&gt;
&lt;li&gt;Markdownlint&lt;/li&gt;
&lt;li&gt;ShellCheck&lt;/li&gt;
&lt;li&gt;Code Spell Checker&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;markdown-and-documentation&quot;&gt;Markdown and Documentation&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Markdown All in One&lt;/li&gt;
&lt;li&gt;Markdown Preview GitHub Styles&lt;/li&gt;
&lt;li&gt;Mermaid diagrams for Markdown&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;remote-development&quot;&gt;Remote Development&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Remote - SSH&lt;/li&gt;
&lt;li&gt;Remote Repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h4 id=&quot;theme-and-icons&quot;&gt;Theme and Icons&lt;/h4&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Vira Theme (my preferred dark theme)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;editorconfig-for-consistent-formatting&quot;&gt;EditorConfig for Consistent Formatting&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I use EditorConfig to maintain consistent coding styles across different editors and IDEs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.editorconfig&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;root = true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[*]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;indent_style = space&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;indent_size = 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;end_of_line = lf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;charset = utf-8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;trim_trailing_whitespace = true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;insert_final_newline = true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-this-ide-setup&quot;&gt;Benefits of This IDE Setup&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistent Formatting&lt;/strong&gt;: EditorConfig and format-on-save ensure consistent code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrated Git&lt;/strong&gt;: Signed commits and branch protection built-in&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI Assistance&lt;/strong&gt;: GitHub Copilot for intelligent code suggestions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language Support&lt;/strong&gt;: First-class support for Go, C#, TypeScript, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;remote-development-1&quot;&gt;Remote Development&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Modern development often involves working with remote machines, containers, and cloud environments. Here’s how I handle remote development.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;vs-code-remote-ssh&quot;&gt;VS Code Remote SSH&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Remote SSH extension allows you to develop on remote machines as if they were local:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# In VS Code, press Cmd+Shift+P and type:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# &quot;Remote-SSH: Connect to Host...&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;My SSH config (&lt;code dir=&quot;auto&quot;&gt;~/.ssh/config&lt;/code&gt;) is kept simple with Keychain integration:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Host *&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UseKeychain yes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;docker-desktop&quot;&gt;Docker Desktop&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Docker is essential for containerized development. Install it with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docker-desktop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Docker provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Local Containers&lt;/strong&gt;: Run applications in isolated environments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;: Built-in single-node Kubernetes cluster for testing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VS Code Integration&lt;/strong&gt;: Seamless integration with the Docker extension&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;kubernetes-tools&quot;&gt;Kubernetes Tools&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For Kubernetes development, I use several tools:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Kubernetes CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kubernetes-cli&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Helm for package management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;helm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Flux for GitOps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fluxcd/tap/flux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# KSail for local cluster management (my own project!)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devantler-tech/tap/ksail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;cloud-clis&quot;&gt;Cloud CLIs&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For cloud development, I have the major cloud provider CLIs installed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# AWS CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;awscli&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Azure CLI (via kubelogin for AKS)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kubelogin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Hetzner Cloud CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hcloud&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;benefits-of-remote-development&quot;&gt;Benefits of Remote Development&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistent Environments&lt;/strong&gt;: Docker ensures the same environment everywhere&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable Resources&lt;/strong&gt;: Develop on powerful remote machines when needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitOps Workflows&lt;/strong&gt;: Flux and other tools enable declarative infrastructure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud-Native Development&lt;/strong&gt;: First-class support for Kubernetes and cloud services&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Setting up macOS as a developer machine requires thoughtful configuration, but the investment pays off in productivity and consistency. The key takeaways from my setup are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use Homebrew&lt;/strong&gt; for package management — it’s reliable and comprehensive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage dotfiles with MackUp&lt;/strong&gt; — keeps configurations in sync across machines&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure your identity&lt;/strong&gt; with SSH and GPG keys for authenticated commits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invest in your terminal&lt;/strong&gt; — a good prompt and shell configuration boosts productivity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure your IDE thoroughly&lt;/strong&gt; — VS Code with the right extensions is incredibly powerful&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace remote development&lt;/strong&gt; — Docker, Kubernetes, and cloud tools are essential for modern development&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I hope this guide helps you set up your own macOS developer machine. Feel free to adapt these configurations to your own needs and preferences.&lt;/p&gt;</content:encoded><category>macos</category><category>developer-experience</category><category>tooling</category></item><item><title>Welcome!</title><link>https://devantler.tech/blog/welcome/</link><guid isPermaLink="true">https://devantler.tech/blog/welcome/</guid><description>A welcome post to introduce myself and the site.

</description><pubDate>Wed, 24 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello everyone! Welcome to my site 👋🏻&lt;/p&gt;
&lt;p&gt;I am Nikolai Emil Damm AKA Devantler. I am a software engineer with a strong passion for open-source, and I believe that working transparently and collaboratively is the best way to create high-quality software. I have been working in the software industry for many years, and I have experience in a wide range of technologies and domains.&lt;/p&gt;
&lt;p&gt;I created this site to share my learnings and experiences with the broader community. I will be writing about software development, open-source, cloud computing, and other topics that I find interesting. I hope that you find my posts useful and informative, and I welcome any feedback or suggestions that you may have.&lt;/p&gt;
&lt;p&gt;Peace ✌🏻&lt;/p&gt;</content:encoded><category>personal</category></item></channel></rss>