<?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: Semih ERDOGAN</title>
    <description>The latest articles on Forem by Semih ERDOGAN (@semiherdogan).</description>
    <link>https://forem.com/semiherdogan</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%2F3918052%2Fd806d51a-71a1-49fd-ab25-f6f49f46936e.jpeg</url>
      <title>Forem: Semih ERDOGAN</title>
      <link>https://forem.com/semiherdogan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/semiherdogan"/>
    <language>en</language>
    <item>
      <title>handoff: Keeping AI Coding Sessions on Track</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Fri, 08 May 2026 05:55:05 +0000</pubDate>
      <link>https://forem.com/semiherdogan/handoff-keeping-ai-coding-sessions-on-track-op0</link>
      <guid>https://forem.com/semiherdogan/handoff-keeping-ai-coding-sessions-on-track-op0</guid>
      <description>&lt;p&gt;AI coding tools are very good at helping with the next step.&lt;/p&gt;

&lt;p&gt;They are much worse at reliably carrying the full thread of a feature across multiple sessions.&lt;/p&gt;

&lt;p&gt;That was the problem I kept running into.&lt;/p&gt;

&lt;p&gt;I would start a feature with Claude, ChatGPT, Copilot, or another assistant, make real progress, then step away. When I came back, I had the same questions again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly was I building?&lt;/li&gt;
&lt;li&gt;What decisions had already been made?&lt;/li&gt;
&lt;li&gt;What was the current step?&lt;/li&gt;
&lt;li&gt;What should happen next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model had no memory. I had partial memory. The repo had some memory. None of it was structured enough.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;handoff&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What handoff is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; is a local-first CLI for structured AI coding workflows.&lt;/p&gt;

&lt;p&gt;It creates a small workspace inside your repository under &lt;code&gt;.handoff/&lt;/code&gt; and uses plain Markdown files as the source of truth for the feature you are working on.&lt;/p&gt;

&lt;p&gt;The core workflow revolves around five files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FEATURE.md&lt;/code&gt;: raw feature intent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SPEC.md&lt;/code&gt;: normalized requirements and acceptance criteria&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DESIGN.md&lt;/code&gt;: optional technical design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE.md&lt;/code&gt;: execution plan and progress&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SESSION.md&lt;/code&gt;: continuation-safe session summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No cloud sync. No provider lock-in. No hidden agent runtime.&lt;/p&gt;

&lt;p&gt;Just files, prompts, and a deterministic workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem was not only memory
&lt;/h2&gt;

&lt;p&gt;At first, I thought continuation was the only missing piece.&lt;/p&gt;

&lt;p&gt;After enough AI-assisted coding sessions, I realized there were actually two different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the assistant forgets where the work stopped&lt;/li&gt;
&lt;li&gt;the assistant starts implementation before the work is decomposed well enough&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second problem matters just as much.&lt;/p&gt;

&lt;p&gt;If the feature is vague, the assistant tends to improvise. Sometimes that works. Sometimes it creates drift, partial implementation, or too much code too early.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff&lt;/code&gt; is planning-aware, not just continuation-aware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I wanted
&lt;/h2&gt;

&lt;p&gt;I wanted something with the strengths of structured decomposition, but without forcing people into one IDE or one proprietary workflow.&lt;/p&gt;

&lt;p&gt;I also did not want a system where users had to manually juggle several tools just to start a feature.&lt;/p&gt;

&lt;p&gt;So the result became a hybrid workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple when you want speed&lt;/li&gt;
&lt;li&gt;explicit when you want control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The default path
&lt;/h2&gt;

&lt;p&gt;For most features, the flow starts like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then you edit &lt;code&gt;.handoff/current/FEATURE.md&lt;/code&gt; with the feature request, requirements, and constraints.&lt;/p&gt;

