<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Huỳnh Lê Nhất Nghĩa</title>
    <description>The latest articles on DEV Community by Huỳnh Lê Nhất Nghĩa (@nghiadaulau).</description>
    <link>https://dev.to/nghiadaulau</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1175626%2Fb7a7c231-e0f8-4785-88f4-798a38f3722e.jpg</url>
      <title>DEV Community: Huỳnh Lê Nhất Nghĩa</title>
      <link>https://dev.to/nghiadaulau</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nghiadaulau"/>
    <language>en</language>
    <item>
      <title>A security merge gate is only as good as its false positive rate</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Thu, 16 Jul 2026 10:06:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/a-security-merge-gate-is-only-as-good-as-its-false-positive-rate-4d1p</link>
      <guid>https://dev.to/aws-builders/a-security-merge-gate-is-only-as-good-as-its-false-positive-rate-4d1p</guid>
      <description>&lt;p&gt;A hard gate on merge is the right instinct. Security findings are cheapest when they interrupt a change before it becomes shared state, before other branches build on it, before it ships. Blocking the merge is the correct place for that class of control.&lt;/p&gt;

&lt;p&gt;But there is a failure mode that is worse than having no gate at all: a gate that blocks too eagerly. The first time it flags something that is obviously fine, people start looking for the bypass flag. The second time, they add it to their muscle memory. After that the gate is theater, and the one real finding it catches next quarter gets waved through with everything else.&lt;/p&gt;

&lt;p&gt;So the interesting engineering problem is not "can we block a merge on a security finding." That part is easy. The problem is staying accurate enough that nobody wants to route around you.&lt;/p&gt;

&lt;p&gt;Here is how we think about it on Synapse.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F700yo8bjti8l7q2mkgmt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F700yo8bjti8l7q2mkgmt.png" alt="The merge gate: request-changes on a secret-hygiene finding, then approve and merge once it is fixed" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The gate blocked a real pull request until an embedded-credential git URL was rejected, then let it through.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate has to be explainable
&lt;/h2&gt;

&lt;p&gt;A finding that blocks a merge carries four things by construction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what failed (a rule id and a CWE),&lt;/li&gt;
&lt;li&gt;why it matters (a short rationale),&lt;/li&gt;
&lt;li&gt;the evidence it used,&lt;/li&gt;
&lt;li&gt;and the exact change that makes it pass (a compliant example).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a dependency finding the evidence is the scanner and the advisory database version. For a static analysis finding it is the matched pattern or the data flow, at a specific file and line. The evidence is hash chained, so a finding cannot be quietly edited after the fact and still pass the report gate.&lt;/p&gt;

&lt;p&gt;The goal is that a developer never has to guess why the gate fired or argue with a black box. If the gate cannot tell you what to change, it has not earned the right to block you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precision is a release gate for the rules, not just for your code
&lt;/h2&gt;

&lt;p&gt;This is the part most tools get wrong, and it is the part we care about most.&lt;/p&gt;

&lt;p&gt;Our deterministic detectors (dependency scanning, pattern static analysis, taint, secrets, misconfiguration) publish findings with no model in the path. Same input, same finding, every time. There is no AI triage step softening the output. That is a feature, because it makes the gate reproducible and auditable. It is also a constraint, because it means a false positive prone rule is not a nuisance. It is a bug in the tool.&lt;/p&gt;

&lt;p&gt;A concrete example. We were reviewing a community pull request that added a rule pack for Jupyter and IPython notebooks. Fifteen new rules, clean build, all tests green. Two of the rules looked completely reasonable on paper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One flagged &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; markup saved in a notebook's output cell. Reasonable sounding, since script markup in output is an XSS smell.&lt;/li&gt;
&lt;li&gt;One flagged local filesystem paths like &lt;code&gt;/home/alice/...&lt;/code&gt; saved in a traceback, since those can disclose environment structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both would have been a disaster on real notebooks.&lt;/p&gt;

&lt;p&gt;Plotly, Bokeh, ipywidgets, and Altair all embed &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags in their normal chart output. Every interactive plot in every data science notebook would have tripped the first rule. And every saved Python exception contains &lt;code&gt;site-packages&lt;/code&gt; frames under a home directory, so the second rule would fire on essentially any notebook that ever caught an error.&lt;/p&gt;

