<?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: Jeff Vincent</title>
    <description>The latest articles on Forem by Jeff Vincent (@jeffvincent).</description>
    <link>https://forem.com/jeffvincent</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%2F990448%2F272d4199-a4ed-45e8-b768-3b945ead88df.jpg</url>
      <title>Forem: Jeff Vincent</title>
      <link>https://forem.com/jeffvincent</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jeffvincent"/>
    <language>en</language>
    <item>
      <title>From Source to Production with OAuth: The Full Kindling Flow</title>
      <dc:creator>Jeff Vincent</dc:creator>
      <pubDate>Wed, 11 Mar 2026 16:49:05 +0000</pubDate>
      <link>https://forem.com/jeffvincent/from-source-to-production-with-oauth-the-full-kindling-flow-1i73</link>
      <guid>https://forem.com/jeffvincent/from-source-to-production-with-oauth-the-full-kindling-flow-1i73</guid>
      <description>&lt;p&gt;Your laptop is your staging environment. Literally. &lt;/p&gt;

&lt;p&gt;In this post, we'll take a polyglot microservice app from local source code to a production Kubernetes cluster with TLS, Auth0 login, and Stripe webhooks — all working end-to-end. No cloud staging environment. No Docker Compose. No YAML by hand.&lt;/p&gt;