&lt;p&gt;After that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is now the default entry point.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff run&lt;/code&gt; looks at the saved workspace state and decides what prompt should come next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if planning is incomplete, it emits a planning prompt&lt;/li&gt;
&lt;li&gt;if the execution plan is ready, it emits an execution prompt&lt;/li&gt;
&lt;li&gt;if execution is already underway, it emits a continuation prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the simple path simple.&lt;/p&gt;

&lt;p&gt;You can also use:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;to inspect what the tool thinks should happen next without generating another prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The advanced path
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to review the planning before any code is written.&lt;/p&gt;

&lt;p&gt;For that, &lt;code&gt;handoff&lt;/code&gt; also has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those commands let you inspect the work in stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spec&lt;/code&gt;: turn feature intent into clear requirements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design&lt;/code&gt;: map those requirements to a practical implementation approach&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tasks&lt;/code&gt;: generate an execution-ready task list in &lt;code&gt;STATE.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and begin implementation from a cleaner plan.&lt;/p&gt;

&lt;p&gt;That distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run&lt;/code&gt; is the default state-aware entry point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; is for direct execution when a valid plan already exists&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the file split matters
&lt;/h2&gt;

&lt;p&gt;The value is not just "more files."&lt;/p&gt;

&lt;p&gt;The value is that each file has one job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FEATURE.md&lt;/code&gt; captures intent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SPEC.md&lt;/code&gt; captures what must be true&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DESIGN.md&lt;/code&gt; captures how to approach it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE.md&lt;/code&gt; captures what is being done right now&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SESSION.md&lt;/code&gt; captures what the next session must know&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation gives the assistant better footing.&lt;/p&gt;

&lt;p&gt;It also gives you better reviewability. You can inspect the spec before implementation, challenge the design before code, and check whether the task list actually matches the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why determinism matters
&lt;/h2&gt;

&lt;p&gt;One of the design goals of &lt;code&gt;handoff&lt;/code&gt; is determinism.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff continue&lt;/code&gt; is guarded.&lt;/p&gt;

&lt;p&gt;If the execution plan is invalid, the command fails with a deterministic error instead of pretending everything is fine.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;no execution plan initialized&lt;/li&gt;
&lt;li&gt;multiple current &lt;code&gt;[&amp;gt;]&lt;/code&gt; steps&lt;/li&gt;
&lt;li&gt;no remaining steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That behavior is deliberate.&lt;/p&gt;

&lt;p&gt;I do not want a workflow that silently fixes state by guessing. I want the handoff to be inspectable and stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Imagine I am adding a payment flow.&lt;/p&gt;

&lt;p&gt;I might start with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;FEATURE.md&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;support Stripe checkout&lt;/li&gt;
&lt;li&gt;keep existing order flow intact&lt;/li&gt;
&lt;li&gt;show clear errors&lt;/li&gt;
&lt;li&gt;do not refactor unrelated modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there I have two options.&lt;/p&gt;

&lt;p&gt;Fast path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reviewable path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once work is in motion, I can continue with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next session gets a prompt grounded in the existing state, not a vague memory of yesterday.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better continuity is not only about prompts
&lt;/h2&gt;

&lt;p&gt;One thing I like about the current shape of &lt;code&gt;handoff&lt;/code&gt; is that it is not only a prompt generator anymore.&lt;/p&gt;

&lt;p&gt;It also helps surface state.&lt;/p&gt;

&lt;p&gt;That is why commands like these matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;handoff status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handoff next&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handoff validate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They make the saved workflow visible instead of burying it inside one long prompt.&lt;/p&gt;

&lt;p&gt;That is important when you are trying to answer simple questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this feature ready for execution?&lt;/li&gt;
&lt;li&gt;What is the current step?&lt;/li&gt;
&lt;li&gt;Why is the workflow blocked?&lt;/li&gt;
&lt;li&gt;What command should I run next?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why local-first still matters
&lt;/h2&gt;

&lt;p&gt;I wanted this to work with any coding assistant.&lt;/p&gt;