&lt;p&gt;Neither rule is wrong in theory. Both are wrong in practice, because they cry wolf on ordinary code. So we blocked the merge until they were tightened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The script rule now requires a real injection signal in the markup, like an event handler attribute, &lt;code&gt;document.write(&lt;/code&gt;, or &lt;code&gt;eval(&lt;/code&gt;, not just the presence of a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;The traceback rule now excludes &lt;code&gt;site-packages&lt;/code&gt;, &lt;code&gt;dist-packages&lt;/code&gt;, and virtualenv frames, and fires only on a genuine project path.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp10wu71w2gk7upg9ci83.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp10wu71w2gk7upg9ci83.png" alt="The tightened rules plus regression tests: Plotly and Bokeh output no longer flagged, real injection and real project paths still flagged" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The rule count stayed the same. The false positive rate went to near zero, with regression tests to keep it there.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The bar we hold is simple: if a rule fires on ordinary, safe code, it does not ship. Precision is a gate the rules have to pass before they are allowed to gate anyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI generated code and the shared blind spot
&lt;/h2&gt;

&lt;p&gt;A growing share of pull requests are agent generated now, and they read clean at a glance. The dangerous case is not the code that looks sloppy. It is the code where the same agent also wrote the tests, so the suite inherits the exact blind spot the implementation has. Everything is green, and the vulnerable pattern sails through.&lt;/p&gt;

&lt;p&gt;This is why the gate is pattern and data flow based, and deliberately independent of the test suite rather than derived from it. If the tests missed it because they shared the author's assumptions, the gate still does not. A gate that trusts the tests is only as good as whoever wrote the tests, which increasingly is the same model that wrote the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the model propose, do not let it decide
&lt;/h2&gt;

&lt;p&gt;We do use models. We just do not let them block a merge on their own opinion.&lt;/p&gt;

&lt;p&gt;Any analytical claim, whether it is reachability, a risk narrative, or a suspected exploit, is a proposal. It cannot gate anything until a separate, deterministic verifier seals a verdict over a threshold. A confident but wrong model output never becomes a merge blocker, because it never gets to be the one deciding.&lt;/p&gt;

&lt;p&gt;For dependency findings, reachability and taint analysis decide whether a vulnerable symbol is actually reachable in your code before the finding escalates. "It exists somewhere in the tree" is not automatically a hard stop. That alone removes a large slice of the noise that makes teams resent dependency scanners.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate stays green because it is boring
&lt;/h2&gt;

&lt;p&gt;None of this works if the checks are flaky. The detectors are plain, deterministic Go: build with and without CGO, vet, and a fast test suite that runs on every change. Reproducible checks are what let you trust a red result enough to block on it.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5h3ddl16atsfkj6jyiof.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5h3ddl16atsfkj6jyiof.png" alt="Build, vet, and the project test suite all passing" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Deterministic checks: same input, same result, fast enough to run on every pull request.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet part
&lt;/h2&gt;

&lt;p&gt;Catching real issues is table stakes. Any scanner can produce findings. The actual engineering work is in the negative space: not firing on the Plotly chart, not firing on the site-packages traceback, not blocking a merge over a vulnerable function nobody calls.&lt;/p&gt;

&lt;p&gt;A gate earns trust by being right, being specific about the fix, and being quiet when there is nothing wrong. Get that part right and people stop trying to route around it, because there is nothing to route around. Get it wrong and it does not matter how good your detection is, because the gate everyone silences is the same as no gate at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Come contribute
&lt;/h2&gt;

&lt;p&gt;Synapse is open source (Apache-2.0): &lt;strong&gt;&lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;github.com/KKloudTarus/synapse-ce&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Star it if the "AI proposes, Go and a second model confirm" direction is one you want to follow.&lt;/li&gt;
&lt;li&gt;Run it against your own LLM endpoint and tell me what happened: which model refutes well, which one likes to make things up. Feedback from a real model is the most useful kind.&lt;/li&gt;
&lt;li&gt;The verifier's adversarial prompt, the &lt;code&gt;driver&lt;/code&gt; vocabulary, the consensus threshold: it all lives in &lt;code&gt;internal/usecase/fptriage&lt;/code&gt;, and it's short and easy to read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you turn this on in your own repo, drop a comment with how many false positives the AI held back on the first run, and whether it ever came close to refuting something that turned out to be real. I'm curious.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>contributorswanted</category>
      <category>go</category>
    </item>
    <item>
      <title>Let an AI clear out your false positives without letting it hide a real bug</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Sun, 12 Jul 2026 11:07:40 +0000</pubDate>
      <link>https://dev.to/aws-builders/let-an-ai-clear-out-your-false-positives-without-letting-it-hide-a-real-bug-1akl</link>
      <guid>https://dev.to/aws-builders/let-an-ai-clear-out-your-false-positives-without-letting-it-hide-a-real-bug-1akl</guid>
      <description>&lt;p&gt;Last time I wrote about wiring up a security gate that blocks merges in CI with &lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;Synapse&lt;/a&gt;. Someone left a comment that hit the exact sore spot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The gate is the easy part. Two weeks in, the team turns it off because it's red over false positives every single time."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yep. A gate that cries wolf too often dies on its own. People start hitting "merge anyway," and by the time a real vulnerability shows up, nobody's looking anymore.&lt;/p&gt;

&lt;p&gt;The obvious 2020s fix is to hand the whole pile to an LLM and say "delete the junk." Except: the moment a security tool lets an AI &lt;em&gt;delete&lt;/em&gt; findings, you've just built a brand new vulnerability. The model guesses wrong once, a real SQL injection quietly drops off the report, and nobody's the wiser.&lt;/p&gt;

&lt;p&gt;So Synapse does it differently. The AI is allowed to &lt;em&gt;propose&lt;/em&gt; that a finding is a false positive. It is never allowed to confirm that on its own, and it is never allowed to delete anything. This whole post is a real run. Terminal output at the bottom, nothing faked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the actual "gate" is
&lt;/h2&gt;

&lt;p&gt;Three rules, and all three exist so you can trust the output:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The model only proposes.&lt;/strong&gt; It doesn't return prose, and it doesn't return a delete command. It returns exactly one typed verdict: &lt;code&gt;verdict&lt;/code&gt; is one of &lt;code&gt;refuted | sound | uncertain&lt;/code&gt;, &lt;code&gt;driver&lt;/code&gt; is a closed snake_case token (not a sentence), and &lt;code&gt;confidence&lt;/code&gt; is 0–100. That grammar is validated in Go. The model can hallucinate a whole paragraph of reasoning and none of it reaches the report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A second, &lt;em&gt;different&lt;/em&gt; model has to agree.&lt;/strong&gt; When the first model proposes "refuted" with confidence ≥ 75, a separate verifier model gets called to assess the same finding independently. The refutation only stands if the verifier also says "refuted" at ≥ 75. And the verifier is prompted adversarially. Its job is to &lt;em&gt;stop a real bug from being waved away&lt;/em&gt;. This is the CLI version of the rule Synapse enforces on the server: a claim is only confirmed by a &lt;em&gt;different&lt;/em&gt; reviewer's sealed verdict. No confirming your own work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Nothing gets deleted.&lt;/strong&gt; A "refuted" finding is retain-and-mark: it stays in the report, it stays evidence-sealed, it still shows up in the compliance table. It's just exempt from the gate's exit code. The worst a wrong verdict can do is let one finding skip &lt;code&gt;--fail-on&lt;/code&gt;. It can never make a finding disappear.&lt;/p&gt;

&lt;p&gt;One more thing, because it matters for the cost: this is the &lt;em&gt;second&lt;/em&gt; layer. Before any model runs, a deterministic scope classifier already strips the obvious noise: test files, fixtures, that sort of thing. The model only gets the harder calls: is this sink actually attacker-controlled? Is that interpolated value a constant or user input? Model calls cost tokens, so they only run on production-scope, first-party source findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it on
&lt;/h2&gt;

&lt;p&gt;It's opt-in. Four environment variables:&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;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_FP_TRIAGE_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;                  &lt;span class="c"&gt;# turn on AI false-positive triage&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:20128/v1  &lt;span class="c"&gt;# any OpenAI-compatible endpoint&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_FP_TRIAGE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;proposer-model&amp;gt;        &lt;span class="c"&gt;# a cheap/fast model to propose&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_VERIFIER_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;different-model&amp;gt;         &lt;span class="c"&gt;# a DIFFERENT model to verify (two-model consensus)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SYNAPSE_LLM_BASE_URL&lt;/code&gt; is a plain OpenAI-style endpoint (&lt;code&gt;/v1/chat/completions&lt;/code&gt;). Run it locally with Ollama or vLLM, or point it at OpenAI. Doesn't matter. Add &lt;code&gt;SYNAPSE_LLM_API_KEY&lt;/code&gt; if yours needs a key.&lt;/li&gt;
&lt;li&gt;If you &lt;em&gt;don't&lt;/em&gt; set &lt;code&gt;SYNAPSE_VERIFIER_MODEL&lt;/code&gt; (or you set it to the same model), you get single-model mode. It still works, but the real gate is the second model being &lt;em&gt;different&lt;/em&gt;. Set it to the proposer and Synapse ignores it. That's not a verifier.&lt;/li&gt;
&lt;li&gt;The bar is 75, the same threshold every other AI claim in Synapse has to clear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you run the exact same scan command as always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synapse-cli scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I built a small service with &lt;strong&gt;both real bugs and deliberate false positives&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;payments/store.py&lt;/code&gt;: a SQL query built with &lt;code&gt;db.session.execute(f"... {account_id}")&lt;/code&gt; where &lt;code&gt;account_id&lt;/code&gt; comes straight from &lt;code&gt;request.args&lt;/code&gt;. That's a &lt;strong&gt;real SQL injection&lt;/strong&gt;. Same file has a &lt;code&gt;hashlib.md5(...)&lt;/code&gt; call used as a &lt;strong&gt;cache key for a static asset&lt;/strong&gt;. The weak-hash rule flags it, but it has nothing to do with security.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payments/settings.py&lt;/code&gt;: a hardcoded AWS access key. &lt;strong&gt;Real&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reports/ledger.py&lt;/code&gt;: another f-string SQL query, but the interpolated value is &lt;code&gt;PERIOD&lt;/code&gt;, a &lt;strong&gt;server-side constant&lt;/strong&gt;, not user input. The pattern matcher flags every f-string SQL, so this is a textbook &lt;strong&gt;high-severity false positive&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Before: no AI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;synapse-cli scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high
&lt;span class="go"&gt;
Synapse SCA dogfood – payments-service
  vulnerabilities: 0
  findings (promoted): 7
    critical  risk  0.00  Hardcoded AWS access key id (payments/settings.py:4)
    high      risk  0.00  SQL execution uses dynamic string construction (payments/store.py:14)
    high      risk  0.00  Python SQLAlchemy/raw SQL uses dynamic string construction (payments/store.py:14)
    medium    risk  0.00  Weak hash: MD5 (payments/store.py:21)
    high      risk  0.00  SQL execution uses dynamic string construction (reports/ledger.py:13)
    high      risk  0.00  Python SQLAlchemy/raw SQL uses dynamic string construction (reports/ledger.py:13)
    high      risk  0.00  AWS access key ID (payments/settings.py:4)

synapse-cli: 6 finding(s) at or above high
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gate is red over &lt;strong&gt;6 findings at or above high&lt;/strong&gt;. Two of them (the SQL in &lt;code&gt;reports/ledger.py&lt;/code&gt;) are false alarms you'd have to triage by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: AI on, two models
&lt;/h3&gt;

&lt;p&gt;The proposer and the verifier are &lt;strong&gt;two different models&lt;/strong&gt; on my endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_FP_TRIAGE_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_FP_TRIAGE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt-5.4-mini      &lt;span class="c"&gt;# proposer&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SYNAPSE_VERIFIER_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt-5.4            &lt;span class="c"&gt;# verifier (a DIFFERENT model)&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;synapse-cli scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high
&lt;span class="go"&gt;
synapse-cli: AI false-positive triage (gpt-5.4-mini, verified by gpt-5.4): critiqued 7 finding(s), 3 suspected false positive(s) held back from the gate

Synapse SCA dogfood – payments-service
&lt;/span&gt;&lt;span class="gp"&gt;  findings (promoted): 7            #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;- still 7, nothing deleted
&lt;span class="c"&gt;  ...
&lt;/span&gt;&lt;span class="go"&gt;  compliance: Synapse AppSec Baseline v1.0 – 3/7 controls passing
    [FAIL] SAB-INJ-1  No injection weaknesses ...
           - SQL ... (payments/store.py:14)
&lt;/span&gt;&lt;span class="gp"&gt;           - SQL ... (reports/ledger.py:13)     #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;- the FP still shows &lt;span class="k"&gt;in &lt;/span&gt;compliance
&lt;span class="c"&gt;    ...
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;synapse-cli: 2 suspected false positive(s) at or above high held back from the gate by AI triage (reported with a verdict;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;not deleted&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;synapse-cli: 4 finding(s) at or above high
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read those lines closely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;critiqued 7 finding(s), 3 suspected false positive(s) held back&lt;/code&gt;: the AI looked at 7 findings and proposed 3 as false.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2 ... at or above high held back ... (reported with a verdict; not deleted)&lt;/code&gt;: two of those were high-severity, held back from the gate but &lt;strong&gt;not deleted&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;4 finding(s) at or above high&lt;/code&gt;: the 4 real bugs still count. Gate is still &lt;strong&gt;red&lt;/strong&gt; (&lt;code&gt;exit 1&lt;/code&gt;). The AI couldn't bury a real bug.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;findings (promoted): 7&lt;/code&gt; is unchanged, and the false positives are still sitting in the compliance table. Nothing left the report.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verdict by verdict
&lt;/h3&gt;

&lt;p&gt;This is my favorite part. With &lt;code&gt;--json&lt;/code&gt;, every finding carries the AI's verdict next to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[critical] Hardcoded AWS access key id (settings.py:4)
    verdict=sound    confidence=98   suspected_fp=false   driver=hardcoded_secret
[high]     SQL execution dynamic string (store.py:14)
    verdict=sound    confidence=99   suspected_fp=false   driver=confirmed_by_review
[high]     SQLAlchemy raw SQL dynamic (store.py:14)
    verdict=sound    confidence=99   suspected_fp=false   driver=dynamic_string_sql
[high]     AWS access key ID (settings.py:4)
    verdict=sound    confidence=99   suspected_fp=false   driver=literal
[medium]   Weak hash: MD5 (store.py:21)
    verdict=refuted  confidence=96   suspected_fp=true    driver=unspecified_refutation
[high]     SQL execution dynamic string (ledger.py:13)
    verdict=refuted  confidence=99   suspected_fp=true    driver=constant_literal
[high]     SQLAlchemy raw SQL dynamic (ledger.py:13)
    verdict=refuted  confidence=98   suspected_fp=true    driver=constant_literal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the &lt;code&gt;driver&lt;/code&gt; column. For &lt;code&gt;reports/ledger.py&lt;/code&gt; the model worked out on its own that the interpolated value is a &lt;code&gt;constant_literal&lt;/code&gt;, a constant, not input. For &lt;code&gt;store.py&lt;/code&gt; it's &lt;code&gt;sound&lt;/code&gt;: real bug, left alone. And &lt;code&gt;suspected_fp=true&lt;/code&gt; is the only thing that exempts a finding from the gate. The key detail: because I configured a &lt;em&gt;different&lt;/em&gt; verifier (the log literally says &lt;code&gt;verified by gpt-5.4&lt;/code&gt;), a finding only gets &lt;code&gt;suspected_fp=true&lt;/code&gt; when &lt;strong&gt;both&lt;/strong&gt; models refute it at ≥ 75. Drop the verifier and these exact three refutations still have to clear a second model to stand.&lt;/p&gt;

&lt;p&gt;End result: you're left with 4 real problems to fix instead of 6-with-2-fake mixed in. And more importantly, not one real bug got dimmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But what if the AI is wrong?"
&lt;/h2&gt;

&lt;p&gt;Right question. The whole design is built around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI wrongly refutes a real bug?&lt;/strong&gt; The verifier is a different model, prompted to push back, and it has to agree before the refutation counts. If the verifier disagrees, is unsure, or errors out, the finding &lt;strong&gt;still counts toward the gate&lt;/strong&gt; (fail-safe). Even if both models are wrong, the finding is still sitting in the report with its verdict attached. You can see it, it didn't vanish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model times out / endpoint dies?&lt;/strong&gt; That critique becomes "uncertain" and the finding gates normally. Triage is best-effort and &lt;strong&gt;never fails the scan&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model tries to smuggle prose into the report?&lt;/strong&gt; &lt;code&gt;driver&lt;/code&gt; is forced into a closed token grammar, &lt;code&gt;verdict&lt;/code&gt; has to be from a closed vocabulary, anything else is rejected. No LLM gets prose into the report path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put another way: the worst a bad verdict can do is exempt one finding from the exit code while it stays visible in the report. Not hide a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into CI
&lt;/h2&gt;

&lt;p&gt;Nothing about the workflow from the last post changes. Add four environment variables to the scan step and you're done:&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;Synapse scan (SARIF + gate on high)&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_FP_TRIAGE_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_LLM_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LLM_BASE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_LLM_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LLM_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_FP_TRIAGE_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-proposer-model"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_VERIFIER_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-verifier-model"&lt;/span&gt;   &lt;span class="c1"&gt;# different from the proposer&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse-cli scan . --sarif --fail-on high &amp;gt; synapse.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gate stays as deterministic as before. The pile of false alarms just stops eroding the team's trust in it. And since nothing gets deleted, the SARIF you push to Code scanning is still complete; the false positives are only marked, not dropped.&lt;/p&gt;

&lt;p&gt;One honest caveat: this is a noise filter, not a replacement for a human. It gets you looking at the 4 real problems instead of 6 muddled ones. You still have to fix those 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Come contribute
&lt;/h2&gt;

&lt;p&gt;Synapse is open source (Apache-2.0): &lt;strong&gt;&lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;github.com/KKloudTarus/synapse-ce&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Star it if the "AI proposes, Go and a second model confirm" direction is one you want to follow.&lt;/li&gt;
&lt;li&gt;Run it against your own LLM endpoint and tell me what happened: which model refutes well, which one likes to make things up. Feedback from a real model is the most useful kind.&lt;/li&gt;
&lt;li&gt;The verifier's adversarial prompt, the &lt;code&gt;driver&lt;/code&gt; vocabulary, the consensus threshold: it all lives in &lt;code&gt;internal/usecase/fptriage&lt;/code&gt;, and it's short and easy to read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you turn this on in your own repo, drop a comment with how many false positives the AI held back on the first run, and whether it ever came close to refuting something that turned out to be real. I'm curious.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>contributorswanted</category>
      <category>go</category>
    </item>
    <item>
      <title>Synapse Feature Demo: Deterministic Software Composition Analysis in Action</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Sat, 11 Jul 2026 10:49:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/synapse-feature-demo-deterministic-software-composition-analysis-in-action-el6</link>
      <guid>https://dev.to/aws-builders/synapse-feature-demo-deterministic-software-composition-analysis-in-action-el6</guid>
      <description>&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;I've just added a short demo video to the Synapse project to showcase how the platform performs deterministic Software Composition Analysis (SCA) from start to finish.&lt;/p&gt;

&lt;p&gt;In this demo, you'll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Importing a project for analysis&lt;/li&gt;
&lt;li&gt;SBOM generation&lt;/li&gt;
&lt;li&gt;Dependency and vulnerability analysis&lt;/li&gt;
&lt;li&gt;Evidence collection&lt;/li&gt;
&lt;li&gt;Findings presented through the web interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of Synapse is to provide a deterministic-first approach to application security, where every finding is backed by reproducible evidence rather than opaque scanning results.&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;li&gt;Scan workflow&lt;/li&gt;
&lt;li&gt;Dashboard design&lt;/li&gt;
&lt;li&gt;Missing capabilities&lt;/li&gt;
&lt;li&gt;Features you'd like to see next&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're interested in application security, DevSecOps, or open-source security tooling, your feedback would be greatly appreciated.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;https://github.com/KKloudTarus/synapse-ce&lt;/a&gt;&lt;br&gt;
Youtube: &lt;a href="https://www.youtube.com/watch?v=rUdt52em2QQ" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=rUdt52em2QQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contributions are always welcome! Whether you're interested in backend development, frontend improvements, documentation, parser support, or security research, feel free to open an issue or submit a pull request.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to watch the demo, and I hope it gives you a better idea of where Synapse is heading.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>contributorswanted</category>
    </item>
    <item>
      <title>Block the merge when a PR ships a vulnerability: a CI security gate with Synapse</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Wed, 08 Jul 2026 16:07:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/block-the-merge-when-a-pr-ships-a-vulnerability-a-ci-security-gate-with-synapse-33bn</link>
      <guid>https://dev.to/aws-builders/block-the-merge-when-a-pr-ships-a-vulnerability-a-ci-security-gate-with-synapse-33bn</guid>
      <description>&lt;p&gt;I have a simple rule when I work with a team: if a vulnerability makes it into &lt;code&gt;main&lt;/code&gt;, the process already failed somewhere upstream. The right place to catch it is on the pull request, before anyone clicks merge.&lt;/p&gt;

&lt;p&gt;This post is how I built exactly that gate with &lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;Synapse&lt;/a&gt;, an open source security scanner written in Go. The end result: open a PR that introduces a vulnerability and CI goes red, a comment lands on the PR telling you what to fix and how, the results show up in the GitHub Code scanning tab, and the merge button stays locked until the issue is dealt with.&lt;/p&gt;

&lt;p&gt;Everything below actually runs. There is a public demo repo linked at the end so you can see it, not just read about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why another tool
&lt;/h2&gt;

&lt;p&gt;Fair question. Trivy, Grype and OSV-Scanner are all good and I still use them.&lt;/p&gt;

&lt;p&gt;What is different about Synapse is not "it finds more CVEs". Detection is basically a commodity now, since every tool pulls from the same handful of data sources. The difference is the governance layer around a finding: a hash chained chain of custody so results cannot be edited quietly, a separation between who proposes and who confirms for the AI assisted parts, and a report path with no LLM in it. In short, it is built for the case where you have to prove a result, not just print one.&lt;/p&gt;

&lt;p&gt;But this post is not about philosophy. It is about one concrete job: blocking the merge.&lt;/p&gt;

&lt;p&gt;One thing I like: &lt;code&gt;synapse-cli&lt;/code&gt; runs with no database and no server. It is a single static Go binary. It shells out to &lt;code&gt;syft&lt;/code&gt; to build the SBOM and to &lt;code&gt;grype&lt;/code&gt; as one detection source, and both of those are single static binaries too. That is the same footprint you already know from running Trivy in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;Three binaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;synapse-cli&lt;/code&gt; (built from source, see the workflow below)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;syft&lt;/code&gt; (builds the SBOM)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grype&lt;/code&gt; (one offline detection source)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Postgres, no external service. If the runner has network access it also uses OSV.dev as a second source. If you are air gapped, add the &lt;code&gt;--offline&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;The gate itself is one flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synapse-cli scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a non zero exit code if there is any finding at or above high, and the important part is that it counts across every kind: dependency vulnerabilities (SCA), issues in your own source code (SAST), hardcoded secrets, and misconfigurations in Dockerfiles, Kubernetes, Helm and Terraform. A leaked API key in the code blocks the merge just like a CVE does.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;--sarif&lt;/code&gt; to emit a SARIF 2.1.0 report for the GitHub Code scanning tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synapse-cli scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--sarif&lt;/span&gt; &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; synapse.sarif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SARIF is written to stdout before the gate sets the exit code, so the file is complete even when the command returns 1. A single run both annotates the PR and blocks the merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full workflow
&lt;/h2&gt;

&lt;p&gt;Here is the &lt;code&gt;.github/workflows/synapse.yml&lt;/code&gt; I use in the demo repo. It runs the scan, gates on high, uploads SARIF, and posts a remediation report on the PR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Synapse Security Scan&lt;/span&gt;

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

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;   &lt;span class="c1"&gt;# to upload SARIF&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;      &lt;span class="c1"&gt;# to comment on the PR&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v5&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;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.26.4'&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;Install syft + grype&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p "$HOME/.local/bin"&lt;/span&gt;
          &lt;span class="s"&gt;echo "$HOME/.local/bin" &amp;gt;&amp;gt; "$GITHUB_PATH"&lt;/span&gt;
          &lt;span class="s"&gt;curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh  | sh -s -- -b "$HOME/.local/bin"&lt;/span&gt;
          &lt;span class="s"&gt;curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b "$HOME/.local/bin"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build synapse-cli&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GOTOOLCHAIN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git clone --depth 1 https://github.com/KKloudTarus/synapse-ce.git /tmp/synapse-ce&lt;/span&gt;
          &lt;span class="s"&gt;cd /tmp/synapse-ce&lt;/span&gt;
          &lt;span class="s"&gt;go build -o "$HOME/.local/bin/synapse-cli" ./cmd/synapse-cli&lt;/span&gt;

      &lt;span class="c1"&gt;# The gate. The JSON result feeds both the exit code and the PR report.&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;Synapse scan (gate on high)&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scan&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_MISCONFIG_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_SAST_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_SECRET_SCAN_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse-cli scan . --json --fail-on high &amp;gt; synapse.json&lt;/span&gt;

      &lt;span class="c1"&gt;# SARIF for the Code scanning tab. The gate already ran, so ignore the exit here.&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;Synapse scan (SARIF for code scanning)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_MISCONFIG_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_SAST_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;SYNAPSE_SECRET_SCAN_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse-cli scan . --sarif &amp;gt; synapse.sarif || &lt;/span&gt;&lt;span class="kc"&gt;true&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;Upload SARIF to code scanning&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&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;github/codeql-action/upload-sarif@v3&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;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;synapse.sarif&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;Post report on the PR&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always() &amp;amp;&amp;amp; github.event_name == 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
          &lt;span class="na"&gt;PR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.number }}&lt;/span&gt;
          &lt;span class="na"&gt;GATE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.scan.outcome }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;crit=$(jq '[.findings[]|select(.Severity=="critical")]|length' synapse.json)&lt;/span&gt;
          &lt;span class="s"&gt;high=$(jq '[.findings[]|select(.Severity=="high")]|length' synapse.json)&lt;/span&gt;
          &lt;span class="s"&gt;med=$(jq '[.findings[]|select(.Severity=="medium")]|length' synapse.json)&lt;/span&gt;
          &lt;span class="s"&gt;low=$(jq '[.findings[]|select(.Severity=="low" or .Severity=="info")]|length' synapse.json)&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;echo "## Synapse security scan"&lt;/span&gt;
            &lt;span class="s"&gt;echo&lt;/span&gt;
            &lt;span class="s"&gt;if [ "$GATE" = "failure" ]; then&lt;/span&gt;
              &lt;span class="s"&gt;echo "**Result: FAILED.** Findings at or above high severity. The merge is blocked until they are resolved."&lt;/span&gt;
            &lt;span class="s"&gt;else&lt;/span&gt;
              &lt;span class="s"&gt;echo "**Result: PASSED.** No findings at or above high severity."&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
            &lt;span class="s"&gt;echo&lt;/span&gt;
            &lt;span class="s"&gt;echo "| Severity | Count |"&lt;/span&gt;
            &lt;span class="s"&gt;echo "|---|---|"&lt;/span&gt;
            &lt;span class="s"&gt;echo "| Critical | $crit |"&lt;/span&gt;
            &lt;span class="s"&gt;echo "| High | $high |"&lt;/span&gt;
            &lt;span class="s"&gt;echo "| Medium | $med |"&lt;/span&gt;
            &lt;span class="s"&gt;echo "| Low | $low |"&lt;/span&gt;
            &lt;span class="s"&gt;echo&lt;/span&gt;
            &lt;span class="s"&gt;if [ "$(jq '[.vulnerabilities[].Component]|unique|length' synapse.json)" -gt 0 ]; then&lt;/span&gt;
              &lt;span class="s"&gt;echo "### Dependencies to upgrade"&lt;/span&gt;
              &lt;span class="s"&gt;echo&lt;/span&gt;
              &lt;span class="s"&gt;echo "Edit the manifest and bump each package to at least the version in the \"Upgrade to\" column."&lt;/span&gt;
              &lt;span class="s"&gt;echo&lt;/span&gt;
              &lt;span class="s"&gt;echo "| Package | Current | Upgrade to | CVEs | Top severity |"&lt;/span&gt;
              &lt;span class="s"&gt;echo "|---|---|---|---|---|"&lt;/span&gt;
              &lt;span class="s"&gt;jq -r '&lt;/span&gt;
                &lt;span class="s"&gt;.vulnerabilities | group_by(.Component)&lt;/span&gt;
                &lt;span class="s"&gt;| map({comp:.[0].Component, ver:.[0].Version,&lt;/span&gt;
                       &lt;span class="s"&gt;fix:((map(.FixedVersion)|map(select(.!=null and .!=""))|max) // "no fix yet"),&lt;/span&gt;
                       &lt;span class="s"&gt;n:length,&lt;/span&gt;
                       &lt;span class="s"&gt;sev:(if any(.[];.Severity=="critical") then "critical"&lt;/span&gt;
                            &lt;span class="s"&gt;elif any(.[];.Severity=="high") then "high"&lt;/span&gt;
                            &lt;span class="s"&gt;elif any(.[];.Severity=="medium") then "medium" else "low" end)})&lt;/span&gt;
                &lt;span class="s"&gt;| sort_by(.n) | reverse&lt;/span&gt;
                &lt;span class="s"&gt;| .[] | "| \(.comp) | \(.ver) | &amp;gt;= \(.fix) | \(.n) | \(.sev) |"' synapse.json&lt;/span&gt;
              &lt;span class="s"&gt;echo&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
            &lt;span class="s"&gt;if [ "$(jq '[.findings[]|select(.Kind!="sca")]|length' synapse.json)" -gt 0 ]; then&lt;/span&gt;
              &lt;span class="s"&gt;echo "### Code and config"&lt;/span&gt;
              &lt;span class="s"&gt;echo&lt;/span&gt;
              &lt;span class="s"&gt;jq -r '.findings[] | select(.Kind != "sca")&lt;/span&gt;
                &lt;span class="s"&gt;| "- **\(.Title)** [\(.Severity)] `\(.DedupKey|split(":")[1])`\n  \(.Description|split("\n")[0])"' synapse.json&lt;/span&gt;
              &lt;span class="s"&gt;echo&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
            &lt;span class="s"&gt;echo "_Per line annotations live in the repository Code scanning tab._"&lt;/span&gt;
          &lt;span class="s"&gt;} &amp;gt; comment.md&lt;/span&gt;
          &lt;span class="s"&gt;gh pr comment "$PR" --edit-last --body-file comment.md || gh pr comment "$PR" --body-file comment.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The gate step (&lt;code&gt;id: scan&lt;/code&gt;) has no &lt;code&gt;continue-on-error&lt;/code&gt;, so when it fails the whole job fails. That is what turns CI red.&lt;/li&gt;
