<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Habib Masri</title>
    <description>The latest articles on Forem by Habib Masri (@habib_masri).</description>
    <link>https://forem.com/habib_masri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810193%2F614bd25d-af53-42fe-850a-c5003df967e6.jpg</url>
      <title>Forem: Habib Masri</title>
      <link>https://forem.com/habib_masri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/habib_masri"/>
    <language>en</language>
    <item>
      <title>Kiro CLI + ArgoCD MCP: Manage GitOps from Your Terminal</title>
      <dc:creator>Habib Masri</dc:creator>
      <pubDate>Thu, 09 Apr 2026 15:17:27 +0000</pubDate>
      <link>https://forem.com/habib_masri/kiro-cli-argocd-mcp-manage-gitops-from-your-terminal-10k3</link>
      <guid>https://forem.com/habib_masri/kiro-cli-argocd-mcp-manage-gitops-from-your-terminal-10k3</guid>
      <description>&lt;p&gt;Managing &lt;a href="https://argo-cd.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;ArgoCD&lt;/a&gt; applications typically means writing Application YAML, configuring sync policies, and switching between the CLI and UI. It works, but there's a better way to do it.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro CLI&lt;/a&gt; and the &lt;a href="https://github.com/argoproj-labs/mcp-for-argocd" rel="noopener noreferrer"&gt;ArgoCD MCP server&lt;/a&gt;, you can do all of that using natural language from your terminal — create apps, sync them, check health, view resource trees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Kubernetes Before GitOps
&lt;/h2&gt;

&lt;p&gt;Before ArgoCD, deploying to Kubernetes was mostly manual. You'd write your manifests, run &lt;code&gt;kubectl apply&lt;/code&gt;, and hope it works. A typical workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write Deployment, Service, ConfigMap YAML by hand&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl apply -f&lt;/code&gt; from your laptop&lt;/li&gt;
&lt;li&gt;Verify the resources are running in the right namespace&lt;/li&gt;
&lt;li&gt;Commit the manifests to Git (if you remember)&lt;/li&gt;
&lt;li&gt;Repeat across environments, hoping cluster state and repo stay in sync&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some teams graduated to Helm charts, which helped with templating but didn't solve the drift problem. Others wrote custom CI/CD pipelines that ran &lt;code&gt;kubectl apply&lt;/code&gt; on merge — better, but still push-based and fragile.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;GitOps&lt;/strong&gt; came along and flipped the model. Instead of pushing changes to the cluster, you declare the desired state in Git and let a controller pull it in. ArgoCD is the most popular implementation of this pattern — it watches your Git repo, compares it to what's running in the cluster, and reconciles the difference automatically.&lt;/p&gt;