&lt;p&gt;That meant the core could not depend on one provider, one editor, or one hosted workflow system.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;handoff&lt;/code&gt; stays local-first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown files in your repository&lt;/li&gt;
&lt;li&gt;prompt generation from a CLI&lt;/li&gt;
&lt;li&gt;no provider dependency in the core flow&lt;/li&gt;
&lt;li&gt;no cloud requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use it with ChatGPT today, Claude tomorrow, Copilot later, or another tool entirely.&lt;/p&gt;

&lt;p&gt;The workflow stays yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why repository context matters too
&lt;/h2&gt;

&lt;p&gt;Feature state is only part of the story.&lt;/p&gt;

&lt;p&gt;Repository context matters too.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt; are missing, stale, or too thin, AI sessions still waste time rediscovering the same project facts.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff init&lt;/code&gt; can flag missing high-value context, and why there is also a &lt;code&gt;handoff prompt context&lt;/code&gt; flow for improving repo-level guidance without writing application code.&lt;/p&gt;

&lt;p&gt;That may sound small, but it matters in practice.&lt;/p&gt;

&lt;p&gt;A feature plan works much better when the surrounding repository is legible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I still like the CLI model
&lt;/h2&gt;

&lt;p&gt;There is a lot of value in editor-native workflows, and I may support more of them over time.&lt;/p&gt;

&lt;p&gt;But the CLI model has one major strength: portability.&lt;/p&gt;

&lt;p&gt;The moment a workflow depends too heavily on one IDE, it becomes harder to reuse across tools, harder to debug, and harder to trust.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; keeps the source of truth in files you can inspect directly.&lt;/p&gt;

&lt;p&gt;That makes it easier to reason about, easier to version, and easier to carry across environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; is a good fit if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you build features across multiple AI sessions&lt;/li&gt;
&lt;li&gt;you switch between assistants or editors&lt;/li&gt;
&lt;li&gt;you want better task decomposition before coding&lt;/li&gt;
&lt;li&gt;you care about deterministic state and inspectable workflow&lt;/li&gt;
&lt;li&gt;you prefer local tools over hosted orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is probably not for you if you want a full IDE platform with built-in visual workflow management and heavy automation everywhere.&lt;/p&gt;

&lt;p&gt;That is fine. The tool is intentionally narrower than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; gives AI coding workflows a memory, a plan, and a deterministic continuation path without taking ownership of your editor or your repository.&lt;/p&gt;

&lt;p&gt;That is the whole idea.&lt;/p&gt;

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

&lt;p&gt;If you want to try the current default flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init my-feature
&lt;span class="c"&gt;# edit .handoff/current/FEATURE.md&lt;/span&gt;
handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff next
handoff status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to inspect planning in stages first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init my-feature
&lt;span class="c"&gt;# edit .handoff/current/FEATURE.md&lt;/span&gt;
handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/semiherdogan/handoff" rel="noopener noreferrer"&gt;handoff on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building with AI every day, you already know the pain this solves.&lt;/p&gt;

&lt;p&gt;The interesting part is not that the model needs context.&lt;/p&gt;

&lt;p&gt;It is that context needs structure.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Cloudflare Tunnel, Traefik, and a Cleaner Self-Hosted Setup</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:26:01 +0000</pubDate>
      <link>https://forem.com/semiherdogan/cloudflare-tunnel-traefik-and-a-cleaner-self-hosted-setup-546l</link>
      <guid>https://forem.com/semiherdogan/cloudflare-tunnel-traefik-and-a-cleaner-self-hosted-setup-546l</guid>
      <description>&lt;p&gt;Today I set up a small stack on my own server using Docker, Cloudflare Tunnel, and Traefik.&lt;/p&gt;

&lt;p&gt;The goal was simple: I wanted a cleaner way to expose and manage multiple projects without treating every new deployment like a one-off configuration job.&lt;/p&gt;

&lt;p&gt;The useful part was not just that it worked. It made the server feel like a reusable system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wanted this
&lt;/h2&gt;