&lt;li&gt;The upload and comment steps use &lt;code&gt;if: always()&lt;/code&gt;, so they still run after the gate fails. That is how you get both the merge block and the annotations plus the comment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;security-events: write&lt;/code&gt; is required for &lt;code&gt;upload-sarif&lt;/code&gt;, and &lt;code&gt;pull-requests: write&lt;/code&gt; is required to post the comment.&lt;/li&gt;
&lt;li&gt;The report is built from &lt;code&gt;synapse-cli scan --json&lt;/code&gt;, which carries the fixed version, EPSS, CVSS and a remediation description per finding. That is what lets the comment say "upgrade to X" and "here is how to fix it", not just "there is a problem".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Actually blocking the merge
&lt;/h2&gt;

&lt;p&gt;The CLI only produces a pass or fail signal. Blocking the merge is GitHub branch protection using that check as a required status.&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;# require the "scan" check to pass before merging into main&lt;/span&gt;
&lt;span class="c"&gt;# enforce_admins=true so even an admin cannot bypass it&lt;/span&gt;
gh api &lt;span class="nt"&gt;-X&lt;/span&gt; PUT repos/OWNER/REPO/branches/main/protection &lt;span class="nt"&gt;--input&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "required_status_checks": { "strict": false, "contexts": ["scan"] },
  "enforce_admins": true,
  "required_pull_request_reviews": null,
  "restrictions": null
}
&lt;/span&gt;&lt;span class="no"&gt;JSON
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here, any PR where the &lt;code&gt;scan&lt;/code&gt; job fails cannot be merged.&lt;/p&gt;

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

&lt;p&gt;I set up a demo repo: a clean &lt;code&gt;main&lt;/code&gt; (CI green), then a PR that adds a "payments service" with three deliberate problems. A Dockerfile that runs as root and pulls a script from an external URL, a function that uses MD5 to build a token, and a &lt;code&gt;requirements.txt&lt;/code&gt; pinned to old Django, PyYAML and requests full of CVEs.&lt;/p&gt;

&lt;p&gt;That PR produced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gate &lt;strong&gt;FAILED&lt;/strong&gt;: findings at and above high, so the job went red.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;51 alerts&lt;/strong&gt; in the Code scanning tab, each with a file location: most on &lt;code&gt;requirements.txt&lt;/code&gt;, several on &lt;code&gt;Dockerfile&lt;/code&gt;, and one on &lt;code&gt;app/payments.py:6&lt;/code&gt; where the MD5 call is.&lt;/li&gt;
&lt;li&gt;A single PR comment that reads like a real report:

&lt;ul&gt;
&lt;li&gt;a severity summary table&lt;/li&gt;
&lt;li&gt;a "Dependencies to upgrade" table (for example &lt;code&gt;django 2.2.0&lt;/code&gt; needs &lt;code&gt;&amp;gt;= 5.2.8&lt;/code&gt;, &lt;code&gt;requests 2.19.0&lt;/code&gt; needs &lt;code&gt;&amp;gt;= 2.33.0&lt;/code&gt;), with a collapsible list of every CVE and its fixed version sorted by severity and EPSS&lt;/li&gt;
&lt;li&gt;a "Code and config" section with each issue as &lt;code&gt;file:line&lt;/code&gt;, its rule id, and a one line remediation ("MD5 is broken, use SHA-256 or a KDF", "add a non-root USER before the entrypoint", and so on)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The merge blocked. I ran &lt;code&gt;gh pr merge&lt;/code&gt; for real and GitHub answered: &lt;code&gt;the base branch policy prohibits the merge&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One run caught all four kinds: vulnerable dependencies, weak source code, and an unsafe Docker config, all rolled into a single pass or fail decision, with the fix spelled out on the PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SARIF gotcha worth knowing
&lt;/h2&gt;

&lt;p&gt;This is the part I most wanted to share, because it only showed up when I ran the real thing.&lt;/p&gt;

&lt;p&gt;On the first run the SARIF upload failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code Scanning could not process the submitted SARIF file:
expected a physical location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out GitHub code scanning rejects the entire file if any result has a location that carries only a &lt;code&gt;logicalLocation&lt;/code&gt; and no &lt;code&gt;physicalLocation&lt;/code&gt;. The SCA findings were pointing at a "module" (something like &lt;code&gt;django@2.2.0&lt;/code&gt;) with no concrete file, so they were all rejected.&lt;/p&gt;

&lt;p&gt;The correct fix, which is also what Trivy does, is to point each SCA finding at the manifest that declares it, for example &lt;code&gt;requirements.txt&lt;/code&gt;. If the manifest is unknown, leave the result with no location at all, since GitHub still accepts that as a repo level alert. Never emit a location that is logical only.&lt;/p&gt;