&lt;p&gt;The cluster state problem is solved. The day-to-day management — writing Application manifests, configuring sync policies, navigating the UI — is where an agentic approach can help.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;By the end of this article, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kiro CLI connected to your ArgoCD instance via the ArgoCD MCP server&lt;/li&gt;
&lt;li&gt;The ability to create, sync, and monitor ArgoCD applications using natural language directly from your terminal&lt;/li&gt;
&lt;li&gt;A workflow for generating Application manifests with automated sync and health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;An EKS cluster (or any Kubernetes cluster) with ArgoCD installed&lt;/li&gt;
&lt;li&gt;An ArgoCD API token (&lt;a href="https://argo-cd.readthedocs.io/en/stable/developer-guide/api-docs/#authorization" rel="noopener noreferrer"&gt;docs for creating one&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kiro.dev/cli/" rel="noopener noreferrer"&gt;Kiro CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;Node.js v18+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't have ArgoCD running yet, the quickest path on EKS is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then port-forward to access it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/argocd-server &lt;span class="nt"&gt;-n&lt;/span&gt; argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This forwards local port 8080 to ArgoCD's HTTPS port (443), so you'll access it at &lt;code&gt;https://localhost:8080&lt;/code&gt; — not &lt;code&gt;http&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Getting Your ArgoCD API Token
&lt;/h3&gt;

&lt;p&gt;You need an API token for the MCP server to authenticate with ArgoCD. From the ArgoCD UI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Accounts → admin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate New&lt;/strong&gt; under Tokens&lt;/li&gt;
&lt;li&gt;Copy the token — you'll need it in the next step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or via CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;argocd account generate-token &lt;span class="nt"&gt;--account&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting Kiro CLI to ArgoCD
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/argoproj-labs/mcp-for-argocd" rel="noopener noreferrer"&gt;ArgoCD MCP server&lt;/a&gt; is what makes this work. &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; lets Kiro communicate with external services through standardized tool interfaces. The ArgoCD MCP server exposes ArgoCD's API as tools that Kiro can call — list apps, create apps, sync, get resource trees, fetch logs, and more.&lt;/p&gt;

&lt;p&gt;I prefer to wire the MCP server into a &lt;a href="https://kiro.dev/docs/cli/custom-agents/examples/" rel="noopener noreferrer"&gt;Kiro Custom Agent&lt;/a&gt; rather than a global &lt;code&gt;mcp.json&lt;/code&gt;. This way the agent has a focused prompt, scoped tools, and the ArgoCD connection is self-contained — easy to share with the team or drop into any project.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.kiro/agents/argocd-agent.json&lt;/code&gt; in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"argocd-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Manages ArgoCD applications via natural language — create, sync, monitor, and troubleshoot GitOps deployments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a GitOps specialist. You manage ArgoCD applications on Kubernetes clusters. Use the ArgoCD MCP tools to list, create, update, sync, and monitor applications. Always confirm destructive actions before executing them."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"argocd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"argocd-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ARGOCD_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ARGOCD_API_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-argocd-api-token&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"@argocd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"@argocd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If your ArgoCD instance uses self-signed certificates (common with port-forwarding), add &lt;code&gt;"NODE_TLS_REJECT_UNAUTHORIZED": "0"&lt;/code&gt; to the &lt;code&gt;env&lt;/code&gt; block inside &lt;code&gt;mcpServers.argocd.env&lt;/code&gt;. Only do this in development.&lt;/p&gt;

&lt;p&gt;For safer use in production, add &lt;code&gt;"MCP_READ_ONLY": "true"&lt;/code&gt; to the &lt;code&gt;env&lt;/code&gt; block — this disables create, update, delete, and sync operations. See &lt;a href="https://github.com/argoproj-labs/mcp-for-argocd#read-only-mode" rel="noopener noreferrer"&gt;Read Only Mode&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start Kiro CLI with the ArgoCD agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
kiro-cli &lt;span class="nt"&gt;--agent&lt;/span&gt; argocd-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify the MCP server loaded by running &lt;code&gt;/mcp&lt;/code&gt; in the chat session — you should see &lt;code&gt;argocd&lt;/code&gt; listed with its available tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a Custom Agent? Subagents and Context Management
&lt;/h3&gt;

&lt;p&gt;There's another reason I use a custom agent instead of configuring all MCPs and tools in the default agent: &lt;a href="https://kiro.dev/docs/cli/chat/subagents/" rel="noopener noreferrer"&gt;subagents&lt;/a&gt;. In Kiro CLI, a parent agent can spawn custom agents as subagents — each running in its own context with its own tools. The subagent does its work in its own isolated context and returns only the result to the parent agent.&lt;/p&gt;

&lt;p&gt;This matters for context window efficiency. Imagine a &lt;code&gt;platform-engineer&lt;/code&gt; parent agent that handles broad infrastructure tasks. When it needs to interact with ArgoCD, it spawns &lt;code&gt;argocd-agent&lt;/code&gt; as a subagent. The ArgoCD MCP tools, API responses, and resource trees only live in the subagent's context — they never bloat the parent's context window. Once the subagent returns the result, that context is freed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: Check if all apps are healthy, then review the Terraform plan for the new VPC

platform-engineer agent:
  → spawns argocd-agent: "List all applications and check their health"
    ← returns: "3 apps, all Healthy and Synced"
  → reviews Terraform plan with its own tools (clean context, no ArgoCD noise)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can even pre-trust the ArgoCD agent so it runs without permission prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform-engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Parent agent for infrastructure and platform tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subagent"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"toolsSettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subagent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"availableAgents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"argocd-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform-agent"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"trustedAgents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"argocd-agent"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the real payoff of defining ArgoCD as a custom agent — it becomes a composable, context-efficient building block in a larger workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an ArgoCD Application with Natural Language
&lt;/h2&gt;

&lt;p&gt;Instead of writing an Application manifest by hand, just tell Kiro what you want:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Create a new ArgoCD application called guestbook that deploys from the repo &lt;a href="https://github.com/argoproj/argocd-example-apps" rel="noopener noreferrer"&gt;https://github.com/argoproj/argocd-example-apps&lt;/a&gt;, path guestbook, targeting the default namespace on the in-cluster destination&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kiro uses the &lt;code&gt;create_application&lt;/code&gt; &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/server/tools" rel="noopener noreferrer"&gt;Tool&lt;/a&gt; from the ArgoCD MCP server. Kiro handles the API call and creates the application directly in ArgoCD.&lt;/p&gt;

&lt;p&gt;The equivalent YAML that ArgoCD creates under the hood looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guestbook&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/argoproj/argocd-example-apps&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HEAD&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guestbook&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what each field does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;project&lt;/strong&gt;: The ArgoCD project this app belongs to. &lt;code&gt;default&lt;/code&gt; works for most cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;source.repoURL&lt;/strong&gt;: The Git repository containing your manifests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;source.path&lt;/strong&gt;: The directory within the repo where the Kubernetes manifests live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;source.targetRevision&lt;/strong&gt;: Which branch/tag/commit to track. &lt;code&gt;HEAD&lt;/code&gt; follows the default branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;destination.server&lt;/strong&gt;: The cluster to deploy to. &lt;code&gt;https://kubernetes.default.svc&lt;/code&gt; means "this cluster."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;destination.namespace&lt;/strong&gt;: Where the resources get created.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding Automated Sync
&lt;/h2&gt;

&lt;p&gt;By default, ArgoCD applications are created without auto-sync — you have to manually trigger syncs. That's fine for production, but for dev/staging environments, you usually want changes to deploy automatically when you push to Git.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Update the guestbook application to enable automated sync with self-healing and auto-pruning&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kiro calls &lt;code&gt;update_application&lt;/code&gt; and configures the sync policy. Here's what those settings mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated sync&lt;/strong&gt;: ArgoCD automatically syncs when it detects the Git repo has changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing&lt;/strong&gt;: If someone manually changes a resource in the cluster (kubectl edit, etc.), ArgoCD reverts it back to match Git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-prune&lt;/strong&gt;: If you remove a resource from Git, ArgoCD deletes it from the cluster too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The equivalent sync policy in YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Checking Application Health
&lt;/h2&gt;

&lt;p&gt;Once the app is synced, you want to know if it's actually healthy — are the pods running? Did the deployment roll out successfully?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Check the health of the guestbook application&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kiro calls &lt;code&gt;get_application&lt;/code&gt; and returns the sync status, health status, and any conditions. You'll see something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync Status&lt;/strong&gt;: Synced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Status&lt;/strong&gt;: Healthy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt;: Deployment (Healthy), Service (Healthy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something is wrong, you can dig deeper:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Show me the resource tree for the guestbook application&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This calls &lt;code&gt;get_application_resource_tree&lt;/code&gt; and shows the full hierarchy — the Application owns a Deployment, which owns a ReplicaSet, which owns Pods. If a pod is in CrashLoopBackOff, you'll see it here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Get the logs for the failing pod in the guestbook application&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kiro calls &lt;code&gt;get_application_workload_logs&lt;/code&gt; and pulls the logs directly, so you don't need to &lt;code&gt;kubectl logs&lt;/code&gt; into the right namespace and pod name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Syncing and Monitoring
&lt;/h2&gt;

&lt;p&gt;When you need to manually trigger a sync (maybe you have auto-sync disabled in production):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sync the guestbook application&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Show me which applications are out of sync&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;List all ArgoCD applications in my cluster&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each of these maps to a specific MCP tool (&lt;code&gt;sync_application&lt;/code&gt;, &lt;code&gt;list_applications&lt;/code&gt;, etc.) and Kiro handles the API calls behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Workflow
&lt;/h2&gt;

&lt;p&gt;Here's what a typical session looks like, end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: List all ArgoCD applications
Kiro: [calls list_applications] You have 3 applications: 
      frontend (Synced/Healthy), backend (OutOfSync/Healthy), 
      monitoring (Synced/Degraded)

You: Sync the backend application
Kiro: [calls sync_application] Sync initiated for backend. 
      Revision: abc123f

You: Check the health of the monitoring application
Kiro: [calls get_application] monitoring is Degraded. 
      The prometheus-server Deployment has 0/1 ready replicas.

You: Show me the logs for the prometheus-server in monitoring
Kiro: [calls get_application_workload_logs] 
      Error: no persistent volume claim found...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All from one terminal session, instead of remembering commands like &lt;code&gt;argocd app get monitoring -o yaml | grep health&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://kiro.dev/cli/" rel="noopener noreferrer"&gt;Kiro CLI&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;macOS/Linux: &lt;code&gt;curl -fsSL https://cli.kiro.dev/install | bash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows (PowerShell): &lt;code&gt;irm 'https://cli.kiro.dev/install.ps1' | iex&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set up the ArgoCD MCP server config as shown above&lt;/li&gt;
&lt;li&gt;Start a chat session with &lt;code&gt;kiro-cli&lt;/code&gt; and try: &lt;em&gt;List all ArgoCD applications in my cluster&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you've been managing ArgoCD through the UI or memorizing CLI flags, try it on a staging cluster and see how it feels.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>kiro</category>
    </item>
    <item>
      <title>Generating GitHub Actions Workflows with Kiro</title>
      <dc:creator>Habib Masri</dc:creator>
      <pubDate>Tue, 10 Mar 2026 10:23:03 +0000</pubDate>
      <link>https://forem.com/habib_masri/generating-github-actions-workflows-with-kiro-4o03</link>
      <guid>https://forem.com/habib_masri/generating-github-actions-workflows-with-kiro-4o03</guid>
      <description>&lt;p&gt;I had a Python Lambda project sitting in a repo with zero CI/CD. No pipeline, no automation, just &lt;code&gt;sam deploy&lt;/code&gt; from my laptop like it's 2019.&lt;/p&gt;

&lt;p&gt;So I used &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; to generate a complete GitHub Actions pipeline — build, test, deploy — and watched it go green on the first push. Okay fine, the third push. But who's&lt;br&gt;
counting.&lt;/p&gt;

&lt;p&gt;Here's exactly how I did it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The project is simple on purpose. A Python Lambda function behind API Gateway, deployed with AWS SAM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── src/
│   └── app.py              # Lambda handler
├── tests/
│   └── test_app.py         # pytest unit tests
├── template.yaml            # SAM template
├── samconfig.toml           # SAM deploy config
└── .kiro/skills/
    └── github-actions-cicd/ # Kiro skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5rlt8sq6kzzkqv8xqio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5rlt8sq6kzzkqv8xqio.png" alt="Architecture diagram showing the CI/CD pipeline flow" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Lambda handler uses &lt;a href="https://docs.powertools.aws.dev/lambda/python/latest/" rel="noopener noreferrer"&gt;Powertools for AWS Lambda (Python)&lt;/a&gt; — a developer toolkit that gives you structured JSON logging, tracing, and metrics out of the box. I'm only using the Logger here, which gives me structured logs with correlation IDs and cold start tracking with a single decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_powertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@logger.inject_lambda_context&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Root endpoint called&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... route handling
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three pytest tests cover the routes. Minimal, but the focus here is the pipeline, not the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching Kiro How I Want My Pipelines Built
&lt;/h2&gt;

&lt;p&gt;Before generating the workflow, I asked Kiro to create a &lt;strong&gt;Skill&lt;/strong&gt; — a reusable instruction package that tells Kiro &lt;em&gt;how&lt;/em&gt; to do something, not just &lt;em&gt;what&lt;/em&gt; to do. Skills follow the open &lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; standard, so they're shareable across tools and teams.&lt;/p&gt;

&lt;p&gt;Kiro uses progressive disclosure with skills — it loads only the name and description at startup, activates the full instructions when your request matches, and pulls in reference files only when needed. No wasted tokens in the context window.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a Kiro skill for generating GitHub Actions CI/CD workflows for Python Lambda projects using SAM. It should use three jobs: test, build, deploy. Use OIDC for AWS auth, pip caching, and GitHub Environments."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kiro generated &lt;code&gt;.kiro/skills/github-actions-cicd/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-actions-cicd&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate GitHub Actions CI/CD workflows for Python Lambda&lt;/span&gt; 
  &lt;span class="s"&gt;projects using SAM. Use when creating pipelines, adding CI/CD,&lt;/span&gt; 
  &lt;span class="s"&gt;generating workflow YAML, or deploying Lambda functions with&lt;/span&gt; 
  &lt;span class="s"&gt;GitHub Actions.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Workflow Structure&lt;/span&gt;

Generate GitHub Actions workflows with three jobs:
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**build**&lt;/span&gt; — Run &lt;span class="sb"&gt;`sam build`&lt;/span&gt; to compile the SAM application
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**test**&lt;/span&gt; — Install dependencies and run &lt;span class="sb"&gt;`pytest`&lt;/span&gt;
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**deploy**&lt;/span&gt; — Run &lt;span class="sb"&gt;`sam deploy`&lt;/span&gt; to deploy to AWS (only on push to &lt;span class="sb"&gt;`main`&lt;/span&gt;)

&lt;span class="gu"&gt;## AWS Authentication&lt;/span&gt;

Always use OIDC for AWS credentials via 
&lt;span class="sb"&gt;`aws-actions/configure-aws-credentials`&lt;/span&gt;. Never use static access keys.

&lt;span class="gu"&gt;## Caching&lt;/span&gt;

Use &lt;span class="sb"&gt;`actions/cache`&lt;/span&gt; for pip dependencies.

&lt;span class="gu"&gt;## Environment Strategy&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`production`&lt;/span&gt; environment: deploys on push to &lt;span class="sb"&gt;`main`&lt;/span&gt; branch
&lt;span class="p"&gt;-&lt;/span&gt; Use GitHub Environments for secrets and protection rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added reference files under &lt;code&gt;references/&lt;/code&gt; with specific patterns for SAM deploys, matrix builds, and security hardening. Kiro loads these only when the instructions point to them — keeping things lean.&lt;/p&gt;

&lt;p&gt;The skill is committed to the repo, so anyone who clones it gets the same behavior. That's the power of skills — they're portable and version-controlled.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full docs on skills: &lt;a href="https://kiro.dev/docs/cli/skills/" rel="noopener noreferrer"&gt;kiro.dev/docs/cli/skills&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Generated Workflow
&lt;/h2&gt;

&lt;p&gt;With the skill in place, I asked Kiro to generate a GitHub Actions workflow. Here are some of the prompts I used:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Generate a GitHub Actions CI/CD workflow for this project"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gave me the base pipeline. Then I iterated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add pip caching to speed up the test job"&lt;/p&gt;

&lt;p&gt;"Add a matrix build to test on Python 3.11 and 3.12"&lt;/p&gt;

&lt;p&gt;"Split the deploy into its own job that only runs on push to main, using OIDC for AWS auth"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The skill activated automatically each time — no special command needed — and Kiro followed the conventions I defined. Here's the structure of the final workflow (full YAML in the &lt;a href="https://github.com/habibmasri/kiro-cli-github-actions/blob/main/.github/workflows/pipeline.yml" rel="noopener noreferrer"&gt;repo&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# checkout, setup python, pip cache, install deps, pytest&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# checkout, setup SAM, sam build, upload artifact&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name == 'push'&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# checkout, download artifact, setup SAM, configure AWS creds (OIDC), sam deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break down what's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking Through the Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Job 1: Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs on every push and PR. The &lt;strong&gt;matrix build&lt;/strong&gt; tests against Python 3.11 and 3.12 in parallel — so you catch version-specific issues early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pip caching&lt;/strong&gt; uses &lt;code&gt;actions/cache&lt;/code&gt; keyed on the OS, Python version, and a hash of &lt;code&gt;requirements.txt&lt;/code&gt;. Second runs are noticeably faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Job 2: Build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only runs after tests pass. Runs &lt;code&gt;sam build&lt;/code&gt; and uploads the &lt;code&gt;.aws-sam/&lt;/code&gt; output as a GitHub artifact. The deploy job downloads this artifact instead of rebuilding — keeping the deploy fast and ensuring you deploy exactly what you built.&lt;/p&gt;

&lt;h3&gt;
  
  
  Job 3: Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main' &amp;amp;&amp;amp; github.event_name == 'push'&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where it gets interesting. The deploy job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only runs on push to &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; — PRs trigger test + build but never deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uses a GitHub Environment&lt;/strong&gt; (&lt;code&gt;production&lt;/code&gt;) — you can add protection rules like required reviewers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticates via OIDC&lt;/strong&gt; — no static AWS keys anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up OIDC Authentication (No Static Keys)
&lt;/h2&gt;

&lt;p&gt;This is the part most people skip or get wrong. Instead of storing &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; as GitHub secrets (please don't), we use &lt;strong&gt;OpenID Connect&lt;/strong&gt; to get short-lived credentials.&lt;/p&gt;

&lt;p&gt;Here's the setup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create the OIDC identity provider in IAM:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam create-open-id-connect-provider &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://token.actions.githubusercontent.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--client-id-list&lt;/span&gt; sts.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--thumbprint-list&lt;/span&gt; 6938fd4d98bab03faadb97b34396831e3780aea1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create an IAM role with a trust policy scoped to your repo + environment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Federated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:oidc-provider/token.actions.githubusercontent.com"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repo:&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;:environment:production"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sub&lt;/code&gt; condition is critical — it locks the role down to your repo's &lt;code&gt;production&lt;/code&gt; environment. Nothing else can assume it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add the secret and variable in GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh secret &lt;span class="nb"&gt;set &lt;/span&gt;AWS_ROLE_TO_ASSUME &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:role/GitHubActions-KiroDemo"&lt;/span&gt;
gh variable &lt;span class="nb"&gt;set &lt;/span&gt;AWS_REGION &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;AWS_ROLE_TO_ASSUME&lt;/code&gt; is a &lt;strong&gt;secret&lt;/strong&gt; (sensitive) while &lt;code&gt;AWS_REGION&lt;/code&gt; is a &lt;strong&gt;variable&lt;/strong&gt; (not sensitive).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full guide: &lt;a href="https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services" rel="noopener noreferrer"&gt;Configuring OpenID Connect in AWS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Push to &lt;code&gt;main&lt;/code&gt;, and the pipeline runs:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;test (3.11)&lt;/strong&gt; — 8s&lt;br&gt;
✅ &lt;strong&gt;test (3.12)&lt;/strong&gt; — 11s&lt;br&gt;
✅ &lt;strong&gt;build&lt;/strong&gt; — 18s&lt;br&gt;
✅ &lt;strong&gt;deploy&lt;/strong&gt; — deploys to AWS via SAM&lt;/p&gt;

&lt;p&gt;Three jobs, zero static credentials, matrix testing, pip caching, artifact passing between jobs. All generated by Kiro with a skill that encodes how I want my pipelines built.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Add Next
&lt;/h2&gt;

&lt;p&gt;The pipeline is intentionally simple, but here's what you could ask Kiro to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-environment deploys&lt;/strong&gt; — staging on &lt;code&gt;develop&lt;/code&gt;, production on &lt;code&gt;main&lt;/code&gt;, each with their own GitHub Environment and IAM role&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linting&lt;/strong&gt; — add &lt;code&gt;ruff&lt;/code&gt; or &lt;code&gt;flake8&lt;/code&gt; as a step in the test job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAM validate&lt;/strong&gt; — run &lt;code&gt;sam validate&lt;/code&gt; in the build job before &lt;code&gt;sam build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifications&lt;/strong&gt; — Slack or Teams notification on deploy success/failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a single prompt to Kiro — and because the skill is in place, it'll follow the same conventions every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full repo is here: &lt;a href="https://github.com/habibmasri/kiro-cli-github-actions" rel="noopener noreferrer"&gt;github.com/habibmasri/kiro-cli-github-actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clone it, install &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro CLI&lt;/a&gt;, and try asking it to modify the pipeline. The skill is included — it'll just work.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>github</category>
      <category>cicd</category>
      <category>kiro</category>
    </item>
  </channel>
</rss>