&lt;p&gt;I like keeping small projects available online, but once there is more than one service involved, setup work starts repeating itself very quickly.&lt;/p&gt;

&lt;p&gt;You need routing, domain handling, HTTPS, container management, and some way to stop the server from becoming a pile of unrelated manual decisions.&lt;/p&gt;

&lt;p&gt;I wanted something that felt structured enough to grow, but still lightweight enough for personal projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker runs the services&lt;/li&gt;
&lt;li&gt;Traefik handles reverse proxying and routing&lt;/li&gt;
&lt;li&gt;Cloudflare Tunnel connects the server to the outside world without the usual direct exposure model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker gives each project a predictable runtime shape. Traefik gives those projects a consistent entry point. Cloudflare Tunnel removes a lot of the usual friction around exposing services safely.&lt;/p&gt;

&lt;p&gt;What makes the stack work is that the responsibilities are separated in a sensible way. Containers are responsible for running applications. Traefik is responsible for routing and service discovery. The tunnel is responsible for controlled external access. Once those boundaries are clear, the setup becomes easier to extend without each layer leaking too much into the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just keep it simpler?
&lt;/h2&gt;

&lt;p&gt;That is a fair question, because for one app, a simpler setup is often good enough. A straightforward reverse proxy config is not a problem when there is only one destination behind it.&lt;/p&gt;

&lt;p&gt;The problem starts when the server stops being "the place where one app runs" and becomes "the place where different projects may come and go over time."&lt;/p&gt;

&lt;p&gt;That is the point where I stop caring only about whether something works and start caring more about whether the structure will still feel reasonable after the fourth or fifth service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this starts to make sense
&lt;/h2&gt;

&lt;p&gt;With a single app, almost any setup can feel acceptable. A manual proxy config does not look too bad when there is only one target. But once there are multiple services, different hostnames, and the possibility of adding more over time, the difference becomes obvious.&lt;/p&gt;

&lt;p&gt;That is the point where Traefik stops feeling optional. It becomes the layer that gives the server a stable structure. Services become easier to reason about, routing stops feeling ad hoc, and adding another project starts to look like a repeatable step instead of a fresh configuration job.&lt;/p&gt;

&lt;p&gt;The most useful part was reduction in repeated decisions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;services run in containers&lt;/li&gt;
&lt;li&gt;routing is handled in one consistent way&lt;/li&gt;
&lt;li&gt;external access follows the same pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding another project no longer looks like "what do I need to invent for this one?" It looks more like "how does this fit into the structure that already exists?"&lt;/p&gt;

&lt;p&gt;That is a much better place to be, especially for side projects and internal tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What adding a new project looks like
&lt;/h2&gt;

&lt;p&gt;At a high level, adding a new project comes down to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;add a hostname rule in the Cloudflare Tunnel dashboard&lt;/li&gt;
&lt;li&gt;add Traefik labels to the project's &lt;code&gt;docker-compose.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the Cloudflare side, the rule is straightforward: point something like &lt;code&gt;test.semiherdogan.net&lt;/code&gt; to the Traefik entry point behind the tunnel.&lt;/p&gt;

&lt;p&gt;On the container side, the project can stay very small:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.enable=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.test.rule=Host(`test.semiherdogan.net`)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.test.entrypoints=web&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.services.test.loadbalancer.server.port=80&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The required shape is easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;join the shared proxy network&lt;/li&gt;
&lt;li&gt;declare the hostname in Traefik&lt;/li&gt;
&lt;li&gt;let the tunnel rule send traffic to that entry point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That repeatability matters more than the individual labels themselves. Once the naming and network pattern are stable, the cost of adding another service drops a lot because the shape of the problem is already known.&lt;/p&gt;

&lt;p&gt;Cloudflare Tunnel also changes the order of concerns in a useful way. Instead of thinking first about direct exposure, port handling, and the outer edge of the server, I can focus more on internal service organization and routing.&lt;/p&gt;