&lt;p&gt;The lesson if you generate SARIF yourself: every result you want GitHub to annotate on a line needs a &lt;code&gt;physicalLocation&lt;/code&gt;, and &lt;code&gt;artifactLocation.uri&lt;/code&gt; must be a repo relative path with no leading slash. This is handled inside &lt;code&gt;synapse-cli&lt;/code&gt; now, but if you write your own exporter, keep it in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it compares to Trivy
&lt;/h2&gt;

&lt;p&gt;I ran both side by side on the same large real repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique CVEs: Synapse 261, Trivy 239, with 235 in common. The four CVEs only Trivy reported were all one package where the two tools resolved a different version, not a database gap.&lt;/li&gt;
&lt;li&gt;License detection: Synapse attached a license to 1443 packages, Trivy to 1394.&lt;/li&gt;
&lt;li&gt;Misconfig: Trivy has more low severity rules in raw count (270 to 208), but both now cover Dockerfile, Kubernetes, Helm and Terraform.&lt;/li&gt;
&lt;li&gt;Plus SAST and secret scanning, which &lt;code&gt;trivy fs&lt;/code&gt; does not do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The short version: detection is at parity with a few areas ahead, and Synapse adds the governance layer that the plain scanners do not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Come and contribute
&lt;/h2&gt;

&lt;p&gt;Synapse is open source under Apache-2.0 at &lt;strong&gt;&lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;github.com/KKloudTarus/synapse-ce&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you find it useful, I would love for you to jump in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop a star if the project looks worth following. It helps other people find it.&lt;/li&gt;
&lt;li&gt;Run it in your own pipeline and open an issue about what breaks or what is missing. Feedback from a real CI run is the most valuable kind.&lt;/li&gt;
&lt;li&gt;Pick up an issue labeled &lt;code&gt;good first issue&lt;/code&gt;, or add a lockfile parser, a SAST or misconfig rule, or a new ecosystem for SCA. The architecture is cleanly layered, so adding a tool does not touch the core.&lt;/li&gt;
&lt;li&gt;There is a &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; and a full docs set to get you started.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can fork the demo repo and play with it right away: &lt;strong&gt;&lt;a href="https://github.com/nghiadaulau/synapse-ci-demo" rel="noopener noreferrer"&gt;github.com/nghiadaulau/synapse-ci-demo&lt;/a&gt;&lt;/strong&gt; (open &lt;a href="https://github.com/nghiadaulau/synapse-ci-demo/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt; to see the blocked merge and the report).&lt;/p&gt;

&lt;p&gt;If you wire this gate into your own project, drop a comment below and tell me what it caught on the first run. My guess is there will be a surprise.&lt;/p&gt;

</description>
      <category>security</category>
      <category>contributorswanted</category>
      <category>go</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing Synapse: a deterministic-first, open-source SCA and evidence platform</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Mon, 06 Jul 2026 18:48:33 +0000</pubDate>
      <link>https://dev.to/nghiadaulau/introducing-synapse-a-deterministic-first-open-source-sca-and-evidence-platform-1k0i</link>
      <guid>https://dev.to/nghiadaulau/introducing-synapse-a-deterministic-first-open-source-sca-and-evidence-platform-1k0i</guid>
      <description>&lt;p&gt;We just open-sourced &lt;strong&gt;Synapse&lt;/strong&gt;, a governed control plane for software composition analysis,&lt;br&gt;
recon, evidence, and reporting. It is built for people who have to scan a dependency tree,&lt;br&gt;
prove what they found, and hand over a report that holds up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://synapse.kkloudtarus.net/" rel="noopener noreferrer"&gt;https://synapse.kkloudtarus.net/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/KKloudTarus/synapse-ce" rel="noopener noreferrer"&gt;https://github.com/KKloudTarus/synapse-ce&lt;/a&gt; (Apache-2.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we built it
&lt;/h2&gt;

&lt;p&gt;The usual workflow is fragmented. One tool for the SBOM, another for vulnerabilities, a&lt;br&gt;
spreadsheet for licenses, a folder of screenshots for evidence, and a report you assemble by&lt;br&gt;
hand. Nothing is reproducible, and when a client asks "how do you know this is real," the&lt;br&gt;
answer lives in someone's memory. Adding an LLM that writes your findings only makes that&lt;br&gt;
worse. We wanted the opposite: fast, but provable.&lt;/p&gt;

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

&lt;p&gt;Synapse runs the assessment lifecycle behind one control plane, in Go, clean architecture. A&lt;br&gt;
few ideas hold it together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic-first.&lt;/strong&gt; Scanning, matching, license classification, and reporting are pure,
reproducible Go. There is no model in the report path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope-gated execution.&lt;/strong&gt; Every engagement carries a scope and an authorization window,
enforced server-side before any tool runs. Tools run via argument arrays, never a shell string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper-evident evidence.&lt;/strong&gt; Every artifact is hash-chained and append-only. A broken chain
blocks the report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded automation.&lt;/strong&gt; The optional AI layer only ever proposes. A distinct verifier or a
human confirms. The agent can never confirm its own claim.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does today: SBOM across 15+ ecosystems, multi-source vulnerability detection with&lt;br&gt;
risk-based prioritization (KEV, then EPSS, then CVSS), license compliance, reachability, and&lt;br&gt;
deterministic reports in CycloneDX, SPDX, SARIF, and OpenVEX.&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;git clone https://github.com/KKloudTarus/synapse-ce.git
&lt;span class="nb"&gt;cd &lt;/span&gt;synapse-ce
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/docker-compose.full.yml up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;span class="c"&gt;# open http://localhost:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or gate CI on real risk: &lt;code&gt;./bin/synapse-cli scan . --fail-on high&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  We are looking for contributors
&lt;/h2&gt;

&lt;p&gt;Synapse is open source because this kind of tool gets better with more eyes on it. Good places&lt;br&gt;
to jump in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New ecosystems and lockfile parsers&lt;/li&gt;
&lt;li&gt;Detection quality and false-positive reduction&lt;/li&gt;
&lt;li&gt;Reachability and SAST coverage&lt;/li&gt;
&lt;li&gt;Reporting and standards (OpenVEX, CSAF, SARIF)&lt;/li&gt;
&lt;li&gt;The dashboard (Vite, React, Tailwind) and the docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with the &lt;a href="https://github.com/KKloudTarus/synapse-ce/tree/main/docs/guide" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;br&gt;
and &lt;a href="https://github.com/KKloudTarus/synapse-ce/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING&lt;/a&gt;, run it&lt;br&gt;
locally, then open an issue or send a pull request.&lt;/p&gt;

&lt;p&gt;One note: Synapse is for authorized security testing only. It validates scope data but cannot&lt;br&gt;
verify your legal authorization, so use it only against systems you are allowed to test.&lt;/p&gt;

&lt;p&gt;If it looks useful, a star helps other people find it, and if you want to build with us, open&lt;br&gt;
an issue and say hello.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>contributorswanted</category>
    </item>
    <item>
      <title>Building a Production Serverless URL Shortener on AWS — 21 Articles, Every Test Run for Real</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Tue, 26 May 2026 00:40:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-production-serverless-url-shortener-on-aws-21-articles-every-test-run-for-real-5905</link>
      <guid>https://dev.to/aws-builders/building-a-production-serverless-url-shortener-on-aws-21-articles-every-test-run-for-real-5905</guid>
      <description>&lt;p&gt;Most serverless tutorials stop at a hello-world Lambda behind API Gateway. I wanted the opposite: build one real product end to end, run every command on real AWS, and write&lt;br&gt;
down the actual numbers — including the ones that didn't go as planned.&lt;/p&gt;

&lt;p&gt;The result is a 21-article series that builds a &lt;strong&gt;URL shortener with realtime click analytics&lt;/strong&gt;, fully serverless, and takes it to production: auth, multi-tenancy, an event&lt;br&gt;
pipeline, realtime push, observability, CI/CD with canary deploys, a cost breakdown, and a load test. Code:&lt;br&gt;
&lt;a href="https://github.com/nghiadaulau/serverless-url-shortener-aws" rel="noopener noreferrer"&gt;github.com/nghiadaulau/serverless-url-shortener-aws&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       ┌──────── Cognito (JWT) ─────────┐
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Browser ─HTTPS──▶ API Gateway (HTTP API)        │ auth&lt;br&gt;
     │         │     │                           │&lt;br&gt;
POST /links ───┼─────▶ create ──▶ DynamoDB (single-table)&lt;br&gt;
GET /{code} ───┼─────▶ resolve ─┬─▶ 301 redirect&lt;br&gt;
     │         │                └─▶ EventBridge ─▶ SQS(+DLQ) ─▶ aggregator&lt;br&gt;
     │         │                                                  │ count + push&lt;br&gt;
     └─WebSocket───────────────────────────────────◀── realtime dashboard &lt;/p&gt;

&lt;p&gt;Step Functions (link moderation) · X-Ray · CloudWatch alarms&lt;br&gt;
SAM (IaC) · GitHub Actions CI · CodeDeploy canary + rollback&lt;/p&gt;

&lt;p&gt;Nothing here is a server you keep running. Idle cost is effectively zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few findings that surprised me (all measured, not quoted)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory is CPU.&lt;/strong&gt; The same CPU-bound work at 128 MB vs 1769 MB:&lt;/p&gt;

&lt;p&gt;128 MB:  2594 ms&lt;br&gt;
1769 MB:   88 ms   → ~29x faster, and ~2.25x cheaper (memory × billed time)&lt;/p&gt;

&lt;p&gt;At 128 MB a Lambda is CPU-starved, not memory-starved (&lt;code&gt;Max Memory Used&lt;/code&gt; stayed ~82 MB in both). The same lever cut a cold start from &lt;strong&gt;1513 ms to 295 ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The concurrency ceiling is a quota, not your code.&lt;/strong&gt; Load testing with k6 at ~554 req/s:&lt;/p&gt;

&lt;p&gt;Total requests: 27741&lt;br&gt;
301 (success):   304  (1.1%)&lt;br&gt;
503 (overload):   25  (0.1%)&lt;br&gt;
429 (throttled): ~98.8%&lt;br&gt;
ConcurrentExecutions (max): 10&lt;/p&gt;

&lt;p&gt;This account had a reduced Lambda concurrency limit of 10, and an API Gateway rate throttle I'd set deliberately. So under load the system &lt;em&gt;sheds&lt;/em&gt; traffic in two layers (429&lt;br&gt;
at the gateway, 503 at Lambda) and stays fast for what it serves — graceful degradation, not a crash. The fix isn't optimizing code; it's raising the quotas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency without a framework.&lt;/strong&gt; Click events are delivered at-least-once, so naive counting double-counts. One DynamoDB &lt;code&gt;TransactWriteItems&lt;/code&gt; bumps the counters &lt;em&gt;and&lt;/em&gt;&lt;br&gt;
writes a &lt;code&gt;CLICK#&amp;lt;eventId&amp;gt;&lt;/code&gt; marker with &lt;code&gt;attribute_not_exists&lt;/code&gt; — a duplicate cancels the whole transaction, so it's counted exactly once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bill.&lt;/strong&gt; Building and testing the entire thing — API, database, auth, event bus, queues, realtime, state machine, observability — cost essentially &lt;strong&gt;$0&lt;/strong&gt;, all within&lt;br&gt;
free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the series covers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Foundations&lt;/strong&gt; — SAM, the Lambda execution lifecycle, cold starts, arm64&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core&lt;/strong&gt; — HTTP API vs REST API, DynamoDB single-table design, GSIs and sparse indexes, conditional writes, atomic counters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt; — Cognito + JWT authorizer, multi-tenancy, blocking IDOR at the data layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven&lt;/strong&gt; — EventBridge, SQS + DLQ, idempotency, partial batch failure, WebSocket realtime push, Step Functions + the saga pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production&lt;/strong&gt; — Powertools + X-Ray, CloudWatch alarms and SLOs, cold-start optimization, IAM least-privilege, throttling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations&lt;/strong&gt; — CI/CD with canary deploys and rollback, a real cost breakdown, load testing with k6, a Well-Architected review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every article is grounded in the official AWS docs and ends with cleanup so you never get a surprise bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read it
&lt;/h2&gt;

&lt;p&gt;Full series (English): &lt;strong&gt;&lt;a href="https://kkloudtarus.net/en/blog/what-is-serverless-when-to-use" rel="noopener noreferrer"&gt;https://kkloudtarus.net/en/blog/what-is-serverless-when-to-use&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Code: &lt;strong&gt;&lt;a href="https://github.com/nghiadaulau/serverless-url-shortener-aws" rel="noopener noreferrer"&gt;https://github.com/nghiadaulau/serverless-url-shortener-aws&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're learning serverless beyond hello-world, this is the path I wish I'd had. Questions and corrections welcome.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>eBPF From Scratch: from the eBPF VM to writing your own tools (tested on a live Cilium cluster)</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Sun, 24 May 2026 15:08:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/ebpf-from-scratch-from-the-ebpf-vm-to-writing-your-own-tools-tested-on-a-live-cilium-cluster-3373</link>
      <guid>https://dev.to/aws-builders/ebpf-from-scratch-from-the-ebpf-vm-to-writing-your-own-tools-tested-on-a-live-cilium-cluster-3373</guid>
      <description>&lt;p&gt;Right now, on one worker of a Kubernetes cluster I built, &lt;strong&gt;140 eBPF programs are running inside the Linux kernel&lt;/strong&gt; — routing every packet, controlling device access, collecting metrics. Nobody recompiled the kernel. Nobody loaded a module.&lt;/p&gt;