&lt;p&gt;The app is &lt;a href="https://github.com/kindling-sh/oauth-example" rel="noopener noreferrer"&gt;oauth-example&lt;/a&gt;, a four-service demo you can clone and follow along with.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;A microservices app with real external integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → UI (React/nginx)
            ↓
         Gateway (Go) ← Auth0 OIDC login/callback
            ↓              ← Stripe webhook receiver
       ┌────┴────┐
    Orders     Inventory
   (Python)    (Node.js)
   Postgres    MongoDB
      Redis ←──── shared event queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI&lt;/strong&gt; — React dashboard served by nginx&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt; — Go reverse proxy with Auth0 OIDC and Stripe webhook verification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orders&lt;/strong&gt; — Python FastAPI with Postgres, publishes events to Redis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory&lt;/strong&gt; — Node.js Fastify with MongoDB, consumes order events from Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part: Auth0 needs a public HTTPS callback URL, and Stripe needs a public HTTPS webhook endpoint. These are the exact things that make local development painful — and the exact things kindling solves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Init the cluster
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kindlingdev/tap/kindling
kindling init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You now have a Kind cluster with an operator, an in-cluster container registry, and a Traefik ingress controller. All local, all disposable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Register a CI runner
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling runners &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;github-user&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; oauth-test &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;github-pat&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deploys a self-hosted GitHub Actions runner inside your Kind cluster. When you push code, CI runs locally — builds happen with Kaniko, images land in the in-cluster registry at &lt;code&gt;localhost:5001&lt;/code&gt;. No DockerHub. No ECR. No waiting for remote CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Generate the CI workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling generate &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;api-key&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kindling's AI analyzes your repo — finds the four services, detects languages and frameworks, reads the Dockerfiles, discovers the deploy manifests — and generates a GitHub Actions workflow that builds and deploys everything. The generated workflow handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building each service with Kaniko&lt;/li&gt;
&lt;li&gt;Pushing images to the in-cluster registry&lt;/li&gt;
&lt;li&gt;Applying the DSE (DevStagingEnvironment) manifests&lt;/li&gt;
&lt;li&gt;Service-to-service wiring via environment variables&lt;/li&gt;
&lt;li&gt;Dependency provisioning (Postgres, Redis, MongoDB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't write this workflow. You don't maintain it. Push code and it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create Auth0 and Stripe accounts and set credentials
&lt;/h2&gt;

&lt;p&gt;The gateway's deploy manifest references secrets via &lt;code&gt;secretKeyRef&lt;/code&gt; — Kubernetes won't start the pod if they don't exist. You need to create your external service accounts and set the credentials &lt;em&gt;before&lt;/em&gt; the first deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Auth0 application
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up or log in at &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In the Auth0 Dashboard, go to &lt;strong&gt;Applications → Create Application&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name it something like &lt;code&gt;oauth-example-dev&lt;/code&gt;, select &lt;strong&gt;Regular Web Application&lt;/strong&gt;, and click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Settings&lt;/strong&gt; tab, copy these three values — you'll need them in a moment:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt; (e.g. &lt;code&gt;your-tenant.us.auth0.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client Secret&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't configure the callback URLs yet — you'll need a public tunnel URL first, which comes after the deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Stripe webhook endpoint
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up or log in at &lt;a href="https://dashboard.stripe.com" rel="noopener noreferrer"&gt;dashboard.stripe.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Make sure you're in &lt;strong&gt;Test mode&lt;/strong&gt; (toggle in the top-right)&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Webhooks&lt;/strong&gt; tab in &lt;a href="https://dashboard.stripe.com/webhooks" rel="noopener noreferrer"&gt;Workbench&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll create the actual webhook endpoint later once you have a tunnel URL. For now, just copy your &lt;strong&gt;Stripe Secret Key&lt;/strong&gt; from &lt;strong&gt;Developers → API keys&lt;/strong&gt; — it starts with &lt;code&gt;sk_test_&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set the pre-deploy secrets
&lt;/h3&gt;

&lt;p&gt;Set the credentials so the pods can start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_DOMAIN your-tenant.auth0.com
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_CLIENT_ID your-client-id
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_CLIENT_SECRET your-client-secret
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;SESSION_SECRET &lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_SECRET_KEY sk_test_your_stripe_secret_key
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_WEBHOOK_SECRET placeholder
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;PUBLIC_URL http://localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; and &lt;code&gt;PUBLIC_URL&lt;/code&gt; are placeholders — the gateway will start without them working, but it won't crash. You'll set the real values after starting a tunnel.&lt;/p&gt;

&lt;p&gt;Note: &lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; (starts with &lt;code&gt;sk_test_&lt;/code&gt;) is your Stripe &lt;strong&gt;API key&lt;/strong&gt; from &lt;strong&gt;Developers → API keys&lt;/strong&gt;. This is different from &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; (starts with &lt;code&gt;whsec_&lt;/code&gt;), which is the webhook &lt;strong&gt;signing secret&lt;/strong&gt; you'll get after creating a webhook endpoint in Step 6.&lt;/p&gt;

&lt;p&gt;The DSE manifests reference these via &lt;code&gt;secretKeyRef&lt;/code&gt; — the operator injects them as environment variables into the gateway pod. No secrets in YAML. No secrets in env files. No secrets in git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Push and deploy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial deploy"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner picks up the push, builds all four images, and the operator deploys them. Check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▸ Dev Staging Environments
    📦 jeff-vincent-gateway      9090   jeff-vincent-gateway.localhost
    📦 jeff-vincent-inventory    3000   jeff-vincent-inventory.localhost
    📦 jeff-vincent-orders       5000   jeff-vincent-orders.localhost
    📦 jeff-vincent-ui           80     jeff-vincent-ui.localhost

▸ All Deployments
    jeff-vincent-gateway             1/1
    jeff-vincent-inventory           1/1
    jeff-vincent-inventory-mongodb   1/1
    jeff-vincent-orders              1/1
    jeff-vincent-orders-postgres     1/1
    jeff-vincent-orders-redis        1/1
    jeff-vincent-ui                  1/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four services, three databases, all running. Open &lt;code&gt;http://jeff-vincent-ui.localhost&lt;/code&gt; and the dashboard is live. Auth0 and Stripe aren't wired yet — that's next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Start a tunnel and configure OAuth + webhooks
&lt;/h2&gt;

&lt;p&gt;Now that the services are running, you need a public HTTPS URL for Auth0 callbacks and Stripe webhooks. Kindling uses Cloudflare's free quick tunnels. Sign up for a free Cloudflare account; navigate to Protect and Connect &amp;gt; Networking &amp;gt; Tunnels, and create one. Then, start the Cloudflare daemon on your local machine like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cloudflared &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
&lt;span class="nb"&gt;sudo &lt;/span&gt;cloudflared service &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;your-token&amp;gt;

kindling expose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get a public URL like &lt;code&gt;https://verb-noun-adj-noun.trycloudflare.com&lt;/code&gt;. Copy it.&lt;/p&gt;

&lt;p&gt;The tunnel runs in the background. The URL changes each time you restart it, so you'll update the callback URLs in Auth0 and Stripe when you need to test external integrations. Most day-to-day development doesn't need the tunnel at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Auth0 callback URLs
&lt;/h3&gt;

&lt;p&gt;Go back to your Auth0 application settings and set:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Auth0 Dashboard → Applications → oauth-example-dev → Settings → Application URIs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Allowed Callback URLs&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-tunnel-url&amp;gt;/auth/callback
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Allowed Logout URLs&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-tunnel-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Allowed Web Origins&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-tunnel-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Replace &lt;code&gt;&amp;lt;your-tunnel-url&amp;gt;&lt;/code&gt; with the URL from &lt;code&gt;kindling expose&lt;/code&gt; (e.g. &lt;code&gt;verb-noun-adj-noun.trycloudflare.com&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The callback URL is what Auth0 redirects to after a user authenticates. It must match &lt;em&gt;exactly&lt;/em&gt; what the gateway sends in the OIDC authorization request — the path &lt;code&gt;/auth/callback&lt;/code&gt; is handled by the gateway's OIDC callback handler, which exchanges the authorization code for tokens and sets a session cookie.&lt;/p&gt;

&lt;p&gt;The gateway's OIDC integration uses Auth0's &lt;a href="https://auth0.com/docs/authenticate/login/auth0-universal-login" rel="noopener noreferrer"&gt;Universal Login&lt;/a&gt; — no custom login page needed. For more detail, see &lt;a href="https://auth0.com/docs/get-started" rel="noopener noreferrer"&gt;Auth0's Getting Started guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Stripe webhook endpoint
&lt;/h3&gt;

&lt;p&gt;Now create the webhook endpoint in Stripe with your tunnel URL:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;a href="https://dashboard.stripe.com/webhooks" rel="noopener noreferrer"&gt;Workbench → Webhooks&lt;/a&gt;, click &lt;strong&gt;Create an event destination&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Walk through the creation flow:&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Events from&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your account
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;API version&lt;/strong&gt;&lt;br&gt;
Select your default API version (or latest).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkout.session.completed
payment_intent.succeeded
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;, then select &lt;strong&gt;Webhook endpoint&lt;/strong&gt; as the destination type.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue&lt;/strong&gt;, then set:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endpoint URL&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;your-tunnel-url&amp;gt;/webhooks/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create destination&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your new endpoint, then click &lt;strong&gt;Click to reveal&lt;/strong&gt; to copy the signing secret (&lt;code&gt;whsec_...&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The webhook URL is where Stripe sends event payloads via POST. The gateway receives the request at &lt;code&gt;/webhooks/stripe&lt;/code&gt;, verifies the &lt;code&gt;Stripe-Signature&lt;/code&gt; header against the signing secret using HMAC-SHA256, and forwards validated payloads to the orders service. Unverified requests are rejected with a 400. See &lt;a href="https://docs.stripe.com/webhooks" rel="noopener noreferrer"&gt;Stripe's webhook docs&lt;/a&gt; for background on signature verification and event types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set the real PUBLIC_URL and webhook secret
&lt;/h3&gt;

&lt;p&gt;Now update the placeholder secrets with the real values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;PUBLIC_URL https://&amp;lt;your-tunnel-url&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_WEBHOOK_SECRET whsec_your_signing_secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway pod will restart automatically to pick up the new values. Verify with &lt;code&gt;kindling status&lt;/code&gt; that the gateway is back to 1/1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: The inner dev loop
&lt;/h2&gt;

&lt;p&gt;Now you're developing. You change code. You need it reflected immediately. Check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▸ Dev Staging Environments
    📦 jeff-vincent-gateway      9090   jeff-vincent-gateway.localhost
    📦 jeff-vincent-inventory    3000   jeff-vincent-inventory.localhost
    📦 jeff-vincent-orders       5000   jeff-vincent-orders.localhost
    📦 jeff-vincent-ui           80     jeff-vincent-ui.localhost

▸ All Deployments
    jeff-vincent-gateway             1/1
    jeff-vincent-inventory           1/1
    jeff-vincent-inventory-mongodb   1/1
    jeff-vincent-orders              1/1
    jeff-vincent-orders-postgres     1/1
    jeff-vincent-orders-redis        1/1
    jeff-vincent-ui                  1/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four services, three databases, all running. Open &lt;code&gt;http://jeff-vincent-ui.localhost&lt;/code&gt; and the dashboard is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: The inner dev loop
&lt;/h2&gt;

&lt;p&gt;Now you're developing. You change code. You need it reflected immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; jeff-vincent-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File changes sync directly into the running pod — no image rebuild, no redeploy. For Go services, the binary recompiles inside the container. For Python and Node.js, the file change triggers a reload automatically.&lt;/p&gt;

&lt;p&gt;Need to step through code? Run &lt;code&gt;debug&lt;/code&gt; from the project root:&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; /path/to/oauth-test
kindling debug &lt;span class="nt"&gt;-d&lt;/span&gt; jeff-vincent-orders &lt;span class="nt"&gt;--port&lt;/span&gt; 5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;kindling debug&lt;/code&gt; must be run from the project root directory — it needs access to the source tree to set up the debug session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Attach your IDE's debugger to &lt;code&gt;localhost:5678&lt;/code&gt; and set breakpoints in the orders service while it's running inside the cluster, talking to real Postgres and Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Test the OAuth and Stripe flows
&lt;/h2&gt;

&lt;p&gt;With the services running and secrets configured, verify everything is wired:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://jeff-vincent-gateway.localhost/auth/status
&lt;span class="c"&gt;# {"auth0_configured":true,"callback_url":"https://&amp;lt;your-tunnel-url&amp;gt;/auth/callback"}&lt;/span&gt;

curl http://jeff-vincent-gateway.localhost/stripe/status
&lt;span class="c"&gt;# {"stripe_webhook_configured":true,"webhook_url":"https://&amp;lt;your-tunnel-url&amp;gt;/webhooks/stripe"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your tunnel URL in a browser (e.g. &lt;code&gt;https://verb-noun-adj-noun.trycloudflare.com&lt;/code&gt;), click Login — Auth0's universal login page loads, you authenticate, and the callback redirects to your tunnel URL. The request routes through Cloudflare → localhost:80 → Traefik → the gateway ingress, which exchanges the code for tokens and sets a session cookie. The full OIDC flow, running locally.&lt;/p&gt;

&lt;p&gt;For Stripe, trigger a test event from the Stripe Dashboard (or use the &lt;a href="https://docs.stripe.com/stripe-cli" rel="noopener noreferrer"&gt;Stripe CLI&lt;/a&gt;). The event hits the gateway at your tunnel URL via the same path — Cloudflare → localhost:80 → Traefik → gateway — the signature is verified against the signing secret, and the payload is forwarded to the orders service, which updates the order status in Postgres.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Deploy to production
&lt;/h2&gt;

&lt;p&gt;The dev environment is validated. Auth0 callbacks work. Stripe webhooks land. Time to go live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling dashboard &lt;span class="nt"&gt;--prod-context&lt;/span&gt; &amp;lt;your-prod-cluster-context&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production dashboard connects to your real cluster. From there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot&lt;/strong&gt; your dev environment — the operator captures the exact image tags, env vars, and dependency configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; to production — images are re-tagged and pushed to your production registry, manifests are applied&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS&lt;/strong&gt; — cert-manager provisions Let's Encrypt certificates automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; After deploying, you'll need to update your domain's DNS A record to point to the new load balancer IP (find it with &lt;code&gt;kubectl get svc traefik -n traefik&lt;/code&gt;). DNS propagation can take a few minutes — during that time you may see a browser warning like "Your connection is not private" (&lt;code&gt;ERR_CERT_AUTHORITY_INVALID&lt;/code&gt;). This is normal. Go walk your dog, knit a sweater, contemplate the mass of the universe — whatever you do, don't sit there refreshing the browser. Once DNS propagates, cert-manager will complete the ACME challenge and the Let's Encrypt certificate will be issued automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In production, swap the secrets for production values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AUTH0_DOMAIN&lt;/code&gt; → your production Auth0 tenant&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTH0_CLIENT_ID&lt;/code&gt; / &lt;code&gt;AUTH0_CLIENT_SECRET&lt;/code&gt; → production app credentials&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; → production Stripe API key (starts with &lt;code&gt;sk_live_&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; → production webhook signing secret (starts with &lt;code&gt;whsec_&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUBLIC_URL&lt;/code&gt; → your production domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same code, the same architecture, the same deployment model. The only things that change are the secrets and the domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  What just happened
&lt;/h2&gt;

&lt;p&gt;Let's trace the full path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source code&lt;/strong&gt; on your laptop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling init&lt;/code&gt;&lt;/strong&gt; — local Kind cluster with operator, registry, ingress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling generate&lt;/code&gt;&lt;/strong&gt; — AI-generated CI workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling secrets set&lt;/code&gt;&lt;/strong&gt; — set Auth0/Stripe credentials so pods can start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/strong&gt; — local runner builds with Kaniko, deploys via operator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling expose&lt;/code&gt; + &lt;code&gt;kindling secrets set&lt;/code&gt;&lt;/strong&gt; — start tunnel, configure Auth0/Stripe callback URLs, set PUBLIC_URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling sync&lt;/code&gt;&lt;/strong&gt; — live code changes without rebuilding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test OAuth + Stripe&lt;/strong&gt; — verify callback flows end-to-end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production deploy&lt;/strong&gt; — same images, same config, real cluster with TLS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No Docker Compose file. No Helm charts. No Terraform. No cloud staging environment bill. No "works on my machine" gaps between dev and prod.&lt;/p&gt;

&lt;p&gt;The entire staging environment runs on your laptop. When it works there, it works in production — because it's the same Kubernetes, the same container images, the same networking model.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kindlingdev/tap/kindling
git clone https://github.com/kindling-sh/oauth-example
&lt;span class="nb"&gt;cd &lt;/span&gt;oauth-example
kindling init
kindling runners &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;user&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; oauth-example &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;pat&amp;gt;
kindling generate &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;api-key&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# Set credentials so pods can start&lt;/span&gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_DOMAIN &amp;lt;your-domain&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_CLIENT_ID &amp;lt;your-id&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;AUTH0_CLIENT_SECRET &amp;lt;your-secret&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;SESSION_SECRET &lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_SECRET_KEY &amp;lt;sk_test_...&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_WEBHOOK_SECRET placeholder
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;PUBLIC_URL http://localhost
&lt;span class="c"&gt;# Deploy&lt;/span&gt;
git push origin main
&lt;span class="c"&gt;# Start tunnel, configure Auth0/Stripe callback URLs, then update secrets&lt;/span&gt;
kindling expose
&lt;span class="c"&gt;# copy the tunnel URL, set it in Auth0 + Stripe dashboards&lt;/span&gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;PUBLIC_URL &amp;lt;your-tunnel-url&amp;gt;
kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_WEBHOOK_SECRET &amp;lt;whsec_...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your laptop is your staging environment. Start building.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What I've Actually Learned Using Coding Agents to Build Software</title>
      <dc:creator>Jeff Vincent</dc:creator>
      <pubDate>Tue, 24 Feb 2026 03:08:01 +0000</pubDate>
      <link>https://forem.com/jeffvincent/what-ive-actually-learned-using-coding-agents-to-build-software-4f1o</link>
      <guid>https://forem.com/jeffvincent/what-ive-actually-learned-using-coding-agents-to-build-software-4f1o</guid>
      <description>&lt;p&gt;Most of the writing about coding agents right now falls into two buckets: breathless excitement or dismissive skepticism. Neither is particularly useful if you're trying to figure out how to actually build things with these tools.&lt;/p&gt;

&lt;p&gt;I've been using GitHub Copilot (backed by Claude) as my primary development partner for a while now, building a project with real scope — a Kubernetes operator, a CLI, an embedded web dashboard, CI pipelines, the works. Not a demo. Not a weekend project. Software I ship and maintain.&lt;/p&gt;

&lt;p&gt;What follows isn't advice about prompting. It's what I've come to believe about the relationship between your codebase, your infrastructure, and how much leverage you actually get from an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern Inertia Is Real, and It Cuts Both Ways
&lt;/h2&gt;

&lt;p&gt;Coding agents are pattern-completion machines. They look at your codebase and write more of what's already there. This sounds obvious, but the second-order effects are worth thinking about carefully.&lt;/p&gt;

&lt;p&gt;Here's a concrete example. My project has two interfaces — a CLI and a web dashboard. The CLI got built first. When it came time to bring the dashboard up to feature parity, I pointed the agent at the work and let it go. What it produced was functional and correct — and completely duplicated. ~800 lines of business logic, reimplemented in a second location, because that's what the existing code looked like. Two implementations of secret management, two implementations of tunnel lifecycle, two implementations of kubectl wrappers.&lt;/p&gt;

&lt;p&gt;The agent wasn't wrong. It was doing exactly what the patterns suggested. The problem was that nobody had named the abstraction. In a team of humans, someone eventually says "hey, we should pull this out" in a code review. The agent will never feel the mess accumulating. That's your job.&lt;/p&gt;

&lt;p&gt;What's interesting is what happened when I did name it. I said, roughly, "there's a lot of duplicated code between the CLI and the dashboard — look at this closely and refactor." The agent analyzed both codebases, identified the shared logic, designed a &lt;code&gt;core/&lt;/code&gt; package with clean interfaces, migrated every call site, fixed the tests, and verified the build. Hundreds of lines removed, zero regressions. But it would never have initiated that on its own.&lt;/p&gt;

&lt;p&gt;The lesson I take from this is that your early architectural decisions carry more weight than they used to. In agent-assisted development, a pattern isn't just a pattern — it's a template the agent will replicate indefinitely. Get the abstractions right early, and you're not writing one good module. You're establishing a convention the agent follows for the rest of the project. Get them wrong, and you're compounding technical debt at the speed of generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Will Write Like You (If You Let It)
&lt;/h2&gt;

&lt;p&gt;This one genuinely surprised me.&lt;/p&gt;

&lt;p&gt;Every developer has idioms. The way you handle errors. The way you name things. The abstractions you reach for instinctively. I expected to spend a lot of time nudging the agent away from generic patterns and toward my preferences.&lt;/p&gt;

&lt;p&gt;What actually happened, once the codebase had enough of my own code in it, is that the agent internalized my style. Not "clean Go" in the abstract. My Go. My error handling conventions, my naming patterns, my preferred ways of structuring packages. At a certain point, code the agent wrote was indistinguishable from code I wrote — not because it was copying, but because it had enough signal to generalize from.&lt;/p&gt;

&lt;p&gt;This has a practical implication that I think people undervalue: the time you spend early on writing clean, consistent, opinionated code isn't just good hygiene. It's training data. The more coherent your existing codebase is, the less correction you do later. Your style becomes the agent's style, and the collaboration gets smoother the longer you work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Pipeline Is the Real Feedback Loop
&lt;/h2&gt;

&lt;p&gt;Here's maybe the most important structural insight I've arrived at: the agent needs guardrails it can run against, and your CI/CD pipeline is where those guardrails live.&lt;/p&gt;

&lt;p&gt;An agent can produce a lot of plausible-looking code quickly. "Plausible-looking" and "actually works" are different things. Without a tight feedback loop, you accumulate silent drift — code that reads fine, passes a casual glance, and breaks somewhere downstream.&lt;/p&gt;

&lt;p&gt;In practice, what I've found works is treating the build-test-lint cycle as the environment that keeps the agent honest. When the agent refactored my codebase — touching 19 files across two packages — the Go compiler immediately caught every signature mismatch, every missing import, every call site that didn't match the new interface. The agent fixed them iteratively, ran the tests, confirmed zero regressions, and moved on. I didn't review individual lines during that process. I didn't need to. The pipeline told both of us whether the code was correct.&lt;/p&gt;

&lt;p&gt;This is also what makes delegation possible. If you trust your pipeline to catch problems, you can hand the agent a substantial task — "write an e2e test suite that exercises every feature group against a real Kind cluster" — and let it work. Without that trust, you're reading every line of output, which isn't meaningfully faster than writing it yourself.&lt;/p&gt;

&lt;p&gt;The tighter and faster the feedback, the less drift you get. A build that runs in seconds is worth more than a code review that happens in hours.&lt;/p&gt;

&lt;p&gt;All of this is how and why I built &lt;a href="https://github.com/kindling-sh/kindling" rel="noopener noreferrer"&gt;Kindling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The feedback loop I just described only works if it's fast. Cloud CI is slow. You push a commit, wait in a queue behind everyone else's jobs, watch a runner spin up from scratch, and by the time the feedback lands you've already context-switched into something else. That's not a feedback loop. That's a waiting room.&lt;/p&gt;

&lt;p&gt;Kindling flips the model. It's a Kubernetes operator that routes your GitHub Actions CI jobs back to your own laptop, running in a local Kind cluster. When you push code, the job comes to you. It builds your container image via Kaniko, right there on your machine, and deploys a full ephemeral staging environment — Deployment, Service, Ingress, all auto-provisioned dependencies wired up and ready — to localhost. In seconds. On compute you already own.&lt;/p&gt;

&lt;p&gt;The patterns I've been describing aren't abstract. They showed up here. The CLI and dashboard diverging into ~800 lines of duplication? That happened in Kindling — two interfaces, one codebase, and an agent that faithfully reproduced the mess until I explicitly told it otherwise. The types that caught mistakes early? Go's type system running in a tight local build loop, surfacing errors before they had a chance to become integration bugs. The idiosyncratic style the agent picked up? A Go codebase opinionated enough about its abstractions that Copilot eventually just wrote them the same way.&lt;/p&gt;

&lt;p&gt;Kindling also ships a &lt;code&gt;kindling generate&lt;/code&gt; command that uses an LLM to scan your repo and produce a GitHub Actions workflow tailored to what's actually in your code — detected services, dependencies, ports, credentials, the works. It's a coding agent operating on your infrastructure configuration. And it works well precisely because the environment it's generating for is well-defined and testable. The feedback loop is tight. The builds are fast. The agent stays honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strong Types Are Cheap Insurance
&lt;/h2&gt;

&lt;p&gt;This is a narrower point but it matters more than I expected: invest in strong typing early, even when it feels like overhead.&lt;/p&gt;

&lt;p&gt;When the agent makes a mistake in a dynamically typed language, that mistake might not surface until runtime, or integration, or production. In a strongly typed language, the compiler catches it in seconds and tells the agent exactly what went wrong and where. A compile failure is free. An integration bug discovered three days later costs a week.&lt;/p&gt;

&lt;p&gt;The same logic applies to schemas, API contracts, CRD definitions — anything that gives the agent a formal specification to check its work against. Without that, the agent is improvising. Improvisation at speed is how you get subtle inconsistencies that are painful to debug later.&lt;/p&gt;

&lt;p&gt;I think of the type system as just another part of the feedback loop. Every checkpoint that catches drift early makes the whole system more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Actually Changes About Your Job
&lt;/h2&gt;

&lt;p&gt;The shift I've experienced isn't "I write code faster now." It's that my job has changed shape.&lt;/p&gt;

&lt;p&gt;I spend less time writing individual functions and more time thinking about architecture — where the boundaries should be, what patterns I want replicated, what the feedback loops look like. The agent handles volume. I handle direction. When I see something wrong, I name it at the level of design intent — "this is duplicated," "this should be a shared package," "write an e2e that covers every feature group" — and the agent figures out the implementation.&lt;/p&gt;

&lt;p&gt;This isn't a small change. It means the skills that matter shift toward systems thinking, toward knowing when an abstraction is missing, toward building infrastructure that validates correctness automatically. The developers who get the most out of these tools won't be the ones who learn to prompt better. They'll be the ones who build environments where the agent can do good work — tight feedback loops, clean patterns, strong types, fast builds.&lt;/p&gt;

&lt;p&gt;That's less exciting than "AI writes all my code." But it's closer to the truth, and honestly, it's more interesting. You're not being replaced by the tool. You're designing the system in which the tool operates. That's a different skill, and it's worth developing deliberately.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>githubcopilot</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Kindling v0.6.0: Hot Reload for Every Language in Your Kubernetes Dev Environment</title>
      <dc:creator>Jeff Vincent</dc:creator>
      <pubDate>Mon, 23 Feb 2026 03:15:37 +0000</pubDate>
      <link>https://forem.com/jeffvincent/kindling-v060-hot-reload-for-every-language-in-your-kubernetes-dev-environment-3cp0</link>
      <guid>https://forem.com/jeffvincent/kindling-v060-hot-reload-for-every-language-in-your-kubernetes-dev-environment-3cp0</guid>
      <description>&lt;p&gt;&lt;strong&gt;February 2026 · &lt;a href="https://github.com/kindling-sh/kindling" rel="noopener noreferrer"&gt;kindling-sh/kindling&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;We just shipped v0.6.0 of kindling, and the headline feature is &lt;code&gt;kindling sync&lt;/code&gt; — live hot reload for any language running in a Kubernetes pod. This post walks through how it works, what it replaces, and where the project is headed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you're building on Kubernetes, your development loop probably looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit code&lt;/li&gt;
&lt;li&gt;Build a container image&lt;/li&gt;
&lt;li&gt;Push to a registry&lt;/li&gt;
&lt;li&gt;Wait for a rollout&lt;/li&gt;
&lt;li&gt;Check logs&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even with local tools, steps 2–4 take 30–90 seconds. Multiply that by the number of iterations in a session and you lose hours per week to build latency. Docker Compose sidesteps Kubernetes entirely, but then you're not testing against real Services, Ingress, RBAC, or any of the infrastructure your app actually runs on.&lt;/p&gt;

&lt;p&gt;Kindling takes a different approach: keep the full Kubernetes environment, but make the feedback loop sub-second.&lt;/p&gt;

&lt;h2&gt;
  
  
  How &lt;code&gt;kindling sync&lt;/code&gt; Works
&lt;/h2&gt;

&lt;p&gt;The sync command watches your local source directory using fsnotify, debounces changes (default 500ms), and copies modified files into the running container via &lt;code&gt;kubectl cp&lt;/code&gt;. That part is straightforward. The interesting part is what happens next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime Detection
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;kindling sync -d my-api --restart&lt;/code&gt;, the CLI reads &lt;code&gt;/proc/1/cmdline&lt;/code&gt; from the target container to identify the running process. It matches the process name against a table of 30+ known runtimes and selects a restart strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kindling sync -d my-api --restart
✓ Detected runtime: Python (uvicorn)
✓ Strategy: signal reload (SIGHUP)
⚡ Watching /Users/you/src/my-api → /app in pod my-api-6f8b9c4d7-x2k9p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detection is based on the actual PID 1 binary, not filenames or heuristics. If the container runs &lt;code&gt;uvicorn&lt;/code&gt;, kindling sends SIGHUP. If it runs &lt;code&gt;node server.js&lt;/code&gt;, kindling injects a restart-loop wrapper. If it runs a compiled Go binary, kindling cross-compiles locally and syncs the binary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Restart Modes
&lt;/h3&gt;

&lt;p&gt;The sync engine implements four distinct restart strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signal reload&lt;/strong&gt; — For servers that support graceful reload (uvicorn, gunicorn, Puma, nginx, Apache). Kindling sends SIGHUP to PID 1. No wrapper injection, no downtime, no container restart. This is the lightest path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrapper + kill&lt;/strong&gt; — For interpreted runtimes (Node.js, Python, Ruby, Deno, Bun, Elixir, Perl). Kindling patches the deployment to wrap the original command in a restart loop (&lt;code&gt;while true; do &amp;lt;original cmd&amp;gt;; sleep 1; done&lt;/code&gt;), then kills the child process after each sync. The loop respawns it with the updated code. The deployment spec records the pre-patch revision for rollback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local build + binary sync&lt;/strong&gt; — For compiled languages (Go, Rust, Java/Gradle, C#, C/C++, Zig). Kindling queries the Kind node's CPU architecture, cross-compiles on your host machine, and syncs the resulting binary into the container. For Go, the default command is:&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="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/kindling-build/&amp;lt;binary&amp;gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture is detected automatically — no manual &lt;code&gt;GOARCH&lt;/code&gt; flag needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-reload&lt;/strong&gt; — For runtimes that already watch for file changes (PHP with mod_php/php-fpm, nodemon). Files are synced and the runtime picks them up. No restart signal needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Rollback
&lt;/h3&gt;

&lt;p&gt;When you stop a sync session — Ctrl+C from the CLI or the Stop button in the dashboard — kindling restores the deployment to its pre-sync state. If a wrapper was injected, it performs a &lt;code&gt;kubectl rollout undo&lt;/code&gt; to the saved revision. If only files were synced (signal-reload servers), it does a &lt;code&gt;rollout restart&lt;/code&gt; to get a fresh pod from the original image. Either way, the container goes back to exactly where it was.&lt;/p&gt;

&lt;p&gt;This matters because sync is a dev-time overlay, not a deployment mechanism. You shouldn't have to remember to clean up patched deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load: Full Rebuild Without the CI Round-Trip
&lt;/h3&gt;

&lt;p&gt;Sync covers most iteration, but sometimes you need a real container build — you changed a dependency, updated a Dockerfile, or you're working with a language the sync engine doesn't have a runtime profile for yet. That's what Load does.&lt;/p&gt;

&lt;p&gt;The pipeline is three steps, all local:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docker build&lt;/code&gt;&lt;/strong&gt; — builds the image on your machine using the project's Dockerfile, with optional &lt;code&gt;--platform&lt;/code&gt; for cross-arch (e.g., &lt;code&gt;linux/arm64&lt;/code&gt; on an Apple Silicon host)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kind load docker-image&lt;/code&gt;&lt;/strong&gt; — loads the built image directly into the Kind cluster's containerd store, no registry push needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patch + rollout&lt;/strong&gt; — updates the DSE or Deployment image reference and waits for the rolling update to complete&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the CLI, this is &lt;code&gt;kindling push&lt;/code&gt; (which wraps &lt;code&gt;git push&lt;/code&gt; and triggers the full CI pipeline) or, for a faster local-only path, the Load button in the dashboard. The dashboard's Load modal auto-discovers your project's service directories and Dockerfiles, so you pick a service and click build.&lt;/p&gt;

&lt;p&gt;The key difference from sync: Load produces a real container image. The deployment runs your updated Dockerfile, installs new dependencies, and goes through the normal Kubernetes rollout. It takes 15–60 seconds instead of sub-second, but it's a complete build — and it's still entirely local, no CI minutes consumed.&lt;/p&gt;

&lt;p&gt;For languages where sync doesn't yet have a runtime profile, Load is the inner loop. Edit code, click Load, wait for the rollout. It's slower than sync but still faster than pushing to a remote CI system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dashboard
&lt;/h2&gt;

&lt;p&gt;v0.6.0 also ships a built-in web dashboard (&lt;code&gt;kindling dashboard&lt;/code&gt;) — a single-binary React app embedded in the CLI via &lt;code&gt;go:embed&lt;/code&gt;. It shows your cluster state and provides one-click access to the inner loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each deployed service shows its detected runtime as a badge (Node.js, Python, Go, nginx, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync&lt;/strong&gt; button opens a modal pre-filled with the detected runtime and source directory, starts a sync session via the API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load&lt;/strong&gt; button triggers a full rebuild: &lt;code&gt;docker build&lt;/code&gt; → &lt;code&gt;kind load&lt;/code&gt; → &lt;code&gt;rollout restart&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Live sync status (file count, duration) is visible on each card while a session is active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dashboard talks to the same APIs as the CLI. Everything you can do in the terminal, you can do from the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling dashboard &lt;span class="nt"&gt;--port&lt;/span&gt; 9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Kindling Actually Is
&lt;/h2&gt;

&lt;p&gt;Kindling is a Kubernetes operator and CLI that gives you a complete dev environment on a local Kind cluster. The full system has two loops:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outer loop (CI):&lt;/strong&gt; &lt;code&gt;git push&lt;/code&gt; triggers a real GitHub Actions workflow. The job runs on a self-hosted runner inside your Kind cluster. Kaniko builds the image (no Docker daemon), pushes to an in-cluster registry, and the kindling operator reconciles a &lt;code&gt;DevStagingEnvironment&lt;/code&gt; CR into a running Deployment with Service, Ingress, and auto-provisioned dependencies (Postgres, Redis, MongoDB, Kafka, RabbitMQ, and others — 15 dependency types).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inner loop (dev):&lt;/strong&gt; &lt;code&gt;kindling sync&lt;/code&gt; watches local files, syncs them into the running container, and restarts the process. Sub-second feedback without rebuilding an image. When you're done iterating, stop sync and the deployment rolls back.&lt;/p&gt;

&lt;p&gt;The inner loop runs inside the outer loop. You push code to get a real staging environment, then use sync to iterate on it without waiting for builds. When you're satisfied, push again and the CI pipeline produces the real artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;p&gt;A few implementation notes for anyone reading the source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;File watching&lt;/strong&gt; uses &lt;a href="https://github.com/fsnotify/fsnotify" rel="noopener noreferrer"&gt;fsnotify&lt;/a&gt; with configurable debounce. Changes are batched and synced as a group to avoid thrashing the container with rapid saves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime detection&lt;/strong&gt; parses &lt;code&gt;/proc/1/cmdline&lt;/code&gt; via &lt;code&gt;kubectl exec&lt;/code&gt;. The process name is matched against a lookup table, with fallback heuristics for bare interpreters (e.g., &lt;code&gt;python3&lt;/code&gt; running a gunicorn module gets detected as gunicorn by scanning the full command line for known framework entry points).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture detection&lt;/strong&gt; for compiled-language cross-compilation reads the Kind node's architecture via &lt;code&gt;kubectl get node -o jsonpath='{.status.nodeInfo.architecture}'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distroless containers&lt;/strong&gt; — containers without a shell — are handled by falling back to the Kubernetes API's tar-stream endpoint instead of &lt;code&gt;kubectl cp&lt;/code&gt;, which requires &lt;code&gt;tar&lt;/code&gt; in the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrapper injection&lt;/strong&gt; patches the deployment spec's container command. The original command and revision number are recorded so rollback is exact, not just "undo the last change."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default excludes&lt;/strong&gt; skip &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;__pycache__&lt;/code&gt;, &lt;code&gt;vendor&lt;/code&gt;, &lt;code&gt;dist&lt;/code&gt;, &lt;code&gt;.next&lt;/code&gt;, build outputs, and other artifacts. Additional patterns can be added with &lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The dashboard frontend is a TypeScript/React SPA built with Vite, compiled to static assets, and embedded into the Go binary at build time. No separate process, no npm at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kindling-sh/tap/kindling
kindling init
kindling deploy &lt;span class="nt"&gt;-f&lt;/span&gt; dev-environment.yaml
kindling &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; my-service &lt;span class="nt"&gt;--restart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or open the dashboard and click Sync.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://kindling-sh.github.io/kindling/getting-started/" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt; walks through the full setup, from cluster bootstrap to inner-loop iteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Kindling is Apache 2.0 licensed and actively looking for contributors and early adopters. The codebase is Go (operator + CLI) and TypeScript (dashboard), built with Kubebuilder v4. Current areas where contributions would have the most impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime profiles&lt;/strong&gt; — adding detection and restart strategies for languages/frameworks not yet in the table (Scala, Haskell, OCaml, Swift, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync engine hardening&lt;/strong&gt; — edge cases around symlinks, large binary files, permission preservation across different container base images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard features&lt;/strong&gt; — log streaming, resource usage visualization, multi-environment views&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt; — expanding e2e coverage across different project types and cluster configurations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — tutorials, example projects, integration guides for specific frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building microservices on Kubernetes and tired of waiting for builds, try it out. File issues, open PRs, or just tell us what breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/kindling-sh/kindling" rel="noopener noreferrer"&gt;github.com/kindling-sh/kindling&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://kindling-sh.github.io/kindling/" rel="noopener noreferrer"&gt;kindling-sh.github.io/kindling&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Run Real K8s CI on your Laptop with Kindling</title>
      <dc:creator>Jeff Vincent</dc:creator>
      <pubDate>Mon, 16 Feb 2026 07:09:42 +0000</pubDate>
      <link>https://forem.com/jeffvincent/run-real-k8s-ci-on-your-laptop-with-kindling-34bd</link>
      <guid>https://forem.com/jeffvincent/run-real-k8s-ci-on-your-laptop-with-kindling-34bd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Kindling wires your GitHub repo to a local Kubernetes cluster. Every &lt;code&gt;git push&lt;/code&gt; builds and deploys your app with real databases and services. No cloud CI minutes, no waiting. AI generates your workflow. A built-in dashboard gives you full cluster visibility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://kindling.sh/" rel="noopener noreferrer"&gt;Kindling&lt;/a&gt; is a Kubernetes operator that runs in a local KinD cluster. Point it at your repo, and it handles builds and deployments locally while you develop.&lt;/p&gt;

&lt;p&gt;It spins up a GitHub Actions runner pool inside your cluster. When you push code, the runner builds your containers using &lt;a href="https://github.com/GoogleContainerTools/kaniko" rel="noopener noreferrer"&gt;Kaniko&lt;/a&gt; and deploys everything to the cluster. Your app gets real Postgres, Redis, Kafka, or whatever you need — already configured and wired up.&lt;/p&gt;

&lt;p&gt;Since the initial release, the CLI has grown quite a bit. Here's what's new.&lt;/p&gt;

&lt;p&gt;The operator supports 15 dependency types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default Image&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Injected Env Var&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postgres&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;postgres:16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5432&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-creates &lt;code&gt;devdb&lt;/code&gt; with user &lt;code&gt;devuser&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6379&lt;/td&gt;
&lt;td&gt;&lt;code&gt;REDIS_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stateless, no persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mysql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mysql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3306&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-creates &lt;code&gt;devdb&lt;/code&gt; with user &lt;code&gt;devuser&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mongodb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;27017&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MONGO_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Root user &lt;code&gt;devuser&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rabbitmq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rabbitmq:3-management&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5672&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AMQP_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Includes management UI on port 15672&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;minio/minio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_ENDPOINT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also injects &lt;code&gt;S3_ACCESS_KEY&lt;/code&gt; + &lt;code&gt;S3_SECRET_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;elasticsearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker.elastic.co/elasticsearch/elasticsearch:8.12.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9200&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ELASTICSEARCH_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single-node, security disabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apache/kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9092&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KAFKA_BROKER_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;KRaft mode (no ZooKeeper)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4222&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NATS_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lightweight messaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memcached&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memcached&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11211&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MEMCACHED_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In-memory cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cassandra&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cassandra&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9042&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CASSANDRA_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single-node dev cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;consul&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hashicorp/consul&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8500&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONSUL_HTTP_ADDR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dev-mode agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hashicorp/vault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8200&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VAULT_ADDR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dev mode, also injects &lt;code&gt;VAULT_TOKEN&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;influxdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;influxdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8086&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INFLUXDB_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also injects &lt;code&gt;INFLUXDB_ORG&lt;/code&gt; + &lt;code&gt;INFLUXDB_BUCKET&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jaeger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jaegertracing/all-in-one&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16686&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JAEGER_ENDPOINT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also injects &lt;code&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  AI-powered workflow generation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling generate&lt;/code&gt; scans your repo and calls an LLM to produce a complete GitHub Actions workflow. It walks your project tree, reads Dockerfiles, dependency manifests, and source files, and sends the right context to the model. It recognizes 20+ ecosystems — Go, Node, Python, Rust, Java, Ruby, PHP, Elixir, .NET, and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling generate &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;your-api-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also detects external credential references and OAuth/OIDC patterns in your code. If it finds them, it suggests follow-up commands like &lt;code&gt;kindling secrets set&lt;/code&gt; and &lt;code&gt;kindling expose&lt;/code&gt;. Supports both OpenAI and Anthropic models via the &lt;code&gt;--provider&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;The generated workflow drops into &lt;code&gt;.github/workflows/dev-deploy.yml&lt;/code&gt;, ready to go on your next push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded dashboard
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling dashboard&lt;/code&gt; launches a local web UI embedded directly in the CLI binary. No extra installs, no Docker image — just run the command and open &lt;code&gt;http://localhost:9090&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard gives you full cluster visibility: nodes, deployments, pods, services, ingresses, secrets, events, DSEs, runner pools, and RBAC resources. It also exposes action endpoints — you can deploy, manage secrets, create runners, set env vars, expose tunnels, restart or scale deployments, and apply raw YAML, all from the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F746x068flq42pejivk89.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%2F746x068flq42pejivk89.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a command palette (Cmd+K) for quick actions and a sidebar with real-time tunnel status when you have a public URL active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public HTTPS tunnels
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling expose&lt;/code&gt; creates a secure tunnel from a public URL to your cluster's ingress controller. Useful for OAuth callbacks, webhook testing, or showing someone your work without deploying to the cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling expose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs cloudflared (or ngrok) in the background and patches your ingress routes to use the tunnel hostname. When you're done, &lt;code&gt;kindling expose --stop&lt;/code&gt; tears it down and restores the original ingress config. The tunnel URL is also stored in a ConfigMap so the deploy action can auto-discover it during CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets management
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling secrets&lt;/code&gt; manages external service credentials as Kubernetes Secrets. API keys, tokens, connection strings — set them once and they're available to your deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling secrets &lt;span class="nb"&gt;set &lt;/span&gt;STRIPE_KEY sk_test_abc123
kindling secrets list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Credentials are also saved to &lt;code&gt;.kindling/secrets.yaml&lt;/code&gt; locally, so &lt;code&gt;kindling secrets restore&lt;/code&gt; can re-create them after a cluster rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live env var management
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling env&lt;/code&gt; sets or removes environment variables on a running deployment without redeploying. Changes take effect immediately via a rolling restart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling &lt;span class="nb"&gt;env set &lt;/span&gt;my-app &lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;&lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;verbose
kindling &lt;span class="nb"&gt;env unset &lt;/span&gt;my-app DEBUG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Selective rebuilds
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kindling push&lt;/code&gt; wraps &lt;code&gt;git push&lt;/code&gt; with an optional service filter. Tag your push so CI only rebuilds the services you changed instead of the full pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling push &lt;span class="nt"&gt;--service&lt;/span&gt; ui &lt;span class="nt"&gt;--service&lt;/span&gt; gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It amends the HEAD commit with a &lt;code&gt;[kindling:ui,gateway]&lt;/code&gt; marker that CI parses. Omit &lt;code&gt;--service&lt;/code&gt; to rebuild everything as usual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other CLI commands
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling status&lt;/code&gt;&lt;/strong&gt; — Full environment overview: cluster health, operator, registry, runner pools, DSEs, deployments, ingress routes, and unhealthy pod logs. Color-coded, no flags needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling logs&lt;/code&gt;&lt;/strong&gt; — Streams operator logs. &lt;code&gt;--all&lt;/code&gt; for all containers, &lt;code&gt;--since 10m&lt;/code&gt; to adjust the window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling reset&lt;/code&gt;&lt;/strong&gt; — Removes the runner pool and its token secret without touching the cluster or your deployments. Good for switching repos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kindling destroy&lt;/code&gt;&lt;/strong&gt; — Tears down the entire Kind cluster.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You need Docker, Kind, and kubectl installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the CLI
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Homebrew (recommended — macOS and Linux)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kindling-sh/tap/kindling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the latest release and pulls in Kind and kubectl as dependencies.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-built binaries
&lt;/h4&gt;

&lt;p&gt;Download the latest release for your platform from &lt;a href="https://github.com/kindling-sh/kindling/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS (Apple Silicon)&lt;/span&gt;
curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; kindling.tar.gz https://github.com/kindling-sh/kindling/releases/latest/download/kindling_&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/kindling-sh/kindling/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;tag_name | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;_darwin_arm64.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf kindling.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kindling /usr/local/bin/

&lt;span class="c"&gt;# macOS (Intel)&lt;/span&gt;
curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; kindling.tar.gz https://github.com/kindling-sh/kindling/releases/latest/download/kindling_&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/kindling-sh/kindling/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;tag_name | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;_darwin_amd64.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf kindling.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kindling /usr/local/bin/

&lt;span class="c"&gt;# Linux (amd64)&lt;/span&gt;
curl &lt;span class="nt"&gt;-Lo&lt;/span&gt; kindling.tar.gz https://github.com/kindling-sh/kindling/releases/latest/download/kindling_&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.github.com/repos/kindling-sh/kindling/releases/latest | &lt;span class="nb"&gt;grep &lt;/span&gt;tag_name | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="nt"&gt;-f4&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^v//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;_linux_amd64.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf kindling.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kindling /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;macOS Gatekeeper note:&lt;/strong&gt; If you see &lt;em&gt;"Apple could not verify kindling is free of malware"&lt;/em&gt;, clear the quarantine flag:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;xattr &lt;span class="nt"&gt;-d&lt;/span&gt; com.apple.quarantine /usr/local/bin/kindling
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Build from source
&lt;/h4&gt;

&lt;p&gt;Requires Go 1.25+:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/kindling-sh/kindling.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;kindling
make cli
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;bin/kindling /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Bootstrap the cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a KinD cluster, installs an in-cluster registry, sets up ingress-nginx, and deploys the operator. Takes about a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create a GitHub repo
&lt;/h3&gt;

&lt;p&gt;You need a repo on GitHub before you can register a runner to it. The easiest way is with the &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&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 repo create my-app &lt;span class="nt"&gt;--public&lt;/span&gt; &lt;span class="nt"&gt;--clone&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have &lt;code&gt;gh&lt;/code&gt; installed, create the repo on &lt;a href="https://github.com/new" rel="noopener noreferrer"&gt;github.com/new&lt;/a&gt;, then clone it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:&amp;lt;you&amp;gt;/my-app.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Register your GitHub runner
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the following &lt;code&gt;-r&lt;/code&gt; should be in the format of &lt;code&gt;&amp;lt;your-gh-user-name&amp;gt;/my-app&lt;/code&gt; if you followed the above exactly.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling runners &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;github-user&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;owner/repo&amp;gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;your-pat&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" rel="noopener noreferrer"&gt;GitHub PAT with &lt;code&gt;repo&lt;/code&gt; scope&lt;/a&gt;. The runner registers with GitHub and starts polling for jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Generate your workflow
&lt;/h3&gt;

&lt;p&gt;You can write the GitHub Actions workflow by hand, or let the AI generate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kindling generate &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;your-openai-or-anthropic-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scans your project and writes &lt;code&gt;.github/workflows/dev-deploy.yml&lt;/code&gt;. Review it, tweak if needed, and move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Deploy the sample app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ~/kindling/examples/microservices/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ~/kindling/examples/microservices/.github &lt;span class="nb"&gt;.&lt;/span&gt;

git add &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial deploy"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Watch it deploy
&lt;/h3&gt;

&lt;p&gt;Check the Actions tab in GitHub or watch pods 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 get pods &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app, Postgres, and Redis will spin up and start running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fnsl7e0mm58g0b90s5i.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%2F9fnsl7e0mm58g0b90s5i.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Access your app
&lt;/h3&gt;

&lt;p&gt;Navigate in your browser to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your-gh-username&amp;gt;-ui.localhost
&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%2Fx0ncjph3u4792lh278f7.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%2Fx0ncjph3u4792lh278f7.png" alt=" " width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Make changes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Edit code&lt;/span&gt;
git add &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"update"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fresh build and deploy in seconds.&lt;/p&gt;

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

&lt;p&gt;The GitHub workflow uses two actions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;kindling-build&lt;/code&gt;&lt;/strong&gt; builds your image with Kaniko and pushes to the in-cluster registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;kindling-deploy&lt;/code&gt;&lt;/strong&gt; generates a &lt;code&gt;DevStagingEnvironment&lt;/code&gt; resource. The operator reads it and creates your Deployment, Service, Ingress, and backing services.&lt;/p&gt;

&lt;p&gt;Here's a sample app workflow:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build image&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kindling-sh/kindling/.github/actions/kindling-build@main&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample-app&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registry:5000/sample-app:${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.TAG&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kindling-sh/kindling/.github/actions/kindling-deploy@main&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.actor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-sample-app"&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registry:5000/sample-app:${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.TAG&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt;
    &lt;span class="na"&gt;ingress-host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.actor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-sample-app.localhost"&lt;/span&gt;
    &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;- type: postgres&lt;/span&gt;
        &lt;span class="s"&gt;version: "16"&lt;/span&gt;
      &lt;span class="s"&gt;- type: redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multi-cluster support&lt;/li&gt;
&lt;li&gt;Built-in observability&lt;/li&gt;
&lt;li&gt;Helm chart integration&lt;/li&gt;
&lt;li&gt;Change detection at the operator level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Contributions welcome. Open an issue or PR on the &lt;a href="https://github.com/kindling-sh/kindling" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. Apache 2.0 licensed.&lt;/p&gt;

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

&lt;p&gt;Kindling gives you local CI speed with real infrastructure. Push code, get a deployed app with real databases in seconds. Generate your workflow with AI, manage secrets and env vars without redeploying, expose your app publicly for OAuth testing, and monitor everything from a built-in dashboard. If you're tired of waiting for cloud CI or want tighter feedback loops, give it a try.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kindling-sh/kindling" rel="noopener noreferrer"&gt;Star the repo&lt;/a&gt; if you find it useful.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Say "Hello" to Byte Sized K8s</title>
      <dc:creator>Jeff Vincent</dc:creator>
      <pubDate>Sat, 30 Sep 2023 19:26:00 +0000</pubDate>
      <link>https://forem.com/jeffvincent/say-hello-to-byte-sized-k8s-3541</link>
      <guid>https://forem.com/jeffvincent/say-hello-to-byte-sized-k8s-3541</guid>
      <description>&lt;p&gt;I'm excited to announce the start of a new side project, in which I'm creating "byte sized" videos that explain specific Kubernetes topics in 10 minutes or less. &lt;/p&gt;

&lt;p&gt;If you're thinking of getting started with K8s, but don't have time to dig into a textbook, check out this video series. &lt;/p&gt;

&lt;p&gt;Here are the first two installments:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ttLLN21o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vputmpdpicsrhfliba57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ttLLN21o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vputmpdpicsrhfliba57.png" alt="Byte Sized K8s: Containers" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=YrQ9C4J2v6E"&gt;Byte Sized K8s: Containers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KcydZQg5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb0azjye7wuwnxd1b077.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KcydZQg5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb0azjye7wuwnxd1b077.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=9pyi6aCjTf4"&gt;Byte Sized K8s: Pods&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy developing!!!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