&lt;p&gt;That does not remove the need to think carefully, but it does remove some of the repetitive edge-work that usually makes self-hosting feel more fragile than it needs to be.&lt;/p&gt;

&lt;p&gt;It also gives me an easy path for the cases where a domain, subdomain, or internal endpoint should not be openly reachable. In those cases, Cloudflare Zero Trust fits naturally into the same setup and makes it straightforward to put a protected layer in front of something without rethinking the whole deployment model.&lt;/p&gt;

&lt;p&gt;It is easier to extend, easier to keep mentally organized, and much nicer to work with than a collection of separate, hand-made deployment paths.&lt;/p&gt;

&lt;p&gt;The interesting part is not the individual tools. It is that together they create a setup where future projects feel cheaper to add.&lt;/p&gt;

&lt;p&gt;And that is exactly the kind of infrastructure decision I tend to like: not flashy, but immediately useful once the number of projects starts growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I would be careful
&lt;/h2&gt;

&lt;p&gt;The main risk in setups like this is not usually the first deployment. It is slow operational drift.&lt;/p&gt;

&lt;p&gt;If hostnames, labels, networks, and entry points are not kept consistent, the "clean structure" benefit disappears surprisingly fast. The same is true if observability is left behind. Once multiple services share the same routing layer, it becomes more important to know what is failing, where requests are going, and which layer is actually responsible when something breaks.&lt;/p&gt;

&lt;p&gt;That is also why I like this stack more as a pattern than as a one-time setup. The value is not just that the first app is online. The value is that the second, third, and fourth app can follow the same rules without the server turning back into improvisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is probably too much
&lt;/h2&gt;

&lt;p&gt;If you only have one small app and do not expect that to change, this may be more structure than you actually need. In that case, a simpler setup might be the better engineering decision.&lt;/p&gt;

&lt;p&gt;What makes this stack appealing is not that it is universally better. It is that it starts paying off when you want a server to host multiple projects without every new service feeling like a fresh round of setup decisions.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>docker</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
    <item>
      <title>Devbox: The First Nix-Based Tool That Felt Practical</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:25:51 +0000</pubDate>
      <link>https://forem.com/semiherdogan/devbox-the-first-nix-based-tool-that-felt-practical-4mbb</link>
      <guid>https://forem.com/semiherdogan/devbox-the-first-nix-based-tool-that-felt-practical-4mbb</guid>
      <description>&lt;p&gt;Recently I started using &lt;a href="https://github.com/jetify-com/devbox" rel="noopener noreferrer"&gt;Devbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is one of those tools that made immediate sense to me because it sits exactly in the gap I have been feeling for a long time.&lt;/p&gt;

&lt;p&gt;Before that, my default workflow for trying a new idea was usually one of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install whatever I needed directly on my machine with Homebrew&lt;/li&gt;
&lt;li&gt;put the whole thing into Docker, even when the project was small&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches work, but neither one felt right all the time.&lt;/p&gt;

&lt;p&gt;Installing tools directly on my laptop is easy in the short term, but it slowly turns the machine into a place where random project decisions accumulate. A tool I needed once ends up sitting around forever. Version differences start to matter. The system becomes less intentional over time.&lt;/p&gt;

&lt;p&gt;Docker solves a different problem, and sometimes it is exactly the right answer. But for very small experiments, side projects, or quick idea testing, it can also feel heavier than what I actually need. Sometimes I do not want a containerized runtime model. I just want a clean development shell with the right tools available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I never fully committed to Nix
&lt;/h2&gt;

&lt;p&gt;I have wanted something like this for a long time, which is why Nix has always been interesting to me in theory.&lt;/p&gt;

&lt;p&gt;The promise is very attractive: reproducible environments, clean dependency boundaries, and less pollution on the host machine.&lt;/p&gt;