&lt;p&gt;That sentence is the whole reason I wrote &lt;strong&gt;eBPF From Scratch&lt;/strong&gt;: a free, 22-chapter series that takes you from &lt;em&gt;"what even is the eBPF virtual machine"&lt;/em&gt; all the way to &lt;em&gt;writing and loading your own eBPF programs&lt;/em&gt; — in C with libbpf + CO-RE, and from Go with cilium/ebpf.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Read it free (English):&lt;/strong&gt; &lt;a href="https://kkloudtarus.net/en/blog/series/ebpf-from-scratch" rel="noopener noreferrer"&gt;https://kkloudtarus.net/en/blog/series/ebpf-from-scratch&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/nghiadaulau/ebpf-from-scratch" rel="noopener noreferrer"&gt;https://github.com/nghiadaulau/ebpf-from-scratch&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Everything is tested on real hardware&lt;/strong&gt; — a Kubernetes cluster running &lt;strong&gt;kernel 6.17&lt;/strong&gt; and &lt;strong&gt;Cilium 1.19&lt;/strong&gt; (kube-proxy-less, hundreds of BPF programs live) is the lab throughout. No hand-wavy diagrams; we dissect programs that are actually running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounded in official docs&lt;/strong&gt; — ebpf.io, kernel.org, libbpf, cilium. Claims are verified, not vibes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep-dive, not surface&lt;/strong&gt; — we go down to registers, the verifier's safety proofs, JIT, maps, and the exact lifecycle a program goes through.&lt;/li&gt;
&lt;li&gt;It's also fully bilingual (English + Vietnamese).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick taste — every eBPF concept is something you can &lt;em&gt;see&lt;/em&gt; on a real node:&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;sudo &lt;/span&gt;bpftool prog show &lt;span class="nb"&gt;id &lt;/span&gt;2871
&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;2871: sched_cls  name tail_no_service_ipv4  tag fe7bcb57c001d434  gpl
    xlated 4920B  jited 2778B  memlock 8192B  map_ids 171,631
    btf_id 758
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;xlated&lt;/code&gt; = bytecode after the verifier accepted it. &lt;code&gt;jited&lt;/code&gt; = native machine code. &lt;code&gt;map_ids&lt;/code&gt; = how it keeps state. That's eBPF, not on a slide — running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn (7 parts, 22 chapters)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part I — Foundations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The eBPF Virtual Machine: registers, instruction set, and bytecode&lt;/li&gt;
&lt;li&gt;The Verifier: why eBPF doesn't crash the kernel&lt;/li&gt;
&lt;li&gt;Maps: memory and the bridge to userspace&lt;/li&gt;
&lt;li&gt;Program types and hooks: where you attach, what you see&lt;/li&gt;
&lt;li&gt;BTF and CO-RE: compile once, run on every kernel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part II — Tracing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bpftrace from a one-liner to maps, counting and histograms&lt;/li&gt;
&lt;li&gt;uprobe, USDT, and inspecting a pod from the host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part III — Writing real tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;libbpf + CO-RE: writing an eBPF tool yourself (C)&lt;/li&gt;
&lt;li&gt;cilium/ebpf: loading eBPF from Go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part IV — Networking&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XDP: processing packets at the earliest point — writing a firewall&lt;/li&gt;
&lt;li&gt;tc/sched_cls and dissecting a live Cilium datapath&lt;/li&gt;
&lt;li&gt;Writing a tc program yourself: &lt;code&gt;__sk_buff&lt;/code&gt; and the tcx chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part V — Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LSM BPF: enforcing security right inside the kernel&lt;/li&gt;
&lt;li&gt;seccomp-bpf: filtering syscalls in every container&lt;/li&gt;
&lt;li&gt;The Tetragon way: from observe to enforce with &lt;code&gt;bpf_send_signal&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part VI — Observability&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU profiling with perf_event (the foundation of flame graphs)&lt;/li&gt;
&lt;li&gt;Off-CPU and scheduler latency&lt;/li&gt;
&lt;li&gt;Inside Hubble: from eBPF events to cluster-wide network flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part VII — Putting it together&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case study: a packet through Cilium's eBPF datapath&lt;/li&gt;
&lt;li&gt;Capstone: writing &lt;code&gt;connmon&lt;/code&gt;, a node-wide TCP connection monitor&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;Backend / platform / SRE / security folks who keep hearing "Cilium does that with eBPF" and want to actually understand — and &lt;em&gt;write&lt;/em&gt; — the thing. You'll want to be comfortable on the Linux command line; everything else is built up from zero.&lt;/p&gt;




&lt;p&gt;If you read any of it, I'd genuinely love feedback — what was clear, what wasn't. And if it's useful, a ⭐ on the &lt;a href="https://github.com/nghiadaulau/ebpf-from-scratch" rel="noopener noreferrer"&gt;repo&lt;/a&gt; helps a lot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start here →&lt;/strong&gt; &lt;a href="https://kkloudtarus.net/en/blog/series/ebpf-from-scratch" rel="noopener noreferrer"&gt;https://kkloudtarus.net/en/blog/series/ebpf-from-scratch&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>kernel</category>
    </item>
    <item>
      <title>Kubernetes From Scratch: build an HA cluster by hand (no kubeadm), then deep-dive every concept</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Sun, 24 May 2026 15:06:41 +0000</pubDate>
      <link>https://dev.to/aws-builders/kubernetes-from-scratch-build-an-ha-cluster-by-hand-no-kubeadm-then-deep-dive-every-concept-bdl</link>
      <guid>https://dev.to/aws-builders/kubernetes-from-scratch-build-an-ha-cluster-by-hand-no-kubeadm-then-deep-dive-every-concept-bdl</guid>
      <description>&lt;p&gt;Most Kubernetes tutorials start with &lt;code&gt;kubeadm init&lt;/code&gt; or a managed cluster. You get a working cluster and almost no idea &lt;em&gt;why&lt;/em&gt; it works.&lt;/p&gt;

&lt;p&gt;So I did the opposite. &lt;strong&gt;Kubernetes From Scratch&lt;/strong&gt; builds a complete, highly-available cluster &lt;strong&gt;by hand — no kubeadm, no scripts&lt;/strong&gt; — starting from the very first TLS certificate, all the way to a real HA control plane. Then it uses &lt;em&gt;that&lt;/em&gt; cluster as a lab to &lt;strong&gt;deep-dive every concept in Kubernetes&lt;/strong&gt;: 73 chapters.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Read it free (English):&lt;/strong&gt; &lt;a href="https://kkloudtarus.net/en/blog/series/kubernetes-from-scratch" rel="noopener noreferrer"&gt;https://kkloudtarus.net/en/blog/series/kubernetes-from-scratch&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;Manifests &amp;amp; scripts:&lt;/strong&gt; &lt;a href="https://github.com/nghiadaulau/kubernetes-from-scratch" rel="noopener noreferrer"&gt;https://github.com/nghiadaulau/kubernetes-from-scratch&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You build it by hand.&lt;/strong&gt; cfssl-signed certs, etcd quorum, apiserver/controller-manager/scheduler wired up yourself, kubelet + containerd on the workers, pod networking with the CNI bridge + VPC routes, CoreDNS — then a smoke test of the whole thing running together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then it goes deep.&lt;/strong&gt; Once the cluster is up, every concept gets explained &lt;em&gt;from the inside&lt;/em&gt; and configured by hand — not just "apply this YAML".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tested for real&lt;/strong&gt; on AWS EC2 with &lt;strong&gt;Kubernetes v1.36&lt;/strong&gt;, grounded in the official kubernetes.io docs.&lt;/li&gt;
&lt;li&gt;Fully bilingual (English + Vietnamese).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The journey
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 1 — Build the cluster from nothing (chapters 0–17)&lt;/strong&gt;&lt;br&gt;
PKI/TLS and &lt;em&gt;why a cluster needs so many certificates&lt;/em&gt; → etcd (quorum, Raft) → kube-apiserver and the request pipeline → controller-manager &amp;amp; scheduler (control loops, leader election) → HAProxy in front of 3 API servers → containerd/CRI → kubelet → kube-proxy → the Kubernetes network model → wiring pod networking by hand → CoreDNS → a full smoke test → and finally, &lt;strong&gt;the lifecycle of a request: from &lt;code&gt;kubectl apply&lt;/code&gt; to a running pod&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2 — Deep-dive every concept (chapters 18–72)&lt;/strong&gt;&lt;br&gt;
Using the cluster you built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workloads:&lt;/strong&gt; Pod lifecycle, init/sidecar containers, probes, Deployment/StatefulSet/DaemonSet/Job, requests/limits/QoS, PodDisruptionBudgets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling:&lt;/strong&gt; the scheduling framework, affinity/taints/tolerations, topology spread, priority &amp;amp; preemption, node-pressure eviction, HPA/VPA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; volumes, PV/PVC, StorageClass + dynamic provisioning (EBS CSI), VolumeSnapshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking:&lt;/strong&gt; why replace kube-proxy, migrating to &lt;strong&gt;kube-proxy-less Cilium (eBPF)&lt;/strong&gt;, NetworkPolicy, Ingress, Gateway API, LB IPAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; authentication → RBAC → ServiceAccounts &amp;amp; bound tokens, Pod Security Standards, seccomp/AppArmor/capabilities, Secrets hardening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extending K8s:&lt;/strong&gt; CRDs, admission webhooks, the operator pattern, API aggregation, device plugins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations:&lt;/strong&gt; etcd backup &amp;amp; cert rotation, upgrades &amp;amp; version skew, logging, metrics/traces/APF, CEL admission policy, in-place pod resize, and a full teardown&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;Anyone who can &lt;em&gt;use&lt;/em&gt; Kubernetes but wants to truly &lt;em&gt;understand&lt;/em&gt; it — for the CKA/CKS, for debugging production with confidence, or just to finally kill the "it's magic" feeling. Comfortable-with-Linux is the only prerequisite.&lt;/p&gt;




&lt;p&gt;If this helps you, a ⭐ on the &lt;a href="https://github.com/nghiadaulau/kubernetes-from-scratch" rel="noopener noreferrer"&gt;repo&lt;/a&gt; means a lot — and I'd love to hear which chapter clicked for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start here →&lt;/strong&gt; &lt;a href="https://kkloudtarus.net/en/blog/series/kubernetes-from-scratch" rel="noopener noreferrer"&gt;https://kkloudtarus.net/en/blog/series/kubernetes-from-scratch&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with EVS CLI: Manage Amazon Elastic VMware Service from the Command Line</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Wed, 06 Aug 2025 15:06:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/getting-started-with-evs-cli-manage-amazon-elastic-vmware-service-from-the-command-line-5cif</link>
      <guid>https://dev.to/aws-builders/getting-started-with-evs-cli-manage-amazon-elastic-vmware-service-from-the-command-line-5cif</guid>
      <description>&lt;h1&gt;
  
  
  🚀 Getting Started with EVS CLI: Manage Amazon Elastic VMware Service from the Command Line
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon EVS is currently in &lt;strong&gt;public preview&lt;/strong&gt; and subject to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Amazon Elastic VMware Service (EVS) is a fully managed service that allows you to run &lt;strong&gt;VMware Cloud Foundation (VCF)&lt;/strong&gt; directly on &lt;strong&gt;EC2 bare metal instances&lt;/strong&gt; inside your &lt;strong&gt;Amazon VPC&lt;/strong&gt;, without refactoring workloads or changing operational tools.&lt;/p&gt;

&lt;p&gt;With the launch of the &lt;strong&gt;EVS CLI&lt;/strong&gt;, you now have a powerful command-line interface to automate and manage your EVS infrastructure, ideal for scripting, DevOps pipelines, and quick access tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧰 What is EVS CLI?
&lt;/h2&gt;

&lt;p&gt;The EVS CLI is an AWS CLI-compatible tool to help manage your &lt;strong&gt;Amazon EVS environments&lt;/strong&gt; programmatically. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚧 Create or delete VCF environments&lt;/li&gt;
&lt;li&gt;🧱 Add or remove ESXi hosts&lt;/li&gt;
&lt;li&gt;📡 List VLANs and connected resources&lt;/li&gt;
&lt;li&gt;🏷️ Tag and organize environments&lt;/li&gt;
&lt;li&gt;📥 Retrieve environment information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It complements AWS Console and offers Infrastructure-as-Code (IaC) like capabilities directly in shell environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Available Commands
&lt;/h2&gt;

&lt;p&gt;Here's a quick overview of commands you can run with &lt;code&gt;evs&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provision a new VCF environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-environment-host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a new host to an existing EVS environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete-environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove an entire EVS environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete-environment-host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a host from a deployed environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-environments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all EVS environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-environment-hosts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View hosts within an environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-environment-vlans&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View VLAN setup in an environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get-environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetch detailed information of an EVS environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tag-resource&lt;/code&gt; / &lt;code&gt;untag-resource&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Manage tags on EVS resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-tags-for-resource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display tags on a specific resource&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚙️ Creating Your First EVS Environment
&lt;/h2&gt;