&lt;p&gt;The part that always stopped me was not the idea. It was the feeling that the configuration itself asked for more commitment than I wanted to give, especially for small projects. Every time I looked at a Nix-based setup directly, it felt like I needed to buy into a whole way of thinking before I could get the practical benefit I was actually after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Devbox clicked
&lt;/h2&gt;

&lt;p&gt;Devbox was the first tool in this space that felt like it reduced the activation energy enough for me to actually use it.&lt;/p&gt;

&lt;p&gt;The part I liked immediately is that it gives me the Nix-backed isolation I wanted, but through a much more approachable shape. Instead of feeling like I need to adopt the Nix language first, I can describe a project environment in a small JSON file and move on.&lt;/p&gt;

&lt;p&gt;Instead of thinking, "do I want to invest in learning this whole toolchain right now?", the question becomes much simpler: "do I want this project to have a clean shell with pinned tools?"&lt;/p&gt;

&lt;p&gt;That is a much easier yes.&lt;/p&gt;

&lt;p&gt;Jetify's own docs describe Devbox as a way to create isolated, reproducible development shells without needing Docker or the Nix language, and that matches the part that mattered most to me in practice. Sources: &lt;a href="https://www.jetify.com/devbox/docs" rel="noopener noreferrer"&gt;What is Devbox?&lt;/a&gt;, &lt;a href="https://github.com/jetify-com/devbox" rel="noopener noreferrer"&gt;Devbox GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;When I want to try a small project, I do not have to decide between making my laptop messier or creating more container setup than the project deserves. I can define the tools, enter the shell, and keep going.&lt;/p&gt;

&lt;p&gt;The flow is simple enough that it is easy to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;devbox init
devbox search hugo
devbox add hugo@0.159.0

devbox shell &lt;span class="c"&gt;# activate environment&lt;/span&gt;
hugo server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the config stays small:&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;"packages"&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;"hugo@0.159.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shell"&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;"scripts"&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;"dev"&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;"hugo server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"build"&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;"hugo --gc --minify"&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;That is what made it click for me. It gives me the boundary I wanted without forcing a heavier setup than the project deserves.&lt;/p&gt;

&lt;p&gt;It also makes cleanup feel more realistic. When the project is over, I do not need to remember which packages I threw onto my machine just to get through one afternoon of testing. The dependency boundary stays with the project.&lt;/p&gt;

&lt;p&gt;That is the part I appreciate most. It feels lighter than Docker for this category of work, but still much more intentional than installing everything globally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it feels worth keeping
&lt;/h2&gt;

&lt;p&gt;Some tools are impressive but never become habit.&lt;/p&gt;

&lt;p&gt;Devbox feels more promising to me because it fits into the kind of development work I actually do: small experiments, side projects, trying tools quickly, switching contexts often, and not wanting my machine to reflect every temporary decision I make.&lt;/p&gt;

&lt;p&gt;I still think Docker and plain host installs both have their place. This is not really about replacing everything with one tool.&lt;/p&gt;

&lt;p&gt;It is more that Devbox finally gave me a practical middle layer I had been missing for a long time.&lt;/p&gt;

&lt;p&gt;That is why it clicked.&lt;/p&gt;

</description>
      <category>devbox</category>
      <category>nix</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Simplifying EC2 Connections with AWS SSH</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:25:39 +0000</pubDate>
      <link>https://forem.com/semiherdogan/simplifying-ec2-connections-with-aws-ssh-370o</link>
      <guid>https://forem.com/semiherdogan/simplifying-ec2-connections-with-aws-ssh-370o</guid>
      <description>&lt;p&gt;As a developer working with AWS, I often found myself jumping between multiple EC2 instances. Connecting to them securely and efficiently was repetitive, and it usually meant bouncing between the AWS CLI and the Session Manager Plugin.&lt;/p&gt;

&lt;p&gt;Those tools are powerful, but I wanted something simpler and faster for day-to-day use. That is why I built AWS SSH, a CLI tool with a small TUI that streamlines the process of connecting to EC2 instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built AWS SSH
&lt;/h2&gt;

&lt;p&gt;The AWS CLI and Session Manager Plugin already solve the core problem, but the workflow can still feel clumsy if you switch between instances often.&lt;/p&gt;

&lt;p&gt;The main friction points for me were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually searching for instance IDs and typing long commands.&lt;/li&gt;
&lt;li&gt;Having no intuitive interface for picking the target instance.&lt;/li&gt;
&lt;li&gt;Needing extra steps whenever I wanted to switch regions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a tool that reduced that overhead and made the flow interactive instead of procedural.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AWS SSH works
&lt;/h2&gt;

&lt;p&gt;AWS SSH is a wrapper around the AWS CLI and Session Manager Plugin. It uses those existing tools and puts a more ergonomic interface on top.&lt;/p&gt;

&lt;p&gt;The basic flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It fetches the EC2 instances available in your AWS account.&lt;/li&gt;
&lt;li&gt;It shows them in a TUI so you can search and select the one you want.&lt;/li&gt;
&lt;li&gt;Once selected, it starts the connection through AWS Session Manager.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also supports region configuration. If you define regions in your AWS credentials file, the tool can search them automatically. If not, it asks you to pick a region at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before using AWS SSH, make sure these are installed and available in your &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can verify both with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
session-manager-plugin &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Download the latest release from the &lt;a href="https://github.com/semiherdogan/aws-ssh/releases" rel="noopener noreferrer"&gt;GitHub releases page&lt;/a&gt;, make it executable, and move it into your &lt;code&gt;PATH&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x aws-ssh
&lt;span class="nb"&gt;mv &lt;/span&gt;aws-ssh /usr/local/bin/aws-ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS, the first run may require allowing the binary from &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Run the tool like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws-ssh &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--profile&lt;/span&gt;|-p] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--region&lt;/span&gt;|-r] searchparam1 searchparam2 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens the interface, lets you filter the instance list, and then connects to the selected EC2 instance through AWS Session Manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A simple TUI for selecting EC2 instances.&lt;/li&gt;
&lt;li&gt;Search support for narrowing down instances quickly.&lt;/li&gt;
&lt;li&gt;Region configuration through your AWS credentials file.&lt;/li&gt;
&lt;li&gt;Optional profile selection with &lt;code&gt;--profile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring regions
&lt;/h2&gt;

&lt;p&gt;If you want the tool to search specific regions automatically, add a &lt;code&gt;regions&lt;/code&gt; key to the relevant section in your &lt;code&gt;.aws/credentials&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[default]&lt;/span&gt;
&lt;span class="py"&gt;aws_access_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;YOUR_ACCESS_KEY&lt;/span&gt;
&lt;span class="py"&gt;aws_secret_access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;YOUR_SECRET_KEY&lt;/span&gt;
&lt;span class="py"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;us-east-1,us-west-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, AWS SSH will search those regions without asking each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Required AWS permissions
&lt;/h2&gt;

&lt;p&gt;To run the tool, you need at least these IAM permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ec2:DescribeInstances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssm:StartSession&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example policy looks like this:&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;"Action"&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;"ec2:DescribeInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ssm:StartSession"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;If you want tighter access control, you can scope &lt;code&gt;Resource&lt;/code&gt; down to specific instances or tags.&lt;/p&gt;

&lt;p&gt;Also make sure SSM is enabled on the target EC2 instance, with the required IAM role attached and the SSM agent installed and running. AWS documents that setup here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-prerequisites.html" rel="noopener noreferrer"&gt;Session Manager prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this tool is useful
&lt;/h2&gt;

&lt;p&gt;AWS SSH is meant to remove friction from a workflow that developers repeat constantly. If you manage one instance it is convenient. If you manage many, it becomes much more valuable.&lt;/p&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/semiherdogan/aws-ssh" rel="noopener noreferrer"&gt;AWS SSH repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI installation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin installation guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>cli</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