&lt;p&gt;The most essential command is &lt;code&gt;create-environment&lt;/code&gt;. It provisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vCenter Server&lt;/li&gt;
&lt;li&gt;NSX Manager&lt;/li&gt;
&lt;li&gt;SDDC Manager&lt;/li&gt;
&lt;li&gt;ESXi hosts (min 4)&lt;/li&gt;
&lt;li&gt;VLANs: Management, vMotion, vSAN, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧪 Sample Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws evs create-environment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-name&lt;/span&gt; &lt;span class="s2"&gt;"prod-vcf-env"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-0a12b345cdef67890 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-access-subnet-id&lt;/span&gt; subnet-0123abcd4567ef890 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vcf-version&lt;/span&gt; VCF-5.2.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--terms-accepted&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--license-info&lt;/span&gt; file://license.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--initial-vlans&lt;/span&gt; file://vlans.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--hosts&lt;/span&gt; file://hosts.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--connectivity-info&lt;/span&gt; file://connectivity.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vcf-hostnames&lt;/span&gt; file://vcf-hostnames.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--site-id&lt;/span&gt; &lt;span class="s2"&gt;"SITE-123456"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📝 Note: Provisioning takes several hours. Host count must be 4 to 16, and all subnets must reside in a single Availability Zone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🛠️ Example: hosts.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "hostName": "esxi01.mycorp.local",
    "keyName": "evs-key",
    "instanceType": "i4i.metal"
  },
  {
    "hostName": "esxi02.mycorp.local",
    "keyName": "evs-key",
    "instanceType": "i4i.metal"
  },
  ...
]

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧹 Cleaning Up Resources
&lt;/h3&gt;

&lt;p&gt;Once you’re done, it’s critical to clean up properly to avoid ongoing charges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete a host:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws evs delete-environment-host \
  --environment-id env-abc123456 \
  --host-name esxi01.mycorp.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ You must decommission the host in SDDC Manager UI before deleting it via CLI.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws evs delete-environment-host \
  --environment-id env-abc123456 \
  --host-name esxi01.mycorp.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete the entire environment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws evs delete-environment \
  --environment-id env-abc123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🛡️ Requirements Summary&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Minimum 4 hosts, maximum 16&lt;/li&gt;
&lt;li&gt;✅ VCF Version: VCF-5.2.1&lt;/li&gt;
&lt;li&gt;✅ Valid VCF and vSAN license keys from Broadcom&lt;/li&gt;
&lt;li&gt;✅ CIDRs for VLAN subnets must not overlap&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ All subnets must be in one Availability Zone&lt;br&gt;
🧠 Why Use EVS CLI?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📦 Infrastructure-as-Code: fits easily into Terraform or CI/CD workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;⚙️ Automation: simplifies provisioning and scaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧪 Testing: replicate environments consistently&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔍 Debugging: quick access to environment state&lt;br&gt;
🧩 Additional Tips&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;--generate-cli-skeleton&lt;/code&gt; to scaffold input JSON or YAML files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;--debug&lt;/code&gt; for troubleshooting and verbose logs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure your credentials using named &lt;code&gt;--profile&lt;/code&gt; or environment variables&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📌 Final Thoughts&lt;br&gt;
Amazon EVS with CLI support marks a new era for hybrid cloud operations. If your team relies on VMware, this opens a path to modernize workloads with minimal disruption while enjoying the scale and security of AWS.&lt;/p&gt;

&lt;p&gt;Try out the CLI and start building your first EVS environment today.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>news</category>
      <category>cli</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Amazon Elastic VMware Service (EVS) is Now GA — Run VMware Cloud Foundation Natively in AWS</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Wed, 06 Aug 2025 14:43:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/amazon-elastic-vmware-service-evs-is-now-ga-run-vmware-cloud-foundation-natively-in-aws-3c8o</link>
      <guid>https://dev.to/aws-builders/amazon-elastic-vmware-service-evs-is-now-ga-run-vmware-cloud-foundation-natively-in-aws-3c8o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🚀 &lt;strong&gt;Amazon Elastic VMware Service (EVS)&lt;/strong&gt; is now generally available. If you're managing VMware workloads, this new service might be the bridge to the cloud you've been waiting for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧩 What is Amazon EVS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Amazon Elastic VMware Service (EVS)&lt;/strong&gt; lets you deploy and run &lt;strong&gt;VMware Cloud Foundation (VCF)&lt;/strong&gt; environments directly on EC2 bare metal instances inside your &lt;strong&gt;Amazon VPC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No replatforming, no IP reassignments, no retraining. Just your existing VMware stack — now in AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Drop-in migration&lt;/strong&gt; — Lift-and-shift workloads without changing IPs or refactoring applications.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Full administrative control&lt;/strong&gt; — Customize and operate your VMware stack just like on-prem.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Self-managed or Partner-operated&lt;/strong&gt; — Choose your ops model based on team capacity.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;License portability&lt;/strong&gt; — Bring your existing VCF licenses to EVS.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Hybrid-ready&lt;/strong&gt; — Extend on-prem networks and workloads with zero disruption.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;AWS-native integration&lt;/strong&gt; — Connect with 200+ AWS services including Lambda, S3, Aurora, Bedrock, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌍 Available Now in 6 Regions
&lt;/h2&gt;

&lt;p&gt;Amazon EVS is now GA in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🇺🇸 US East (N. Virginia)&lt;/li&gt;
&lt;li&gt;🇺🇸 US East (Ohio)&lt;/li&gt;
&lt;li&gt;🇺🇸 US West (Oregon)&lt;/li&gt;
&lt;li&gt;🇯🇵 Asia Pacific (Tokyo)&lt;/li&gt;
&lt;li&gt;🇩🇪 Europe (Frankfurt)&lt;/li&gt;
&lt;li&gt;🇮🇪 Europe (Ireland)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VCF Version:&lt;/strong&gt; 5.2.1
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Type:&lt;/strong&gt; &lt;code&gt;i4i.metal&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Compatible with external storage and backup tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔄 Use Cases to Explore
&lt;/h2&gt;

&lt;p&gt;Here are some hybrid scenarios Amazon EVS enables:&lt;/p&gt;

&lt;h3&gt;
  
  
  🏢 Datacenter Exit Strategy
&lt;/h3&gt;

&lt;p&gt;Migrate entire clusters from on-prem to AWS without rewriting infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  💼 Disaster Recovery &amp;amp; Resilience
&lt;/h3&gt;

&lt;p&gt;Use EVS as your DR site with full VMware feature parity.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Dev/Test Bursting
&lt;/h3&gt;

&lt;p&gt;Spin up VCF environments in AWS for short-term workloads, testing, or isolated dev.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Gradual Modernization
&lt;/h3&gt;

&lt;p&gt;Move VMs to EVS and start layering AWS-native services like S3, RDS, or even Bedrock-based AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Getting Started with EVS
&lt;/h2&gt;

&lt;p&gt;Here's how to spin up your first Amazon EVS environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Complete prerequisites&lt;/strong&gt; (VPC, CIDR, IAM, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create your EVS environment&lt;/strong&gt; via:

&lt;ul&gt;
&lt;li&gt;AWS Console&lt;/li&gt;
&lt;li&gt;AWS CLI&lt;/li&gt;
&lt;li&gt;CloudFormation (&lt;code&gt;AWS::EVS::Environment&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customize VCF&lt;/strong&gt; in the familiar vSphere UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect &amp;amp; Migrate&lt;/strong&gt; your workloads&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📘 &lt;a href="https://docs.aws.amazon.com/evs/latest/userguide/getting-started.html" rel="noopener noreferrer"&gt;Official Docs – Getting Started&lt;/a&gt;&lt;br&gt;&lt;br&gt;
🖥️ &lt;a href="https://console.aws.amazon.com/evs/" rel="noopener noreferrer"&gt;EVS Console Access&lt;/a&gt;&lt;br&gt;&lt;br&gt;
💸 &lt;a href="https://aws.amazon.com/evs/pricing/" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you've been looking for a cloud migration path that &lt;strong&gt;doesn't disrupt your VMware expertise, tooling, or licensing&lt;/strong&gt;, Amazon EVS might be your golden ticket.&lt;/p&gt;

&lt;p&gt;You get the &lt;strong&gt;operational consistency&lt;/strong&gt; of VMware + the &lt;strong&gt;scale and innovation&lt;/strong&gt; of AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Try It Today
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;a href="https://aws.amazon.com/evs" rel="noopener noreferrer"&gt;Explore Amazon EVS on AWS&lt;/a&gt;&lt;br&gt;&lt;br&gt;
📩 Thinking about migrating? Let’s talk about your use case!&lt;br&gt;&lt;br&gt;
🛠 Have a hybrid cloud story? Share it in the comments!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks for reading! Follow me for more AWS deep dives and hybrid cloud updates.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>news</category>
      <category>evs</category>
      <category>challenge</category>
    </item>
    <item>
      <title>Beyond Auto-Complete: Supercharge Your Workflow with AWS's Newest AI, Amazon Q</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Wed, 06 Aug 2025 14:14:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/beyond-auto-complete-supercharge-your-workflow-with-awss-newest-ai-amazon-q-44n2</link>
      <guid>https://dev.to/aws-builders/beyond-auto-complete-supercharge-your-workflow-with-awss-newest-ai-amazon-q-44n2</guid>
      <description>&lt;p&gt;You've probably used AI code assistants. They're great for boilerplate, auto-completing a function, or even writing a unit test. But what if an AI could do more than just write the code you tell it to? What if it could understand your intent, debug multi-step problems, and even upgrade your application's framework for you?&lt;/p&gt;

&lt;p&gt;That's the promise of Amazon Q Developer, AWS's new, supercharged generative AI assistant. It's more than just a chatbot that knows the AWS docs; it's an active participant in your development workflow. If you're an experienced developer looking for the next leap in productivity, this is a tool you need to pay attention to.&lt;/p&gt;

&lt;p&gt;This post isn't just about what Amazon Q is. We'll dive into the specific, powerful features that move beyond simple code generation and show you how to leverage them with concrete examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Code Whisperer to True Collaborator
&lt;/h2&gt;

&lt;p&gt;AWS has had CodeWhisperer, its direct competitor to GitHub Copilot. It's solid for inline code suggestions. But Amazon Q represents a fundamental shift. It's less of a passive "whisperer" and more of an active "collaborator."&lt;/p&gt;

&lt;p&gt;The biggest leap forward is its agentic capabilities. An "agent" in this context is an AI that can perform a series of actions to achieve a goal. Instead of just suggesting a single block of code, Amazon Q can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scan your entire project to understand its structure and dependencies.&lt;/li&gt;
&lt;li&gt;Reason about the steps needed to complete a complex task.&lt;/li&gt;
&lt;li&gt;Read files, write new code, and apply diffs on your behalf.&lt;/li&gt;
&lt;li&gt;Run shell commands to test its own work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where things get interesting for seasoned developers. Think about the time you spend on tasks that are more than just writing a single function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killer Feature #1: The &lt;code&gt;/fix&lt;/code&gt; Command for In-IDE Debugging
&lt;/h2&gt;

&lt;p&gt;Imagine you're running your application and hit a cryptic error message in the console. Your usual workflow might be to copy-paste the error into Google and hunt through Stack Overflow.&lt;/p&gt;

&lt;p&gt;With Amazon Q, you streamline this dramatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario: A Null Pointer Exception in Java&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's say your test run fails with this common Java error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Exception &lt;span class="k"&gt;in &lt;/span&gt;thread &lt;span class="s2"&gt;"main"&lt;/span&gt; java.lang.NullPointerException
    at com.example.myapp.service.DataProcessor.process&lt;span class="o"&gt;(&lt;/span&gt;DataProcessor.java:25&lt;span class="o"&gt;)&lt;/span&gt;
    at com.example.myapp.Main.main&lt;span class="o"&gt;(&lt;/span&gt;Main.java:10&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of leaving your IDE, you open the Amazon Q chat window and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/fix Exception in thread "main" java.lang.NullPointerException at com.example.myapp.service.DataProcessor.process(DataProcessor.java:25)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazon Q doesn't just look up the error. It analyzes &lt;code&gt;DataProcessor.java&lt;/code&gt; at line 25 within the context of your project. It might see that you're calling a method on an object that wasn't initialized.&lt;/p&gt;

&lt;p&gt;It will then propose a specific, multi-file solution with a complete diff that you can review.&lt;/p&gt;

&lt;p&gt;Q's Proposed Fix (Diff View):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/src/main/java/com/example/myapp/service/DataProcessor.java
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/src/main/java/com/example/myapp/service/DataProcessor.java
&lt;/span&gt;&lt;span class="p"&gt;@@ -22,7 +22,9 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;     public void process(Data data) {
         // The object 'someObject' was not initialized.
&lt;span class="gd"&gt;-        String result = data.getSomeObject().getValue();
&lt;/span&gt;&lt;span class="gi"&gt;+        if (data != null &amp;amp;&amp;amp; data.getSomeObject() != null) {
+            String result = data.getSomeObject().getValue();
+        }
&lt;/span&gt;         System.out.println("Processing: " + result);
     }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a clear, context-aware suggestion that you can accept with one click. It's like pair-programming with someone who has already diagnosed the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killer Feature #2: Agentic Framework Upgrades
&lt;/h2&gt;

&lt;p&gt;This is a game-changer for dealing with technical debt. Let's say you have a Java 8 application using Spring Boot 2.x and you need to upgrade to Java 17 and Spring Boot 3.x. This is a notoriously complex task.&lt;/p&gt;

&lt;p&gt;With Amazon Q's upgrade agent, you can initiate this from the command line (after installing the Q tools).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command to Start the Upgrade:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// Make sure you are &lt;span class="k"&gt;in &lt;/span&gt;your project&lt;span class="s1"&gt;'s root directory
amazon-q-transformation upgrade --from-version 8 --to-version 17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazon Q will then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analyze Dependencies: It scans your &lt;code&gt;pom.xml&lt;/code&gt; or &lt;code&gt;build.gradle&lt;/code&gt; and identifies all dependencies that need updating (e.g., Spring Boot, Jakarta EE).&lt;/li&gt;
&lt;li&gt;Refactor Code: It systematically refactors code that relies on deprecated APIs. For instance, it knows that &lt;code&gt;javax.persistence.*&lt;/code&gt; needs to be changed to &lt;code&gt;jakarta.persistence.*&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example: Refactoring javax to jakarta&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old Code (Before Q):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;New Code (After Q's Transformation):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just one of hundreds of changes it can make automatically. It handles the tedious work, leaving you to focus on testing and logic verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killer Feature #3: Implementing Features with &lt;code&gt;/dev&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really powerful for teams. The &lt;code&gt;/dev&lt;/code&gt; command acts as a "workspace agent." You can give it high-level tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario: Add a new API endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you need to add a standard CRUD API for a Product entity. Instead of creating all the files manually, you can instruct Q:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev create a new REST API for a 'Product' resource. It should have full CRUD operations (GET all, GET by ID, POST, PUT, DELETE). Use Spring Boot conventions and create a controller, service, and repository layer. The Product entity has 'id', 'name', and 'price' fields.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amazon Q will then generate a plan and, upon your approval, create the following file structure and content:&lt;/p&gt;

&lt;p&gt;Plan Proposed by Q:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I will perform the following actions:
1.  ✅ Create `src/main/java/com/example/myapp/model/Product.java` with id, name, and price.
2.  ✅ Create `src/main/java/com/example/myapp/repository/ProductRepository.java` extending JpaRepository.
3.  ✅ Create `src/main/java/com/example/myapp/service/ProductService.java` with business logic for CRUD.
4.  ✅ Create `src/main/java/com/example/myapp/controller/ProductController.java` with REST endpoints.
5.  ✅ Suggest a new unit test file `ProductControllerTest.java`.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example Code Generated for &lt;code&gt;ProductController.java&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/products"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getAllProducts&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ResponseEntity:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;createProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... other methods for PUT and DELETE&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes a high-level developer request and turns it into fully-scaffolded, idiomatic code, saving you a significant amount of setup time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Is It Worth It?
&lt;/h2&gt;

&lt;p&gt;AI assistants are becoming standard, but Amazon Q Developer is making a strong case for being best-in-class. For intermediate and advanced developers, the value isn't in generating simple loops; it's in automating the complex, time-consuming tasks that bog us down.&lt;/p&gt;

&lt;p&gt;Features like &lt;strong&gt;context-aware debugging&lt;/strong&gt; (&lt;code&gt;/fix&lt;/code&gt;), automated framework upgrades, and the ability to &lt;strong&gt;act as a workspace agen&lt;/strong&gt;t (&lt;code&gt;/dev&lt;/code&gt;) are significant productivity multipliers. By understanding your entire codebase and executing multi-step plans, Amazon Q is moving the goalposts for what a developer assistant can be.&lt;/p&gt;

&lt;p&gt;If you're already in the AWS ecosystem, it's a no-brainer to integrate it into your IDE and give it a try. The time you save on just one nasty bug or one framework upgrade could easily justify the investment. The future of development is collaborative, and AWS is making a powerful new collaborator available to all of&lt;/p&gt;

</description>
      <category>aws</category>
      <category>challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>Running a Go Echo Web App on AWS Lambda (Serverless) with Minimal Changes</title>
      <dc:creator>Huỳnh Lê Nhất Nghĩa</dc:creator>
      <pubDate>Sun, 03 Aug 2025 03:28:20 +0000</pubDate>
      <link>https://dev.to/aws-builders/running-a-go-echo-web-app-on-aws-lambda-serverless-with-minimal-changes-2lg2</link>
      <guid>https://dev.to/aws-builders/running-a-go-echo-web-app-on-aws-lambda-serverless-with-minimal-changes-2lg2</guid>
      <description>&lt;h2&gt;
  
  
  🌎 Introduction
&lt;/h2&gt;

&lt;p&gt;Deploying a Go Echo application to AWS Lambda allows you to leverage a serverless architecture – no server management, auto-scaling, and pay-per-use pricing. Traditionally, Echo apps run as HTTP servers on their own ports, but Lambda functions are invoked by events (like API Gateway HTTP requests). In this guide, we’ll integrate the Echo framework with Lambda’s API Gateway event trigger, enabling your existing routes to run on Lambda with minimal code changes. We assume you already have a Go Echo app (or at least basic familiarity with Echo) and focus on adapting it for serverless deployment.&lt;/p&gt;

&lt;p&gt;Running Echo on Lambda brings the benefits of quick scaling and minimal idle cost. Thanks to Go’s fast startup and the efficiency of Echo, the performance overhead is small – Go is among the fastest AWS Lambda runtimes in terms of cold starts and execution speed. We will, however, highlight some cold start considerations and tips to keep your function responsive. Let’s dive into the steps: modifying the application code, containerizing the app for Lambda, deploying to AWS, and testing the serverless Echo application.&lt;/p&gt;




&lt;h2&gt;
  
  
  📁 Adapting an Echo Application for AWS Lambda
&lt;/h2&gt;

&lt;p&gt;The key to running Echo on Lambda is to translate incoming Lambda events (from API Gateway) into HTTP requests that Echo can understand, and vice versa for responses. AWS provides a convenient library for Go, &lt;strong&gt;aws-lambda-go-api-proxy&lt;/strong&gt;, which includes an adapter for the Echo framework. This adapter handles the conversion between API Gateway events and Echo’s request/response objects. Using this library, you can preserve your existing Echo routes and middleware – the adapter will funnel API Gateway calls through your Echo router.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Installing the Lambda Echo adapter
&lt;/h3&gt;

&lt;p&gt;Begin by adding the AWS Lambda libraries to your Go module. In your project, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/aws/aws-lambda-go/lambda
go get github.com/awslabs/aws-lambda-go-api-proxy/echo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first package (aws-lambda-go/lambda) is essential – it implements the Lambda runtime interface for Go. Including this package (and calling lambda.Start in your code, as we’ll do) makes your Go binary capable of receiving events from the Lambda service. The second package is the Echo adapter provided by AWS Labs, which we’ll use to bridge API Gateway events to the Echo framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Minimal code changes in &lt;code&gt;main.go&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;With the libraries in place, you only need to add a small amount of code to initialize the adapter and start the Lambda function. Below is an example of how to modify your &lt;code&gt;main.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

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

    &lt;span class="s"&gt;"github.com/labstack/echo/v4"&lt;/span&gt;
    &lt;span class="c"&gt;// Import the Lambda event and adapter packages&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aws/aws-lambda-go/events"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aws/aws-lambda-go/lambda"&lt;/span&gt;
    &lt;span class="n"&gt;echoadapter&lt;/span&gt; &lt;span class="s"&gt;"github.com/awslabs/aws-lambda-go-api-proxy/echo"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;echoLambda&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;echoadapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EchoLambdaV2&lt;/span&gt;  &lt;span class="c"&gt;// adapter for API Gateway HTTP API events&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 1. Initialize your Echo instance and routes as usual&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// ... (register routes, middleware, etc.)&lt;/span&gt;
    &lt;span class="c"&gt;// e.GET("/hello", handlerFunc) for example&lt;/span&gt;

    &lt;span class="c"&gt;// 2. Detect if running in AWS Lambda environment&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AWS_LAMBDA_FUNCTION_NAME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// We are in Lambda, so do not start Echo in the usual way.&lt;/span&gt;
        &lt;span class="c"&gt;// Initialize the Echo adapter for API Gateway V2 (HTTP API)&lt;/span&gt;
        &lt;span class="n"&gt;echoLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;echoadapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;// Start the Lambda event processing loop with our handler function&lt;/span&gt;
        &lt;span class="n"&gt;lambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Not in Lambda (running locally or in another environment), start Echo normally&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// 3. Lambda handler function for API Gateway HTTP API (v2) events&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIGatewayV2HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Proxy the incoming API Gateway request to the Echo instance and return the response&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;echoLambda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down the changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We declare a package-level echoLambda variable of type *echoadapter.EchoLambdaV2. This will hold the adapter that connects API Gateway HTTP API events to our Echo *echo.Echo router.&lt;/li&gt;
&lt;li&gt;In main(), after setting up the Echo instance e and defining all your routes and middleware, we check for an environment variable AWS_LAMBDA_FUNCTION_NAME. AWS sets this variable (the function name) for processes running on Lambda, so it's a reliable way to detect the Lambda runtime. If this variable is present, it means the code is running as a Lambda function.&lt;/li&gt;
&lt;li&gt;When on Lambda, we initialize the adapter: echoadapter.NewV2(e) wraps our Echo instance. This adapter knows how to convert API Gateway V2 events into standard HTTP requests that Echo can handle. We then call lambda.Start(handler). This instructs the AWS Lambda Go runtime to start receiving events and pass them to the specified handler function.&lt;/li&gt;
&lt;li&gt;The handler function we provide matches the expected signature for API Gateway HTTP API events (events.APIGatewayV2HTTPRequest -&amp;gt; events.APIGatewayV2HTTPResponse). Inside, it simply delegates to echoLambda.ProxyWithContext(ctx, req), which does the work of converting the event to an http.Request, routing it through Echo, and capturing the Echo response to convert back to an API Gateway response.&lt;/li&gt;
&lt;li&gt;If the environment variable is not set (meaning we are running the app locally or in a non-Lambda context), we fall back to the normal Echo startup: e.Start(":8080") to run an HTTP server on port 8080. This dual-mode setup is very useful for testing and for gradually migrating existing applications – you can still run the app normally, and when deployed to Lambda it will automatically switch to the event handling mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;: When the function runs on Lambda, the call to lambda.Start(handler) never returns; it will continuously loop, waiting for incoming events (invocations) from the Lambda service and passing them to your handler. The echoLambda adapter, having been initialized with your Echo router, will handle each request. All your defined routes, middleware, and handlers will work as usual – for example, an HTTP GET /hello request from API Gateway will be translated to an Echo context and trigger the same "/hello" route handler as it would on a normal server.&lt;br&gt;
&lt;strong&gt;Choosing APIGateway V1 or V2&lt;/strong&gt;: In our code, we used EchoLambdaV2 and the APIGatewayV2HTTPRequest event type, which correspond to API Gateway’s HTTP API (the newer, simpler and lower-latency version of API Gateway introduced by AWS). If instead you plan to use the older REST API (APIGateway v1), the adapter library also provides EchoLambda (without V2) for the v1 events. You’d import and use events.APIGatewayProxyRequest/Response and call echoadapter.New(e) accordingly. However, we recommend using HTTP APIs in most cases, as they are cheaper and have lower overhead. Just ensure that when creating your API Gateway, you choose HTTP API so that the events match the types expected by our handler. (The code above is for HTTP APIs.)&lt;br&gt;
&lt;strong&gt;Initialization at cold start&lt;/strong&gt;: It’s worth noting that we set up the Echo instance (e := echo.New(), route definitions, etc.) outside of the handler – in the main() function (and by extension, within the Lambda environment, before calling lambda.Start). This means all the initialization (setting up routes, connecting to databases if any, etc.) happens during the Lambda function’s cold start, and the initialized Echo router is reused for subsequent invocations. The global echoLambda holds the state between calls. This is important for performance: you wouldn’t want to rebuild your entire router on every invocation. By doing it once, subsequent events can be handled faster (just routing to the already-defined handlers). In fact, you could even move the Echo initialization to a global init() function to ensure it runs at import time. The point is to perform expensive setup only once per execution environment.&lt;/p&gt;

&lt;p&gt;At this stage, we have modified our application to be Lambda-compatible without disturbing its normal operation. If you run this binary on your local machine, it will start a web server on 8080 as always. If you run it in AWS Lambda, it will not open a socket; instead it will use the Lambda API to handle incoming events. With code changes done, let’s move on to packaging this app into a container image for deployment.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧱 Containerizing with a Multi-Stage Dockerfile
&lt;/h2&gt;

&lt;p&gt;AWS Lambda supports deploying functions as container images up to 10 GB in size. We will use a multi-stage Docker build to compile our Go Echo application and package it in a lean image based on AWS’s official Lambda base. Using a multi-stage Dockerfile ensures that the final image only contains the compiled binary and the minimal runtime environment needed, keeping the image small and efficient. Below is the Dockerfile, broken into two stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# **Stage 1 – Build the Go binary**&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.24-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="c"&gt;# Install any needed packages (tzdata for timezone support, git for fetching modules)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk &lt;span class="nt"&gt;--no-cache&lt;/span&gt; add tzdata git

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www/src&lt;/span&gt;

&lt;span class="c"&gt;# Bring in the Go module files and source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./ &lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go get                 &lt;span class="c"&gt;# (Optional) fetch any direct dependencies (if not already in go.mod)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download        &lt;span class="c"&gt;# download Go module dependencies&lt;/span&gt;

&lt;span class="c"&gt;# Build the binary:&lt;/span&gt;
&lt;span class="c"&gt;# Use -ldflags to inject the Git commit as version (GIT_COMMIT will be passed during build)&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; GIT_COMMIT&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; ./exec &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-X 'main.Version=&lt;/span&gt;&lt;span class="nv"&gt;$GIT_COMMIT&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;

&lt;span class="c"&gt;# **Stage 2 – Create the deployment image for Lambda**&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; public.ecr.aws/lambda/provided:al2023&lt;/span&gt;

&lt;span class="c"&gt;# Copy the binary from the build stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /var/www/src/exec ./exec&lt;/span&gt;

&lt;span class="c"&gt;# Set the Lambda entry point to our binary executable&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "./exec" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s explain what’s happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stage 1 (Build Stage): We use the official Go 1.24 Alpine image as the build environment. Alpine is lightweight and includes the Go compiler. We add tzdata (if our app deals with timezones) and git (often required for go get or fetching private modules). We set a working directory and copy our source code into the image. Then we run go mod download to fetch dependencies. Finally, we compile the Go program with go build. We name the output binary exec for convenience. The -ldflags="-X 'main.Version=$GIT_COMMIT'" part is optional – it’s injecting a Version variable (defined in our Go code’s main package) with the Git commit hash, which is a nice way to embed version info into the binary. The actual $GIT_COMMIT value can be passed during the docker build command (using --build-arg GIT_COMMIT=$(git rev-parse HEAD) for example). If you don’t need that, you can simplify to RUN go build -o exec . or similar.&lt;/li&gt;
&lt;li&gt;Stage 2 (Runtime Stage): We start from public.ecr.aws/lambda/provided:al2023, which is Amazon’s Amazon Linux 2023 Lambda base image. This base image includes the necessary components for Lambda’s custom runtime API. In other words, it’s an empty Lambda environment that will run our binary as the function. We copy the compiled exec binary from the build stage into the root of this image. We then set the ENTRYPOINT to ["./exec"]. This means when the Lambda service invokes our container, it will execute our exec binary. Because our binary was built with the aws-lambda-go/lambda package and calls lambda.Start (as we wrote in main.go), it will act as a Lambda-compatible executable. The AWS base image ensures the Runtime Interface Client (RIC) is present to coordinate between the function and the Lambda platform. (For reference, the provided.al2023 image expects your app to either have its own custom runtime interface or use the Lambda Go library which handles it for you. We did the latter by using the Lambda Go SDK.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few notes on this Docker setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We used the AWS-provided base image for Go (provided:al2023). This is an “OS-only” image that doesn’t include a language runtime (since our Go app is a self-contained binary) but has the Lambda API interface. Using this base is recommended by AWS for Go functions. (Alternatively, AWS offers similar base images for specific languages and even provided.al2 for Amazon Linux 2. AL2023 is fine and supported until 2029)&lt;/li&gt;
&lt;li&gt;By using multi-stage builds, our final image only contains the single binary and the minimal OS libraries needed. The Go compiler and source files from Stage 1 are not included in the final image, which keeps it small. This helps reduce the image download time at cold start and improves security (less attack surface).&lt;/li&gt;
&lt;li&gt;We didn’t explicitly include a CMD in the Dockerfile; it’s not needed here because the entrypoint alone suffices (Lambda will invoke the entrypoint). We also didn’t set a WORKDIR in the runtime stage – by default, the working directory is root (/) and we copy the binary there. The ENTRYPOINT ["./exec"] will execute the binary from that location. (The Lambda base image might set LAMBDA_TASK_ROOT or similar, but in our simple case we can run from root.)&lt;/li&gt;
&lt;li&gt;If your Echo app requires any static files or templates, ensure they are either embedded in the binary (e.g., using Go embed) or copied into the image. Typically, API-only apps won’t need extra files. If you do have assets, copy them in Stage 2 and adjust your working directory as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🕒 Performance and Cold Start Considerations
&lt;/h2&gt;

&lt;p&gt;When moving a web app to AWS Lambda, it’s important to understand the cold start behavior and overall performance implications. Here are key points and best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt;: A cold start occurs when a new Lambda instance (container) is spun up to handle a request. This involves provisioning the runtime, initializing your code (running main() and global inits), etc. Go-based Lambdas are generally very fast to cold start compared to languages like Java or .NET – usually on the order of tens of milliseconds up to a few hundred milliseconds, depending on the binary size and initialization work. In fact, Go and Rust are often cited as having the smallest cold start times among common runtimes. Echo itself is a lightweight framework, so it doesn’t add much overhead on startup beyond registering routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What contributes to cold start time?&lt;/strong&gt; For our Echo app, cold start includes the time to load the container and execute the main() function: creating the Echo instance, setting up routes, and any other startup logic (e.g., establishing a database connection, reading config). The Docker image size can also play a role – a larger image might take slightly longer for AWS to download and start. Our image is fairly small (the alpine-built binary plus AWS base, likely under ~50MB depending on your app), which is good. Using the multi-stage build and minimal base helps keep this small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse of instances (warm starts)&lt;/strong&gt;: After a cold start, the Lambda may keep the instance alive for subsequent requests (a “warm” invocation). In warm invocations, the Echo app is already initialized, so handling a request is just running through your handlers. This should be as fast as running on a regular server for the most part. AWS typically reuses instances for a while (several minutes) before retiring them, if traffic is steady. According to AWS, in practice &amp;lt;1% of invocations are cold starts for typical workloads, though if your traffic is very sporadic you might see more cold starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent requests&lt;/strong&gt;: One difference from running Echo on your own server is that a single Lambda instance handles one request at a time (per concurrent execution). With an Echo HTTP server, you could serve many requests concurrently on one process using Go routines. In Lambda, if 10 requests come in simultaneously, AWS will spin up 10 separate instances (assuming you’ve allowed that concurrency). Each instance will handle one request at that moment. This means your app scales automatically, but it also means you might encounter multiple cold starts if many instances need to spin up at once. The benefit is each instance has the full CPU for one request, often leading to fast processing for each request. The downside is if your app isn’t busy enough to keep those instances around, you pay the penalty of cold starts for bursts of traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provisioned Concurrency&lt;/strong&gt;: If you have a sensitive endpoint that must always respond quickly, you might consider Lambda’s Provisioned Concurrency feature. This keeps a number of instances warm and ready to handle requests, eliminating cold start latency at the cost of a constant hourly charge. For example, keeping 1 or 2 instances provisioned during business hours can ensure low latency for a public API. With Go and Echo, you likely won’t need this unless you have strict sub-100ms latency requirements on the first request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuning memory size&lt;/strong&gt;: AWS Lambda’s performance is tied to the memory setting – higher memory gives more CPU. Our function might run fine in 128 MB, but if your Echo app does CPU-intensive work, you can allocate more memory (which linearly increases available CPU). This can also reduce cold start duration because more CPU speed means faster initialization. There’s a trade-off with cost, as more memory = higher cost per millisecond. A common approach is to benchmark your function at different memory levels to see where the best price-performance lies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize initialization&lt;/strong&gt;: Since cold start includes running your global initialization, make sure to optimize that path. Lazy-load things if possible or use lightweight clients. For instance, if you connect to a database in main(), that will add to cold start time. Sometimes it’s better to initialize clients on first use rather than at start (depending on your use case). In our example, we set up routes and such, which is usually very fast (microseconds). Echo’s startup is not heavy. If you have any heavy computations (like loading large config files, ML models, etc.), consider moving those to on-demand or using something like Lambda Layers (for large assets) to reduce init time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aws-lambda-go-api-proxy overhead&lt;/strong&gt;: The adapter we use does add a slight overhead for translating the event to an HTTP request. It constructs an http.Request from the JSON event and then after Echo handles it, it constructs the response event. This overhead is usually quite small (microseconds to a few milliseconds). If ultimate performance is needed, one could write a custom handler that avoids this translation and uses the events structs directly, but then you’d lose the advantage of reusing the Echo framework. In most cases, the convenience is well worth the tiny overhead. In fact, the approach of using this proxy is common and recommended for porting existing apps. The alternative AWS provides (discussed next) uses a similar principle but at the container level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda Web Adapter (alternative approach)&lt;/strong&gt;: AWS recently released an extension called the Lambda Web Adapter, which can simplify running web frameworks on Lambda. With that, you don’t even need to modify your code to call lambda.Start or use the proxy library – you can run Echo as if it were just listening on a port (8080), and the adapter (as a Lambda extension) will capture requests and route them to your web server automatically. In our case, we’ve already done the integration manually, but it’s good to know such an option exists. The web adapter might slightly increase cold start (since it’s an additional layer to load) and currently you’d need to include the adapter binary in your image, but it eliminates the need to write a custom handler. If you were starting from scratch or integrating a very complex web app, the adapter is worth looking into. (It supports Echo, Gin, Chi, etc., and works with API Gateway, Function URLs, or ALB similarly) In summary, the awslabs proxy library and the AWS web adapter achieve similar goals; one is in-process (code library) and the other is an external extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost considerations&lt;/strong&gt;: Running on Lambda means you’re billed per execution time and memory. Go is efficient and typically will handle requests quickly. If your Echo app was running on an EC2 or Kubernetes constantly, you might save costs by going serverless, especially if requests are infrequent. However, if you have consistently high load, at some point a constantly warm Lambda could cost more than a stable server – you’d have to analyze. The benefit is you get automatic scaling and zero management. Also remember that API Gateway (if used) has its own cost per request (though HTTP API is cheaper than REST API). If cost is a concern for high volume, you could consider an ALB (Application Load Balancer) as a trigger, since ALB’s pricing might be different (per LCU hours, etc.) for high throughput. But for most moderate uses, the difference is minor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging and monitoring&lt;/strong&gt;: Echo logs to stdout by default, which ends up in CloudWatch Logs for each Lambda invocation. Be mindful of logging too much (as it can slow things and incur costs). You can use structured logging and log only essential info, as needed. AWS X-Ray can be used if you need tracing – you’d have to integrate the X-Ray SDK with Echo (or at least capture the handler execution). This might be an advanced topic beyond this guide, but it’s possible to enable X-Ray for deeper performance analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, performance of Go Echo on Lambda is generally excellent. Cold starts are minimal (especially compared to heavier runtimes) – as one independent benchmark noted, “All languages (except Java and .NET) have a pretty small cold start,” with Go being a top performer. Warm execution of requests should be on par with running on a dedicated server for the same CPU power. The main things to watch are occasional cold start spikes and ensuring your app is stateless (which it should be – Echo by itself doesn’t store global state per request, so it’s naturally stateless across invocations).&lt;/p&gt;

&lt;p&gt;🏁 Conclusion&lt;br&gt;
We’ve shown how you can deploy a Go Echo framework application to AWS Lambda with only minor adjustments. By using the AWS Lambda Go API Proxy adapter, you can preserve your existing Echo routes and middleware, making the move to serverless relatively straightforward. We covered writing a small Lambda bootstrap in main.go to initialize the adapter (only a few lines of code) and using a multi-stage Dockerfile to produce a lean container image for Lambda. After pushing the image to ECR and creating a Lambda function, your Echo app runs in the cloud without servers – each request triggers your code via API Gateway or a Lambda URL.&lt;/p&gt;

&lt;p&gt;This approach lets you consolidate what might have been multiple microservice endpoints into a single Lambda function (since Echo can route different paths internally), which can simplify deployment. (In one case study, developers combined routes under one Echo Lambda and avoided deploying many separate functions) Keep in mind the trade-offs: a single Lambda handling many routes means all routes scale together, which is usually fine for a coherent API.&lt;br&gt;
We also discussed how to optimize for performance. With Go, cold starts are fast, but it’s still wise to minimize cold start impact by doing one-time initialization and possibly leveraging provisioned concurrency for mission-critical low-latency needs. Monitor your function’s memory usage and execution time with CloudWatch – you might find that increasing memory reduces runtime enough to be cost-effective (since you pay for time, a faster execution might offset the higher memory cost).&lt;/p&gt;

&lt;p&gt;With your Echo app successfully running on Lambda, you can enjoy the benefits of a serverless architecture: automatic scaling, high availability, and no servers to maintain or patch. Development remains almost the same as writing a normal Echo web service, which means you retain productivity and familiarity. For further enhancements, you could integrate other AWS services (e.g., DynamoDB, S3, etc.) by calling their SDKs within your handlers – the Lambda environment will allow outbound calls to AWS services or the internet as configured by your function’s role and VPC settings.&lt;/p&gt;

&lt;p&gt;Finally, remember to handle things like timeouts and retries appropriately (API Gateway might retry on errors, etc.), and return proper HTTP responses through Echo for various conditions. Since Echo is fully in charge of HTTP-level behavior, you have flexibility to use its middleware (like authentication, CORS middleware, etc.) as you would normally. Those will all work in the Lambda context as they would on a standalone server.&lt;/p&gt;

&lt;p&gt;By following this guide, you’ve containerized a Go Echo application for AWS Lambda with minimal friction, enabling a scalable, serverless deployment. Happy coding, and enjoy your new serverless Echo setup!&lt;/p&gt;

&lt;p&gt;References:&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/go-image.html" rel="noopener noreferrer"&gt;AWS Official Docs – Deploy Go Lambda functions with container images&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/awslabs/aws-lambda-go-api-proxy" rel="noopener noreferrer"&gt;aws-lambda-go-api-proxy (GitHub)&lt;/a&gt;: Library used to adapt Echo (and other Go frameworks like Gin, Chi) to Lambda events.&lt;/p&gt;

</description>
      <category>go</category>
      <category>aws</category>
      <category>developer</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
