<?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: Deepu K Sasidharan</title>
    <description>The latest articles on DEV Community by Deepu K Sasidharan (@deepu105).</description>
    <link>https://dev.to/deepu105</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%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png</url>
      <title>DEV Community: Deepu K Sasidharan</title>
      <link>https://dev.to/deepu105</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deepu105"/>
    <language>en</language>
    <item>
      <title>Introducing LlamaStash: a zero-overhead, terminal-native llama.cpp launcher</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:01:22 +0000</pubDate>
      <link>https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g</link>
      <guid>https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://deepu.tech/introducing-llamastash/" rel="noopener noreferrer"&gt;deepu.tech&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://deepu.tech/my-fully-offline-ai-assisted-linux-development-machine" rel="noopener noreferrer"&gt;recent post about my fully offline AI-assisted Linux development machine&lt;/a&gt;, I dropped a small detail near the bottom. I run my local model with an alias.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;I described it as "a small script. It lets me pick a GGUF model, context size, and reasoning mode. It remembers the last choice, so most of the time I just start it and get going."&lt;/p&gt;

&lt;p&gt;That script grew up. Today I'm releasing &lt;a href="https://github.com/llamastash/llamastash" rel="noopener noreferrer"&gt;&lt;strong&gt;LlamaStash&lt;/strong&gt;&lt;/a&gt;, the first public release of a fast, cross-platform, terminal-native launcher for &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; with zero overhead.&lt;/p&gt;

&lt;p&gt;It is a TUI. It is also a CLI. It is also a background daemon. It is also an OpenAI-compatible proxy. One small Rust binary (~5 MB download), three personas, same primitives everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf2wltv8a9681igk4m39.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf2wltv8a9681igk4m39.png" alt="Terminal session recording" width="799" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this exist?
&lt;/h2&gt;

&lt;p&gt;Well: ADHD, insomnia and the below 😆&lt;/p&gt;

&lt;p&gt;Local LLMs sit in an awkward gap.&lt;/p&gt;

&lt;p&gt;On one side, raw &lt;a href="https://github.com/ggml-org/llama.cpp/tree/master/tools/server" rel="noopener noreferrer"&gt;&lt;code&gt;llama-server&lt;/code&gt;&lt;/a&gt; is fast and honest. It is also tedious. You memorize flags. You write wrapper scripts. You remember which port a model is on. You guess context sizes that may or may not fit your VRAM. After a while you have a &lt;code&gt;~/scripts/&lt;/code&gt; folder full of shell aliases that nobody else can read.&lt;/p&gt;

&lt;p&gt;On the other side, &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; wrap llama.cpp in friendlier shells. Ollama is opinionated about model storage, format, and config. LM Studio is GUI-first and not terminal native. Both pay a real performance cost compared to raw llama-server, and both hide the underlying primitives that I actually like working with.&lt;/p&gt;

&lt;p&gt;I wanted something in the middle. A launcher that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stays out of llama.cpp's way (no fork, no patched copy, no opinions about its flags).&lt;/li&gt;
&lt;li&gt;Is fast to invoke from a terminal and fast to drive from a script.&lt;/li&gt;
&lt;li&gt;Is also good as a TUI, because I genuinely like terminal interfaces.&lt;/li&gt;
&lt;li&gt;Treats agents and humans as equals. Anything a person can do in the TUI, an agent can do via &lt;code&gt;--json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Has a daemon underneath so models survive the TUI closing, and so multiple clients can hit the same model concurrently.&lt;/li&gt;
&lt;li&gt;Exposes an OpenAI/Ollama-compatible proxy on loopback so any existing OpenAI client (your editor, your agent, your scripts) just works without per-model setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LlamaStash is that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a TUI?
&lt;/h2&gt;

&lt;p&gt;I love terminal UIs (see &lt;a href="https://github.com/kdash-rs/kdash" rel="noopener noreferrer"&gt;KDash&lt;/a&gt;, &lt;a href="https://github.com/jwt-rs/jwt-ui" rel="noopener noreferrer"&gt;JWT-UI&lt;/a&gt;, and &lt;a href="https://github.com/deepu105/battleship-rs" rel="noopener noreferrer"&gt;battleship-rs&lt;/a&gt;). I wrote KDash, a Kubernetes dashboard TUI in Rust. That was 2020. The Rust TUI ecosystem at the time was tui-rs and a lot of patience. Threading was DIY. Layouts were arithmetic. State management was you-figure-it-out.&lt;/p&gt;

&lt;p&gt;Building LlamaStash brought me back to a lot of that, but the ground has shifted. &lt;a href="https://ratatui.rs/" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt; (the maintained fork of tui-rs) is a real, polished framework now. &lt;a href="https://tokio.rs/" rel="noopener noreferrer"&gt;tokio&lt;/a&gt; makes async daemons boring in a good way. &lt;a href="https://hyper.rs/" rel="noopener noreferrer"&gt;hyper&lt;/a&gt; gives you a respectable HTTP server in a few hundred lines. &lt;a href="https://github.com/crossterm-rs/crossterm" rel="noopener noreferrer"&gt;crossterm&lt;/a&gt; handles the cross-platform terminal mess. &lt;a href="https://github.com/GuillaumeGomez/sysinfo" rel="noopener noreferrer"&gt;sysinfo&lt;/a&gt; covers host metrics. The pieces are all there and you have LLMs to help you speed up everything to 10x.&lt;/p&gt;

&lt;p&gt;I still believe what &lt;a href="https://deepu.tech/my-second-impression-of-rust/" rel="noopener noreferrer"&gt;I wrote then&lt;/a&gt;. Rust gives you safety, speed, and a great UX without picking just one. LlamaStash is ~180 Rust files and not one production panic. It feels solid in a way that the JavaScript and Java tooling I shipped earlier in my career never quite did.&lt;/p&gt;

&lt;p&gt;OK, enough nostalgia. Let me show you what the tool actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero to chat in one command
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;This is the first-run wizard. It detects your hardware, installs &lt;code&gt;llama-server&lt;/code&gt; for your OS/GPU combo, looks at your available VRAM, recommends a GGUF model that fits, downloads it, writes a tuned config, updates configs for your AI tools (OpenCode, Zed, etc.), and smoke-launches it to make sure the whole pipeline works end-to-end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dhqhfkt2pj0k9bifshz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dhqhfkt2pj0k9bifshz.gif" alt="Init wizard" width="600" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On my Strix Halo machine that means an automatic ROCm/HIP path with sensible defaults. On a MacBook it picks up Metal. On an NVIDIA Linux box it picks up Vulkan (CUDA coming soon). On a Windows 11 machine it picks the matching &lt;code&gt;win-cpu&lt;/code&gt; / &lt;code&gt;win-cuda&lt;/code&gt; / &lt;code&gt;win-vulkan&lt;/code&gt; llama.cpp asset. On an old laptop with no GPU it picks up CPU and quietly recommends a smaller model.&lt;/p&gt;

&lt;p&gt;If you already have a llama.cpp build you like, point at it with &lt;code&gt;--llama-server&lt;/code&gt;. If you already have GGUFs in &lt;code&gt;~/.cache/huggingface/&lt;/code&gt;, &lt;code&gt;~/.ollama/models&lt;/code&gt;, or &lt;code&gt;~/.lmstudio/models&lt;/code&gt;, LlamaStash discovers them. It also watches your model paths live, so a new download shows up without a restart.&lt;/p&gt;

&lt;p&gt;Already have a coding model on disk? Skip the wizard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llamastash start qwen-coder &lt;span class="nt"&gt;--ctx&lt;/span&gt; 16384 &lt;span class="nt"&gt;--reasoning&lt;/span&gt; on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole command. Or use the TUI and pick from a list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TUI
&lt;/h2&gt;

&lt;p&gt;This is what, I hope, most users will spend time in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbskbfsi50mgarb9bxfk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbskbfsi50mgarb9bxfk8.png" alt="TUI screenshot" width="799" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vim-style navigation everywhere.&lt;/strong&gt; &lt;code&gt;hjkl&lt;/code&gt;, &lt;code&gt;Ctrl+F&lt;/code&gt;/&lt;code&gt;Ctrl+B&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;/&lt;code&gt;$&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;/&lt;code&gt;gT&lt;/code&gt; for tab cycling, &lt;code&gt;/&lt;/code&gt; to filter, &lt;code&gt;?&lt;/code&gt; for help. The keybindings are not modal voodoo, they are real, documented, and rebindable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The right pane is a smoke test.&lt;/strong&gt; Logs, Chat, Embed, Rerank tabs that hit the same OpenAI-compatible endpoint external clients use. So when something works in the TUI, it works in your editor, your agent, your script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-TUI HuggingFace browser.&lt;/strong&gt; Search, sort, paginate, see per-file hardware fit, download with cancel. No tab-switching to a website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five themes plus custom.&lt;/strong&gt; &lt;a href="https://catppuccin.com/" rel="noopener noreferrer"&gt;Catppuccin Macchiato&lt;/a&gt; is the default because of course it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive layout.&lt;/strong&gt; Works from 60 cells up. Tiny terminal? List columns and hint chips drop by priority rank so the model name stays readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility.&lt;/strong&gt; Status is dual-encoded with both color and a glyph, so it reads on mono terminals too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mouse is off by default (except scrolling) so your terminal keeps native click-and-drag text selection. Opt in with &lt;code&gt;--mouse-focus&lt;/code&gt; or a single line in &lt;code&gt;config.yaml&lt;/code&gt; if you want 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.amazonaws.com%2Fuploads%2Farticles%2Fbznkcnqyyhau22i5708j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbznkcnqyyhau22i5708j.png" alt="TUI second view" width="799" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI
&lt;/h2&gt;

&lt;p&gt;Everything the TUI can do, the CLI can do. The CLI is a first-class surface, not an afterthought.&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;# List models, human-friendly&lt;/span&gt;
llamastash list

&lt;span class="c"&gt;# List models, machine-friendly&lt;/span&gt;
llamastash list &lt;span class="nt"&gt;--json&lt;/span&gt; | jq

&lt;span class="c"&gt;# Show, start, stop, status&lt;/span&gt;
llamastash show qwen-coder
llamastash start qwen-coder
llamastash stop qwen-coder
llamastash status &lt;span class="nt"&gt;--json&lt;/span&gt;

&lt;span class="c"&gt;# Pull a model from HuggingFace&lt;/span&gt;
llamastash pull bartowski/Qwen3.6-27B-Instruct-GGUF

&lt;span class="c"&gt;# Recommend something based on my hardware&lt;/span&gt;
llamastash recommend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things that matter for scripting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--json&lt;/code&gt; is the stable agent contract. If we change the human-readable output, scripts using &lt;code&gt;--json&lt;/code&gt; keep working.&lt;/li&gt;
&lt;li&gt;Documented &lt;a href="https://github.com/llamastash/llamastash/blob/main/FEATURES.md#documented-exit-codes-per-failure-class" rel="noopener noreferrer"&gt;exit codes per failure class&lt;/a&gt;. Branch on numbers, not on message text.&lt;/li&gt;
&lt;li&gt;TTY output is colored and padded. Piped output is byte-stable TSV, so your &lt;code&gt;awk&lt;/code&gt; and &lt;code&gt;column&lt;/code&gt; pipelines keep working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also an &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; bundle in the repo that teaches your coding agent to use the CLI properly. Drop it into your OpenCode, OpenClaw, or Claude Code skills directory and the agent learns to prefer &lt;code&gt;--json&lt;/code&gt;, branch on exit codes, and read &lt;code&gt;status --json&lt;/code&gt; before configuring an OpenAI-compatible client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proxy
&lt;/h2&gt;

&lt;p&gt;Once a model is running, LlamaStash exposes an OpenAI-compatible endpoint on &lt;code&gt;http://127.0.0.1:11435/v1&lt;/code&gt; by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:11435/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "qwen-coder", "messages": [{"role": "user", "content": "hi"}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenCode, &lt;a href="https://pi.dev" rel="noopener noreferrer"&gt;Pi&lt;/a&gt;, Cline, the OpenAI SDKs, &lt;code&gt;llm-cli&lt;/code&gt;, anything that speaks OpenAI works as-is.&lt;/p&gt;

&lt;p&gt;A few details worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-start on first request.&lt;/strong&gt; If the requested model is not running yet, the proxy starts it and serves the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback with audit headers.&lt;/strong&gt; If launch fails for any reason, the proxy falls back to a Ready peer model and adds &lt;code&gt;x-llamastash-served-by&lt;/code&gt; and &lt;code&gt;x-llamastash-fallback-reason&lt;/code&gt; headers so the client can react. You can turn off this behavior via the &lt;code&gt;fallback_enabled&lt;/code&gt; config option if you prefer a hard failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama discovery surface.&lt;/strong&gt; &lt;code&gt;GET /api/tags&lt;/code&gt;, &lt;code&gt;/api/version&lt;/code&gt;, &lt;code&gt;/api/ps&lt;/code&gt;, &lt;code&gt;POST /api/show&lt;/code&gt;. Tools that auto-detect Ollama via &lt;code&gt;OLLAMA_HOST&lt;/code&gt; will recognize LlamaStash and fall through to the OpenAI-compat endpoints for actual inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in Ollama drop-in mode.&lt;/strong&gt; With &lt;code&gt;--ollama-compat&lt;/code&gt; (or &lt;code&gt;LLAMASTASH_OLLAMA_COMPAT=1&lt;/code&gt;) the proxy claims port &lt;code&gt;11434&lt;/code&gt; and answers the byte-exact "Ollama is running" handshake. The official &lt;code&gt;ollama&lt;/code&gt; CLI talks to it. Most Ollama-Go-based clients talk to it. Useful when you want to replace Ollama in a workflow you didn't write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loopback only, no auth.&lt;/strong&gt; Single-user local threat model. The proxy refuses LAN binds. LAN exposure plus auth plus TLS is on the roadmap, not in 0.0.2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Zero overhead means zero overhead
&lt;/h2&gt;

&lt;p&gt;The benchmark detail lives in &lt;a href="https://deepu.tech/benchmarking-llamastash" rel="noopener noreferrer"&gt;its own post&lt;/a&gt; and &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks.md" rel="noopener noreferrer"&gt;docs/benchmarks.md&lt;/a&gt;, but here is the one-line version.&lt;/p&gt;

&lt;p&gt;LlamaStash spawns the unmodified upstream &lt;code&gt;llama-server&lt;/code&gt;. So the wrapper better not add measurable overhead vs running &lt;code&gt;llama-server&lt;/code&gt; directly. We measure it. It does not. LlamaStash matches raw &lt;code&gt;llama-server&lt;/code&gt; within &lt;code&gt;≤1%&lt;/code&gt; on every cell, across AMD APU, Apple Silicon, and NVIDIA. The proxy hop is &lt;code&gt;+0.45 ms&lt;/code&gt; median TTFT and zero impact on decode.&lt;/p&gt;

&lt;p&gt;The comparison against Ollama is more interesting, but I'll let the benchmark post do that talking. The methodology is published. The harness is reproducible. Run &lt;code&gt;make bench-end-to-end&lt;/code&gt; and check for yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvl9qfdasspmou8zy0669.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvl9qfdasspmou8zy0669.png" alt="LlamaStash vs raw llama-server vs LM Studio vs Ollama — decode tok/s across AMD APU, Apple M1, NVIDIA RTX 3050 Ti (defaults mode, log scale)" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture, in one paragraph
&lt;/h2&gt;

&lt;p&gt;One Rust binary. Invoking it with no arguments is the TUI. Invoking it with a subcommand is the CLI. Invoking it as &lt;code&gt;llamastash daemon&lt;/code&gt; is the supervisor. The TUI and the CLI both talk to the daemon over a 127.0.0.1 HTTP control plane that's bearer-token-authed. The URL and per-restart token live in a &lt;code&gt;runtime.json&lt;/code&gt; handshake file under the state dir, locked down to the file owner (mode &lt;code&gt;0600&lt;/code&gt; on Unix, Protected DACL on Windows). Same trust model on every supported OS: same-machine, same-UID. The daemon spawns &lt;code&gt;llama-server&lt;/code&gt; instances on demand, tracks them with a state machine, picks ports from a configurable range, and exposes the OpenAI-compatible proxy on loopback as a separate listener. State persists to atomic, mode-checked files. If your daemon crashes, the next invocation reads the state and re-adopts the running &lt;code&gt;llama-server&lt;/code&gt; instances. Models survive a TUI close. They do not survive a reboot, by design.&lt;/p&gt;

&lt;p&gt;For the deeper version, see &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/architecture.md" rel="noopener noreferrer"&gt;docs/architecture.md&lt;/a&gt;. I had so much fun designing the architecture and a lot of it came from &lt;a href="https://github.com/kdash-rs/kdash" rel="noopener noreferrer"&gt;KDash&lt;/a&gt; obviously.&lt;/p&gt;

&lt;h2&gt;
  
  
  How carefully tested?
&lt;/h2&gt;

&lt;p&gt;What &lt;a href="https://github.com/llamastash/llamastash/blob/main/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;CI&lt;/a&gt; gates before anything reaches crates.io, the Homebrew tap, or the AUR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~2,000 test attributes across the workspace (1,700+ in Rust, the rest in &lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;bats&lt;/code&gt;). 86.5% line coverage tracked via grcov + &lt;a href="https://coveralls.io/github/llamastash/llamastash?branch=main" rel="noopener noreferrer"&gt;Coveralls&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cargo audit --deny warnings&lt;/code&gt; for dependency vulnerabilities. Any new advisory fails CI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bats&lt;/code&gt; integration tests for &lt;code&gt;install.sh&lt;/code&gt; on Linux and macOS, plus &lt;code&gt;shellcheck -s sh&lt;/code&gt; on the script itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pytest&lt;/code&gt; for the Python packager scripts that drive the Homebrew formula and the AUR &lt;code&gt;PKGBUILD&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;MSRV pinned to Rust 1.95, verified with &lt;code&gt;cargo check --locked&lt;/code&gt; against the pinned toolchain. Cross-compile check for &lt;code&gt;aarch64-unknown-linux-gnu&lt;/code&gt; on every push.&lt;/li&gt;
&lt;li&gt;A "release readiness" job runs &lt;code&gt;cargo publish --dry-run --locked&lt;/code&gt; plus packager unit tests for both the Homebrew formula and the AUR &lt;code&gt;PKGBUILD&lt;/code&gt;s, so the publish steps are green before the tag ever moves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LlamaStash is a single-author project. Bugs will find me. The hygiene above is there to make that less painful when they do. The Windows and macOS builds are not as vigorously tested since I develop on Linux so there are gonna be rough edges there. If you have a Windows 11 machine or a Mac and want to help with testing, please do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Pick a channel. They all install the same binary. The download is &lt;code&gt;~5 MB&lt;/code&gt; across every supported target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS + Linux, one-shot installer&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://llamastash.dev/install.sh | sh

&lt;span class="c"&gt;# Homebrew (macOS + Linuxbrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;llamastash/llamastash/llamastash

&lt;span class="c"&gt;# Arch Linux (AUR)&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; llamastash         &lt;span class="c"&gt;# source build from the tagged GitHub release&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; llamastash-bin     &lt;span class="c"&gt;# prebuilt x86_64 / aarch64 tarball&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; llamastash-git     &lt;span class="c"&gt;# main-branch checkout, rebuilds on every -Syu&lt;/span&gt;

&lt;span class="c"&gt;# Windows 11 (PowerShell, no admin elevation)&lt;/span&gt;
irm https://llamastash.dev/install.ps1 | iex

&lt;span class="c"&gt;# Windows via Scoop bucket&lt;/span&gt;
scoop bucket add llamastash https://github.com/llamastash/scoop-llamastash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; scoop &lt;span class="nb"&gt;install &lt;/span&gt;llamastash

&lt;span class="c"&gt;# crates.io (any platform with a Rust toolchain)&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;llamastash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;llamastash init&lt;/code&gt; and you are off.&lt;/p&gt;

&lt;p&gt;Full install notes, including the non-interactive agent-friendly path, in &lt;a href="https://github.com/llamastash/llamastash/blob/main/INSTALL.md" rel="noopener noreferrer"&gt;INSTALL.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's not in 0.0.2
&lt;/h2&gt;

&lt;p&gt;A few things are explicitly on the next-release roadmap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LAN exposure + auth + TLS.&lt;/strong&gt; Today: loopback-only by design. If there's enough interest in safe LAN sharing, that needs auth and TLS done properly. Soon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic &lt;code&gt;/v1/messages&lt;/code&gt; compatibility shim.&lt;/strong&gt; OpenAI-compatible is good enough for most agents, but a few prefer the Anthropic shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server surface.&lt;/strong&gt; The CLI is already agent-friendly, so I'm double minded about whether a &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; server would make integration smoother. I'm personally &lt;a href="https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/" rel="noopener noreferrer"&gt;not a fan of MCP&lt;/a&gt; and prefer skills and CLIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLX and vLLM backends.&lt;/strong&gt; llama.cpp is the right default, but I'd like LlamaStash to be backend-pluggable if possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPU support.&lt;/strong&gt; It would be nice if the same tool could run models on both GPUs and NPUs, so modern Copilot+ PCs and M-series Macs put their Neural Engines to work too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-PID VRAM attribution.&lt;/strong&gt; Today the supervisor reports host-level GPU memory. A per-process number is more useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CUDA on Linux.&lt;/strong&gt; Vulkan is a fine GPU backend, but CUDA is the most popular GPU compute API on NVIDIA hardware. Today the Linux+NVIDIA route ships the Vulkan binary. CUDA on Linux is a bit sketchy to support (driver version matching, ABI stability), but I want to do it cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aarch64-pc-windows-msvc&lt;/code&gt;.&lt;/strong&gt; 0.0.2 ships x86_64 Windows only. Snapdragon X / Surface Pro coverage on the roadmap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track or shape the roadmap on the &lt;a href="https://github.com/llamastash/llamastash/blob/main/TODO.md" rel="noopener noreferrer"&gt;TODO.md&lt;/a&gt; file in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local AI tooling matters
&lt;/h2&gt;

&lt;p&gt;I wrote about this in the &lt;a href="https://deepu.tech/my-fully-offline-ai-assisted-linux-development-machine" rel="noopener noreferrer"&gt;Linux machine post&lt;/a&gt;, and I'm not going to repeat it all here. The short version.&lt;/p&gt;

&lt;p&gt;I use hosted models too. The best ones are still better at some things. But local-first tooling is a capability I want to have, not just for the privacy or the cost or the offline use, but for the ownership. It lets me change models, contexts, build flags, server parameters, and client config whenever I want. It lets me run experiments that I wouldn't think to run on a paid API. And in a world that is sliding fast toward a few companies owning the infrastructure that developers depend on, having my own stack feels less like a hobby and more like a small act of preservation.&lt;/p&gt;

&lt;p&gt;LlamaStash is one piece of that. There will be others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, break it, tell me
&lt;/h2&gt;

&lt;p&gt;If you have a Linux box, a Mac, or a Windows 11 machine sitting around with a half-decent GPU (or an Apple Silicon machine with enough unified memory), &lt;code&gt;llamastash init&lt;/code&gt; should get you to a working local model in a few minutes. Bug reports, feature ideas, and "this didn't work for me" stories are all welcome at &lt;a href="https://github.com/llamastash/llamastash/issues" rel="noopener noreferrer"&gt;github.com/llamastash/llamastash/issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A star on the repo helps the project show up for other people looking for the same itch. It also makes me feel good about the effort that went into this 🤓. Not going to lie about that.&lt;/p&gt;

&lt;p&gt;And finally, thanks to all the AI coding harnesses (OpenCode, Claude Code, Copilot) and LLMs (Claude, DeepSeek, Kimi, Qwen, Gemini, etc.) that turned what would have been a six-month, multi-person project into a one-person, couple-of-weeks project. I'm planning a deeper dive into the development process in a future post, especially the AI harness engineering around it and the vibe coding discipline that made it possible to keep the scope tight and the quality high. Stay tuned.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://bsky.app/profile/deepu105.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llamacpp</category>
      <category>localllm</category>
      <category>rust</category>
    </item>
    <item>
      <title>How fast is LlamaStash? Overhead, throughput, and a fair comparison with Ollama and LM Studio</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:34:05 +0000</pubDate>
      <link>https://dev.to/deepu105/how-fast-is-llamastash-overhead-throughput-and-a-fair-comparison-with-ollama-and-lm-studio-2e7c</link>
      <guid>https://dev.to/deepu105/how-fast-is-llamastash-overhead-throughput-and-a-fair-comparison-with-ollama-and-lm-studio-2e7c</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://deepu.tech/benchmarking-llamastash/" rel="noopener noreferrer"&gt;deepu.tech&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://deepu.tech/introducing-llamastash" rel="noopener noreferrer"&gt;release post for LlamaStash&lt;/a&gt; I made a claim I need to back up. The wrapper adds zero overhead vs running &lt;code&gt;llama-server&lt;/code&gt; directly. That is the kind of claim that should not exist in a blog post without numbers behind it. So here are the numbers.&lt;/p&gt;

&lt;p&gt;LlamaStash spawns the unmodified upstream &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;llama-server&lt;/code&gt;&lt;/a&gt;. So three different questions follow from that, and there is a benchmark suite for each.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suite A: overhead regression.&lt;/strong&gt; Does &lt;code&gt;llamastash start &amp;lt;model&amp;gt;&lt;/code&gt; add any measurable overhead on top of raw &lt;code&gt;llama-server&lt;/code&gt; when both run the same command line? This is the question the whole architecture depends on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suite B: cross-tool comparison.&lt;/strong&gt; How does LlamaStash-as-shipped compare to &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; on the same model, same hardware, through their OpenAI-compatible HTTP endpoints? This is the question users care about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suite C: proxy overhead.&lt;/strong&gt; Does going through the LlamaStash OpenAI-compat proxy cost anything vs hitting &lt;code&gt;llama-server&lt;/code&gt; directly? This is the smallest question, but it has to be asked because the proxy is the default surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three suites live in &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/" rel="noopener noreferrer"&gt;docs/benchmarks/&lt;/a&gt; in the repo. The harness is in &lt;a href="https://github.com/llamastash/llamastash/blob/main/scripts/bench/" rel="noopener noreferrer"&gt;scripts/bench/&lt;/a&gt;. The &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/methodology.md" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; covers fairness, how I throw out noisy runs, and why outputs differ across backends. Read it before pulling any single number out of context. On its own, one cell can mislead.&lt;/p&gt;

&lt;p&gt;The short version, if you are skimming. Two terms first: &lt;strong&gt;decode&lt;/strong&gt; is tokens per second while the model generates, and &lt;strong&gt;TTFT&lt;/strong&gt; is time to first token, the delay before anything comes back. And there are two ways to read every comparison below: &lt;strong&gt;matched-flags&lt;/strong&gt; (called &lt;code&gt;normalized&lt;/code&gt; in the harness, every tool runs the same knobs) and &lt;strong&gt;out-of-the-box&lt;/strong&gt; (called &lt;code&gt;defaults&lt;/code&gt;, what you get when you just run the tool). They tell different stories.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The wrapper adds no measurable overhead.&lt;/strong&gt; With matched flags, LlamaStash runs the same speed as raw &lt;code&gt;llama-server&lt;/code&gt;: within 1% on Apple Silicon and on three of four sizes on the AMD APU. The one exception is the 35B-A3B MoE on AMD, at -1.8% decode. That same cell flips to +1.1% the other way out of the box, so it is run-to-run timing noise on a fork-and-spawn, not real overhead. NVIDIA decode is -0.57%, a hair past my 0.5% warning line. Everything stays well inside my 2% hard-fail line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out of the box, LlamaStash is a touch faster than raw &lt;code&gt;llama-server&lt;/code&gt;,&lt;/strong&gt; because it sets good defaults for you: all GPU layers on every GPU backend, and flash-attention for Qwen and Llama models on Apple Metal and CUDA. On the Mac Qwen run that is +7.3% decode over raw llama-server's stock defaults. On the AMD APU the upstream defaults are already good, so the two match. On NVIDIA the gap is wider than those flags explain, which I leave as an open question below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama is slower, sometimes a lot slower.&lt;/strong&gt; It is 38-72% behind raw llama-server on decode on the AMD APU. On the Mac its RAG prefill is 52× slower than the direct path, because it re-reads the whole document on every request instead of caching it. On NVIDIA Vulkan, the RAG prefill runs never finished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio is a mixed bag.&lt;/strong&gt; Its bundled ROCm runtime crashes on Strix Halo (&lt;code&gt;gfx1151&lt;/code&gt;) for every model except the small one, so I could only compare it on Vulkan. There it loses decode to LlamaStash on three of four sizes (+8% small, +52% mid, +32% large MoE) and wins large_dense by 7%. And everywhere, it pays a 170-2300 ms first-token tax from its OpenAI shim and just-in-time model loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy is free.&lt;/strong&gt; Talking to LlamaStash's proxy instead of llama-server directly costs +0.45 ms to first token on AMD, -0.6 ms on the Mac, +0.57 ms on NVIDIA. Zero on decode. Sub-millisecond, which is nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now to the actual point of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Same model bytes, same workloads, same hardware. Three platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Platforms
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;CPU / SoC&lt;/th&gt;
&lt;th&gt;GPU&lt;/th&gt;
&lt;th&gt;RAM / VRAM&lt;/th&gt;
&lt;th&gt;OS&lt;/th&gt;
&lt;th&gt;llama.cpp build&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AMD APU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ryzen AI Max+ 395 (Strix Halo)&lt;/td&gt;
&lt;td&gt;Radeon 8060S iGPU (RDNA 3.5, &lt;code&gt;gfx1151&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;128 GiB unified, 70 W TDP&lt;/td&gt;
&lt;td&gt;Arch Linux, ROCm 7.2.3&lt;/td&gt;
&lt;td&gt;b9245 (&lt;code&gt;b39a7bf1b&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;2026-05-24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apple Silicon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apple M1&lt;/td&gt;
&lt;td&gt;Integrated, Metal&lt;/td&gt;
&lt;td&gt;16 GiB unified&lt;/td&gt;
&lt;td&gt;macOS 26 (Darwin 25.4.0)&lt;/td&gt;
&lt;td&gt;b9330 (&lt;code&gt;328874d05&lt;/code&gt;, Homebrew Metal)&lt;/td&gt;
&lt;td&gt;2026-05-27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intel i9-11900H laptop&lt;/td&gt;
&lt;td&gt;RTX 3050 Ti Laptop (Ampere, &lt;code&gt;sm_86&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;4 GiB VRAM, 63 GiB host&lt;/td&gt;
&lt;td&gt;Manjaro 6.6.141, driver 595.71.05, CUDA 13.2&lt;/td&gt;
&lt;td&gt;b9360 (&lt;code&gt;6b4e4bd5&lt;/code&gt;, CUDA + Vulkan lanes)&lt;/td&gt;
&lt;td&gt;2026-05-28&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is just the hardware I had on hand, but the three machines sit in usefully different places. Strix Halo is high-end consumer with a unified-memory iGPU. The M1 with 16 GiB is the entry-level Apple Silicon most laptops actually have. The RTX 3050 Ti Laptop is a discrete NVIDIA card starved for VRAM. Not a full sweep of the hardware space, but three points different enough to stress different assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Models
&lt;/h3&gt;

&lt;p&gt;The AMD APU run covered four points on the size and architecture axes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Quant&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Why it's here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemma 4 E2B&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;1.6 GiB&lt;/td&gt;
&lt;td&gt;Fast iteration, tests TTFT and per-request overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemma 4 31B&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;17.4 GiB&lt;/td&gt;
&lt;td&gt;The everyday workhorse size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;large_dense&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;26.6 GiB&lt;/td&gt;
&lt;td&gt;High-fidelity dense, exposes memory bandwidth limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;large_moe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Qwen3.6 35B-A3B&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;34.4 GiB&lt;/td&gt;
&lt;td&gt;Mixture of experts, ~3B active per token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Mac and NVIDIA runs were constrained to a single model each: different models, different sizes, both intentional.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Quant&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Why this one&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple M1 16 GiB&lt;/td&gt;
&lt;td&gt;Qwen2.5-0.5B-Instruct&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;~397 MB&lt;/td&gt;
&lt;td&gt;The M1 16 GiB tier is the entry-level Apple Silicon class. It can load larger models, but the primary target for this hardware is fast, responsive edge inference of small models. M2 Pro / M3 Max runs at the mid/large tier are deferred.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTX 3050 Ti Laptop 4 GiB&lt;/td&gt;
&lt;td&gt;gemma-3-4b-it&lt;/td&gt;
&lt;td&gt;Q3_K_M&lt;/td&gt;
&lt;td&gt;2.1 GiB&lt;/td&gt;
&lt;td&gt;The 4 GiB VRAM ceiling forces every dense model larger than &lt;code&gt;small&lt;/code&gt; into a partial-offload regime that is not interesting for cross-tool comparison. Larger-VRAM NVIDIA hosts should re-run the full matrix.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same GGUF bytes across tools. Where a tool needs its own pulled copy (Ollama has its own blob store), bit-identical content was verified via SHA-256 before benchmarking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are interested in running this benchmarks yourself on your hardware, checkout &lt;a href="https://github.com/llamastash/llamastash/blob/main/scripts/run-uat-and-bench.md" rel="noopener noreferrer"&gt;scripts/run-uat-and-bench.md&lt;/a&gt;. Please consider sending a PR with the results if you do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Workloads
&lt;/h3&gt;

&lt;p&gt;Four. They model what a real coding session looks like.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chat_turn&lt;/code&gt;: 50-token prompt, 64-token decode. The conversational baseline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent_decode&lt;/code&gt;: short prompt, 1024-token decode. Measures sustained generation throughput.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rag_prefill&lt;/code&gt;: 4096-token prompt, 256-token decode. Measures prompt processing at realistic agent context sizes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parallel_4&lt;/code&gt;: four &lt;code&gt;chat_turn&lt;/code&gt; requests concurrently. Measures the supervisor's ability to interleave.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One warmup run, then three measured repetitions per cell. If those three spread by more than 10%, the cell is re-run; if it is still noisy on the second try, it gets flagged in the per-cell JSON.&lt;/p&gt;

&lt;h3&gt;
  
  
  Methodology rules
&lt;/h3&gt;

&lt;p&gt;A few rules the harness enforces, because they are what make the result honest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suite A runs an identical command line.&lt;/strong&gt; The harness drops &lt;code&gt;--port&lt;/code&gt;, then checks that the rest of the &lt;code&gt;llama-server&lt;/code&gt; command LlamaStash spawns is character-for-character identical to the one the raw run uses. There is nowhere for a hidden tweak to hide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suite B talks to every tool the same way.&lt;/strong&gt; Every tool is driven through its OpenAI-compatible HTTP endpoint, not its CLI. Same client, same payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each platform runs in one sitting.&lt;/strong&gt; All of a platform's rows (LlamaStash, raw, Ollama, LM Studio) run in the same session, no reboots in between.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engine fallbacks are labelled.&lt;/strong&gt; When a tool falls back to a different engine (for example LM Studio's ROCm runtime crashing on &lt;code&gt;gfx1151&lt;/code&gt;, so the row uses its Vulkan runtime instead), the cell is annotated and the reason explained.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full methodology is in &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/methodology.md" rel="noopener noreferrer"&gt;docs/benchmarks/methodology.md&lt;/a&gt;. If you intend to argue with a number, read that page first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suite A: overhead regression
&lt;/h2&gt;

&lt;p&gt;The headline question. If LlamaStash spawns the unmodified &lt;code&gt;llama-server&lt;/code&gt;, the only way it can be slower is by adding overhead in the wrapper. We measure that overhead directly.&lt;/p&gt;

&lt;p&gt;Each metric has two thresholds: a &lt;strong&gt;warning line&lt;/strong&gt; (the harness calls it &lt;em&gt;advisory&lt;/em&gt;, where the run passes but prints a banner) and a &lt;strong&gt;hard-fail line&lt;/strong&gt; (&lt;em&gt;catastrophic&lt;/em&gt;, where the run exits non-zero). That is the "warning line" and "hard-fail line" I refer to throughout.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Catastrophic (exits non-zero)&lt;/th&gt;
&lt;th&gt;Advisory (exits zero with banner)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ttft_ms&lt;/code&gt; delta&lt;/td&gt;
&lt;td&gt;≥ 200 ms&lt;/td&gt;
&lt;td&gt;≥ 30 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;decode_tps&lt;/code&gt; delta percentage&lt;/td&gt;
&lt;td&gt;≥ 2.0% slower&lt;/td&gt;
&lt;td&gt;≥ 0.5% slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daemon idle RSS&lt;/td&gt;
&lt;td&gt;≥ 64 MiB extra&lt;/td&gt;
&lt;td&gt;≥ 48 MiB extra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z6e3lor0fbnn43krunb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z6e3lor0fbnn43krunb.png" alt="Suite A overhead: LlamaStash vs raw llama-server, six matched-flags cells, with advisory and hard-fail lines" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Top panel: decode delta % per cell, positive = LlamaStash faster. Bottom panel: TTFT delta ms, negative = LlamaStash faster. Yellow dashed lines are the harness advisory thresholds (±0.5% decode, ±30 ms TTFT); red dashed lines are the hard-fail thresholds (±2% decode; the ±200 ms TTFT hard-fail line is off-chart). Every cell sits inside the hard-fail box.&lt;/p&gt;

&lt;h3&gt;
  
  
  AMD APU
&lt;/h3&gt;

&lt;p&gt;Suite A on Strix Halo used &lt;code&gt;normalized&lt;/code&gt; mode against the matching self-built HIP &lt;code&gt;llama-server&lt;/code&gt; (&lt;code&gt;b9440&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;Workload&lt;/th&gt;
&lt;th&gt;LlamaStash decode tok/s&lt;/th&gt;
&lt;th&gt;raw decode tok/s&lt;/th&gt;
&lt;th&gt;Δ decode&lt;/th&gt;
&lt;th&gt;LlamaStash TTFT ms&lt;/th&gt;
&lt;th&gt;raw TTFT ms&lt;/th&gt;
&lt;th&gt;Δ TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;chat_turn&lt;/code&gt; (small)&lt;/td&gt;
&lt;td&gt;82.1&lt;/td&gt;
&lt;td&gt;81.0&lt;/td&gt;
&lt;td&gt;+1.3%&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;chat_turn&lt;/code&gt; (mid)&lt;/td&gt;
&lt;td&gt;9.9&lt;/td&gt;
&lt;td&gt;9.9&lt;/td&gt;
&lt;td&gt;-0.0%&lt;/td&gt;
&lt;td&gt;468&lt;/td&gt;
&lt;td&gt;466&lt;/td&gt;
&lt;td&gt;+3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;chat_turn&lt;/code&gt; (large_dense)&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;+0.1%&lt;/td&gt;
&lt;td&gt;406&lt;/td&gt;
&lt;td&gt;406&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;chat_turn&lt;/code&gt; (large_moe)&lt;/td&gt;
&lt;td&gt;42.3&lt;/td&gt;
&lt;td&gt;43.1&lt;/td&gt;
&lt;td&gt;-1.8%&lt;/td&gt;
&lt;td&gt;178&lt;/td&gt;
&lt;td&gt;185&lt;/td&gt;
&lt;td&gt;-7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of the four sizes land within 0.3% on decode. The 35B-A3B MoE is the exception at -1.8%, the only AMD cell past my 1% warning line. The run-to-run spread was tiny on both sides (0.06% to 0.17%), so the gap is real measurement, not jitter. But out of the box, the same cell flips the other way, to +1.1% in LlamaStash's favor, and a real overhead would not change sign like that. So this is fork-and-spawn timing noise on a big MoE, not the wrapper costing you throughput. It is well inside my 2% hard-fail line, and the daemon's idle memory is well under budget too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apple Silicon
&lt;/h3&gt;

&lt;p&gt;Suite A on M1 used &lt;code&gt;normalized&lt;/code&gt; mode against the matching Homebrew Metal &lt;code&gt;llama-server&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;Metric&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTFT (mean)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;+2.3 ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;OK&lt;/strong&gt; (advisory 30 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+0.8%&lt;/code&gt; LlamaStash faster (92.2 vs 91.5 tok/s)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;OK&lt;/strong&gt; (advisory 0.5%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An earlier overhead pass showed LlamaStash -5.33% on decode, but that number mixed in the out-of-the-box defaults and was noisy on top of a real defaults difference. The matched-flags chat_turn cell is the clean Suite A comparison: +0.8% for LlamaStash, comfortably inside 1%. What the defaults add is broken out under Suite B below.&lt;/p&gt;

&lt;h3&gt;
  
  
  NVIDIA
&lt;/h3&gt;

&lt;p&gt;Suite A on RTX 3050 Ti Laptop used &lt;code&gt;normalized&lt;/code&gt; mode against the b9360 Vulkan &lt;code&gt;llama-server&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;Metric&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTFT (mean)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+1.7 ms&lt;/code&gt; (114.9 vs 113.2 ms)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;OK&lt;/strong&gt; (advisory 30 ms; well inside catastrophic 200 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decode&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;−0.57%&lt;/code&gt; (41.80 vs 42.04 tok/s)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ADVISORY&lt;/strong&gt; (marginal miss on the 0.5% advisory floor)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Decode misses the 0.5% warning line by 0.07 of a point. A marginal miss, nowhere near the 2% hard-fail line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verdict: Suite A
&lt;/h3&gt;

&lt;p&gt;Across four model sizes on the AMD APU and one model each on the Mac and the RTX 3050 Ti Laptop, LlamaStash matches raw &lt;code&gt;llama-server&lt;/code&gt; within 1% on five of six matched-flags cells. Two cells miss that line: NVIDIA chat_turn at -0.57% decode (0.07 of a point past 0.5%), and the AMD MoE at -1.8% (which flips to +1.1% out of the box, so it reads as noise, not overhead). Both are well inside the 2% hard-fail line. The architecture (one binary, daemon on demand, a supervisor state machine, an HTTP control plane on loopback) does not cost throughput.&lt;/p&gt;

&lt;p&gt;The zero-overhead claim holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suite B: cross-tool comparison
&lt;/h2&gt;

&lt;p&gt;Now the more interesting question. How does LlamaStash compare to Ollama and LM Studio in practice?&lt;/p&gt;

&lt;p&gt;Same model bytes, same workload, OpenAI-compatible endpoint on every tool. Variance-gated runs. Decode tok/s and TTFT in milliseconds per cell.&lt;/p&gt;

&lt;h3&gt;
  
  
  AMD APU
&lt;/h3&gt;

&lt;p&gt;Hardware: Ryzen AI Max+ 395, Radeon 8060S, 70 W. HIP/ROCm via system ROCm 7.2.3 + self-built &lt;code&gt;llama-server&lt;/code&gt; (b9440). Dates: 2026-05-23 (small) and 2026-05-24 (mid, large_dense, large_moe).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;small (E2B Q4)&lt;/th&gt;
&lt;th&gt;mid (31B Q4)&lt;/th&gt;
&lt;th&gt;large_dense (27B Q8)&lt;/th&gt;
&lt;th&gt;large_moe (35B-A3B Q8)&lt;/th&gt;
&lt;th&gt;Engine notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaStash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;82.1 / 51&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.9 / 468&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.5 / 406&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.3 / 178&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;local HIP/ROCm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;raw &lt;code&gt;llama-server&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;81.0 / 51&lt;/td&gt;
&lt;td&gt;9.9 / 466&lt;/td&gt;
&lt;td&gt;7.5 / 406&lt;/td&gt;
&lt;td&gt;43.1 / 185&lt;/td&gt;
&lt;td&gt;local HIP/ROCm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio 2.18.0&lt;/td&gt;
&lt;td&gt;91.1 / 187&lt;/td&gt;
&lt;td&gt;— (crash¹)&lt;/td&gt;
&lt;td&gt;— (crash¹)&lt;/td&gt;
&lt;td&gt;— (crash¹)&lt;/td&gt;
&lt;td&gt;bundled ROCm 6.4 vendor (see footnote)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama 0.24.0&lt;/td&gt;
&lt;td&gt;50.8 / 224&lt;/td&gt;
&lt;td&gt;4.8 / 1096&lt;/td&gt;
&lt;td&gt;2.6 / 1750&lt;/td&gt;
&lt;td&gt;12.2 / 484&lt;/td&gt;
&lt;td&gt;bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each cell is decode tok/s / TTFT ms on &lt;code&gt;chat_turn&lt;/code&gt; in &lt;code&gt;normalized&lt;/code&gt; mode (&lt;code&gt;ctx=4096&lt;/code&gt;, &lt;code&gt;n_gpu_layers=999&lt;/code&gt;, &lt;code&gt;flash_attn=on&lt;/code&gt;, &lt;code&gt;batch=512&lt;/code&gt;, &lt;code&gt;ubatch=512&lt;/code&gt;). Sourced from one bench run per row, no averaging across runs or modes. See source map at the bottom of this section. Defaults-mode numbers, agent-decode workload, and the wrapper-vs-raw delta are broken out in the charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c96snng7qzegklh5quy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c96snng7qzegklh5quy.png" alt="AMD APU decode (HIP/ROCm, chat_turn defaults)" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh51zwyq2m0myj73y67g3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh51zwyq2m0myj73y67g3.png" alt="AMD APU TTFT (HIP/ROCm, chat_turn defaults, log scale)" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Headline numbers from this matrix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LlamaStash is the same speed as raw &lt;code&gt;llama-server&lt;/code&gt;&lt;/strong&gt; within 1% on three of four sizes (small +1.3%, mid -0.0%, large_dense +0.1%). The 35B-A3B MoE shows -1.8%, the same noise-not-overhead cell explained in Suite A above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama is 38-72% slower on decode&lt;/strong&gt; than raw llama-server across all four sizes. Worst on large_dense (-65%), least bad on small (-38%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio only loads the small model on ROCm&lt;/strong&gt; on this hardware (&lt;code&gt;gfx1151&lt;/code&gt;); mid, large_dense, and large_moe all crash at startup. Its full per-size view is in the Vulkan addendum below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first-token gap is the one to watch. Ollama and LM Studio both pay at least 200 ms, and over a second on the larger models, mostly in their HTTP shim layers. LlamaStash and raw llama-server stay under 200 ms even on the MoE. For an agent that fires a lot of short requests, that delay is most of the wall-clock you feel.&lt;/p&gt;

&lt;p&gt;¹ LM Studio's bundled ROCm vendor libraries (v6.4 in &lt;code&gt;linux-llama-rocm-vendor-v3&lt;/code&gt;) abort in &lt;code&gt;ggml_cuda_error&lt;/code&gt; during backend initialization on &lt;code&gt;gfx1151&lt;/code&gt; (Strix Halo), across all three LMS-shipped runtime versions (&lt;code&gt;amd-rocm-avx2@1.33.0&lt;/code&gt;, &lt;code&gt;@2.16.0&lt;/code&gt;, &lt;code&gt;@2.18.0&lt;/code&gt;). The system ROCm 7.2.3 loads the same models without issue via raw &lt;code&gt;llama-server&lt;/code&gt;, so this is an LM Studio vendor-bundle limitation, not a hardware limitation. LM Studio numbers on AMD APU therefore use its Vulkan runtime exclusively, shown as a separate engine addendum below.&lt;/p&gt;

&lt;h4&gt;
  
  
  Vulkan addendum (LM Studio's only working AMD path)
&lt;/h4&gt;

&lt;p&gt;Same-day side-by-side of LlamaStash and LM Studio on the Vulkan backend, since that is the only engine where LMS can load every model on this hardware. The other two cross-tool comparators are explicitly out of scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;raw &lt;code&gt;llama-server&lt;/code&gt;&lt;/strong&gt; is omitted because the wrapper-overhead claim is already covered by the HIP/ROCm table above (and it repeats on Vulkan: LlamaStash matches raw within 1% across the measured cells).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; is omitted because mainline Ollama does not support Vulkan. (Community forks exist; benchmarking a fork is not a fair representation of Ollama-as-shipped.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Date: 2026-06-01. Backend: Vulkan (LlamaStash via self-built &lt;code&gt;llama-server&lt;/code&gt; b9440 Vulkan; LM Studio via bundled &lt;code&gt;vulkan-avx2@2.18.0&lt;/code&gt; runtime). Same GGUF bytes across tools.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;small (E2B Q4)&lt;/th&gt;
&lt;th&gt;mid (31B Q4)&lt;/th&gt;
&lt;th&gt;large_dense (27B Q8)&lt;/th&gt;
&lt;th&gt;large_moe (35B-A3B Q8)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaStash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;101.2 / 55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.8 / 671&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.5 / 196&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50.7 / 72&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio 2.18.0&lt;/td&gt;
&lt;td&gt;93.6 / 191&lt;/td&gt;
&lt;td&gt;7.1 / 2307&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.0 / 801&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;38.4 / 227&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each cell is decode tok/s / TTFT ms on &lt;code&gt;chat_turn&lt;/code&gt; in &lt;code&gt;normalized&lt;/code&gt; mode. Same-day rerun on a clean memory baseline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falwcb85vq997q39e4x0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falwcb85vq997q39e4x0n.png" alt="AMD APU Vulkan addendum (LlamaStash vs LM Studio, chat_turn defaults)" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vulkan addendum read-out&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LlamaStash wins decode on three of four sizes&lt;/strong&gt;: small (101.2 vs 93.6, +8%), mid (10.8 vs 7.1, +52%), large_moe (50.7 vs 38.4, +32%). LM Studio takes large_dense by 7% (8.0 vs 7.5); its defaults pick something better than LlamaStash's stock defaults for that one model. The harness logs every effective flag if you want to dig into why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio's Vulkan mid number got worse since May.&lt;/strong&gt; It used to be 11.6 tok/s decode and 1463 ms to first token; today's LM Studio with its bundled vulkan-avx2 2.18.0 runtime lands 7.1 and 2307. That is just what LM Studio ships now on this hardware, and it reproduced across two runs (33 GiB and 105 GiB free RAM). Worth flagging, because anyone on an older LM Studio version will see different numbers than this table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First token is the wider gap.&lt;/strong&gt; LlamaStash stays under 200 ms on small, large_dense, and large_moe. LM Studio sits at 191-2307 ms across the board, from its OpenAI shim and just-in-time model loading. On short-request agent work, that adds up fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Combined view (all tools × backends × modes)
&lt;/h4&gt;

&lt;p&gt;For readers who want the whole comparison in one place, the next two charts overlay HIP/ROCm and Vulkan side by side, in both &lt;code&gt;defaults&lt;/code&gt; and &lt;code&gt;normalized&lt;/code&gt; mode, across all four model sizes and all four tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fne7etirm1ckj4gms7w0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fne7etirm1ckj4gms7w0h.png" alt="AMD APU combined: chat_turn" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc53tyn3zbldtj2gekavg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc53tyn3zbldtj2gekavg.png" alt="AMD APU combined: agent_decode" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Light mauve / dark mauve = HIP/ROCm defaults / normalized. Light teal / dark teal = Vulkan defaults / normalized. Em-dashes mark cells not run (raw and Ollama on Vulkan) or backend-init crashes (LM Studio on HIP for &lt;code&gt;mid&lt;/code&gt;, &lt;code&gt;large_dense&lt;/code&gt;, &lt;code&gt;large_moe&lt;/code&gt;). &lt;code&gt;agent_decode&lt;/code&gt; is the longer-context workload (256-token target vs 64 for &lt;code&gt;chat_turn&lt;/code&gt;); the relative ordering across tools holds.&lt;/p&gt;

&lt;h4&gt;
  
  
  Source map (AMD APU HIP cells)
&lt;/h4&gt;

&lt;p&gt;Per-cell provenance, since this is the table the audience will pick apart first.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Source JSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LS / raw / Ollama&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch/2026-05-23-335deaf47ccf.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LS / raw / Ollama&lt;/td&gt;
&lt;td&gt;mid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch/2026-05-24-135416-0f23808f9b5f.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LS / raw / Ollama&lt;/td&gt;
&lt;td&gt;large_dense&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch-clean70w/2026-05-24-191126-0f23808f9b5f.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LS / raw / Ollama&lt;/td&gt;
&lt;td&gt;large_moe&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch/2026-05-24-150131-0f23808f9b5f.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch/2026-05-23-335deaf47ccf.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio&lt;/td&gt;
&lt;td&gt;mid / large_dense / large_moe&lt;/td&gt;
&lt;td&gt;crash: no HIP data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LS / LMS Vulkan addendum&lt;/td&gt;
&lt;td&gt;all sizes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runs/deepu-flowz13-arch-vulkan-clean-2026-06-01/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Apple Silicon
&lt;/h3&gt;

&lt;p&gt;Hardware: Apple M1, 16 GiB unified, Metal. Model: Qwen2.5-0.5B-Instruct Q4_K_M. Date: 2026-05-27.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chat_turn&lt;/code&gt; decode tok/s / TTFT ms, both modes shown side-by-side because the defaults-vs-normalized story matters here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;defaults&lt;/th&gt;
&lt;th&gt;normalized&lt;/th&gt;
&lt;th&gt;Engine notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaStash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.0 / 15&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;92.2 / 21&lt;/td&gt;
&lt;td&gt;local Metal (Homebrew b9330)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;raw &lt;code&gt;llama-server&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;92.3 / 19&lt;/td&gt;
&lt;td&gt;91.5 / 21&lt;/td&gt;
&lt;td&gt;local Metal (Homebrew b9330)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio&lt;/td&gt;
&lt;td&gt;88.0 / 68&lt;/td&gt;
&lt;td&gt;88.7 / 67&lt;/td&gt;
&lt;td&gt;bundled Metal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama 0.24.0&lt;/td&gt;
&lt;td&gt;79.1 / 101&lt;/td&gt;
&lt;td&gt;80.1 / 102&lt;/td&gt;
&lt;td&gt;bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds2tmcov50gmn6eh8s9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds2tmcov50gmn6eh8s9s.png" alt="Apple M1 decode tok/s by tool, defaults vs normalized" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the whole story in one paragraph. With matched flags (ctx 4096, all GPU layers, flash-attention on, batch and ubatch 512), LlamaStash and raw &lt;code&gt;llama-server&lt;/code&gt; are within 1% of each other. The wrapper has nothing to add when both sides start from the same flags. Out of the box they pull apart, because LlamaStash's &lt;a href="https://github.com/llamastash/llamastash/blob/main/src/launch/defaults_table.rs" rel="noopener noreferrer"&gt;defaults table&lt;/a&gt; turns on all GPU layers and flash-attention for Qwen on Metal, two things upstream leaves off. That puts the chat_turn cell +7.3% ahead on decode (99.0 vs 92.3) and 4 ms faster to first token. You get that without knowing which flags matter on your hardware.&lt;/p&gt;

&lt;p&gt;Ollama lands at 79.1 tok/s and 101 ms out of the box, about 14% slower decode than raw llama-server, and 5.3× the time to first token. The first-token gap is the one you actually feel in interactive use, and it compounds for an agent making many short requests.&lt;/p&gt;

&lt;p&gt;The other workloads (&lt;code&gt;agent_decode&lt;/code&gt;, &lt;code&gt;rag_prefill&lt;/code&gt;, &lt;code&gt;parallel_4&lt;/code&gt;) track the same shape. Two cells worth pulling out before they get buried.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama's RAG prefill takes 2,849 ms to first token&lt;/strong&gt;, against 40-86 ms for LlamaStash, raw llama-server, and LM Studio. That is 52× the direct path on the same hardware, for the same 8K-token document. The other three cache the document after the first request; Ollama re-reads it every time by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama's four-way parallel run: 314.6 tok/s total, but 1,352 ms to first token per stream.&lt;/strong&gt; Ollama posts the highest total throughput by queueing the four requests, but each user waits 35× longer for their first token than on LlamaStash (38 ms). Total throughput is the wrong thing to optimize for here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full per-workload tables and the LM Studio reasoning-parser caveat: &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/macos-m1-final-report.md" rel="noopener noreferrer"&gt;Apple M1 final report&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  NVIDIA
&lt;/h3&gt;

&lt;p&gt;Hardware: NVIDIA RTX 3050 Ti Laptop (Ampere, 4 GiB VRAM), Intel i9-11900H, 63 GiB RAM. Model: gemma-3-4b-it Q3_K_M. Date: 2026-05-28.&lt;/p&gt;

&lt;p&gt;The NVIDIA platform has two backends worth measuring side-by-side: CUDA (driver-provided) and Vulkan (vendor-neutral). Both come from the same upstream llama.cpp commit (&lt;code&gt;b9360&lt;/code&gt;); the comparison is across engines, not across builds. &lt;code&gt;chat_turn&lt;/code&gt; decode tok/s / TTFT ms, &lt;code&gt;defaults&lt;/code&gt; mode.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;CUDA&lt;/th&gt;
&lt;th&gt;Vulkan&lt;/th&gt;
&lt;th&gt;Engine notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaStash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;41.1 / 74&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42.0 / 113&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;b9360 CUDA self-built (&lt;code&gt;sm_86&lt;/code&gt;) and b9360 Vulkan prebuilt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;raw &lt;code&gt;llama-server&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;36.6 / 110&lt;/td&gt;
&lt;td&gt;37.5 / 148&lt;/td&gt;
&lt;td&gt;same b9360 binaries, invoked directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LM Studio 2.16.0&lt;/td&gt;
&lt;td&gt;48.7 / 318&lt;/td&gt;
&lt;td&gt;48.3 / 308&lt;/td&gt;
&lt;td&gt;LMS-bundled CUDA / Vulkan runtimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama 0.24.0&lt;/td&gt;
&lt;td&gt;40.7 / 120&lt;/td&gt;
&lt;td&gt;42.0 / 115&lt;/td&gt;
&lt;td&gt;bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkijvqoz60yy52vqqq4tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkijvqoz60yy52vqqq4tn.png" alt="NVIDIA RTX 3050 Ti decode tok/s by tool, CUDA vs Vulkan, defaults" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three findings from this matrix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Out of the box, LlamaStash leads raw &lt;code&gt;llama-server&lt;/code&gt; by 12-16% on decode&lt;/strong&gt;, on both CUDA and Vulkan, same b9360 binary, same model, same hardware. But the visible defaults for gemma3 on NVIDIA only turn on all GPU layers (gemma is not on the flash-attention list, so the overlay does not enable it here). A 12-16% gap is wider than all-GPU-layers alone should buy. Maybe raw llama-server's CUDA build defaults to fewer GPU layers; maybe the auto-fit context picks a different size than raw's default; maybe there is another knob outside the visible command line. &lt;strong&gt;The gap is real in the data, but I have not pinned down the cause.&lt;/strong&gt; With matched flags, LlamaStash and raw collapse to within 0.5 tok/s of each other, which confirms the wrapper itself adds nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vulkan decode beats CUDA decode in 26 of 28 comparable cells&lt;/strong&gt;, median +5%, range -7% to +27%. This is a 4 GiB Ampere card running a Q3 4B model, a memory-bandwidth-bound job where Vulkan's kernels have caught up. The old "CUDA always wins on NVIDIA" rule is not universal. Suite A and Suite C here were Vulkan-only; a CUDA run of Suite A is a follow-up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two cross-tool data points from the broader matrix worth pulling forward.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama's Vulkan RAG prefill never finished.&lt;/strong&gt; Both runs together burned about 1.5 hours before I gave up. Ollama on Vulkan is simply not usable for document-RAG on this hardware. The CUDA rows at least finish, but slowly: 3,422 ms and 3,712 ms to first token, against 52-61 ms for LlamaStash and raw on the same document. That is an order-of-magnitude gap you cannot buy your way out of with faster hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio wins on decode, loses on first token.&lt;/strong&gt; It posts the highest decode in six of eight out-of-the-box cells. With matched flags everyone converges to the same engine baseline, so that lead is smarter defaults, not a faster engine. But its first token sits at 300-830 ms across every workload, an order of magnitude worse than the direct llama-server path (74-189 ms). On NVIDIA that first-token tax is even worse than on AMD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reproducing this on NVIDIA needs two local bench-harness patches that are not yet upstream (forwarding &lt;code&gt;LLAMASTASH_LLAMA_SERVER&lt;/code&gt; to &lt;code&gt;llamastash start&lt;/code&gt; and using &lt;code&gt;Path.absolute()&lt;/code&gt; instead of &lt;code&gt;Path.resolve()&lt;/code&gt; for the proxy model-id fallback). Both are documented in the &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/linux-nvidia-final.md" rel="noopener noreferrer"&gt;NVIDIA report&lt;/a&gt;, which also carries the open question about the 12-16% out-of-the-box gap above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verdict: Suite B
&lt;/h3&gt;

&lt;p&gt;Three findings pull through every platform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;With matched flags, LlamaStash and raw &lt;code&gt;llama-server&lt;/code&gt; are indistinguishable.&lt;/strong&gt; The wrapper is honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out of the box, LlamaStash's defaults make it a little faster&lt;/strong&gt; by turning on all GPU layers plus flash-attention where the model supports it (Qwen, Llama on Metal and CUDA). On the AMD APU the upstream ROCm defaults are already good, so the two match. On the Mac Qwen run the defaults buy +7.3% decode over raw, about what you would expect from turning on flash-attention. On NVIDIA the gap is wider than the visible flags explain, an open question, covered above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama and LM Studio each have a structural weakness that follows them across hardware.&lt;/strong&gt; Ollama's problem is RAG prefill: fine on tiny inputs, catastrophic when it has to re-read a document (52× on the Mac, 60×+ on NVIDIA CUDA, never finishing on NVIDIA Vulkan). LM Studio pays a steady 300 ms to 1.5 s first-token tax from its OpenAI shim everywhere, with smart defaults that win some decode cells on small models and lose on bigger ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The picture is clearest if you want a simple takeaway. If you care about throughput, run raw &lt;code&gt;llama-server&lt;/code&gt; or LlamaStash; they are the same engine. If you want raw &lt;code&gt;llama-server&lt;/code&gt; plus a TUI, plus a CLI, plus a daemon, plus an OpenAI-compatible proxy, plus a script-friendly agent surface, run LlamaStash. There is no measurable cost to that bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suite C: proxy overhead
&lt;/h2&gt;

&lt;p&gt;Last question. If you talk to the LlamaStash proxy at &lt;code&gt;http://127.0.0.1:11435/v1&lt;/code&gt; instead of &lt;code&gt;llama-server&lt;/code&gt; directly, does it cost you anything?&lt;/p&gt;

&lt;p&gt;The harness brings up one model, then sends the same chat request to both URLs, alternating one-for-one. Same &lt;code&gt;llama-server&lt;/code&gt; behind both.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Direct TTFT&lt;/th&gt;
&lt;th&gt;Proxy TTFT&lt;/th&gt;
&lt;th&gt;Delta TTFT&lt;/th&gt;
&lt;th&gt;Decode delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AMD APU (&lt;code&gt;chat_turn&lt;/code&gt;, small, 15 reps)&lt;/td&gt;
&lt;td&gt;52.37 ms&lt;/td&gt;
&lt;td&gt;52.82 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.45 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple M1 (Qwen2.5-0.5B Q4, 5 reps)&lt;/td&gt;
&lt;td&gt;31.4 ms&lt;/td&gt;
&lt;td&gt;30.8 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.6 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+1.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA RTX 3050 Ti Laptop (Vulkan, 4 reps)&lt;/td&gt;
&lt;td&gt;111.0 ms&lt;/td&gt;
&lt;td&gt;111.6 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.57 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−0.20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh38evav1mmpx7st0iv8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh38evav1mmpx7st0iv8c.png" alt="Suite C proxy overhead: decode delta % (top) and TTFT delta ms (bottom) across AMD APU, Apple M1, RTX 3050 Ti" width="799" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Top: decode delta % (proxy − direct). Bottom: TTFT delta ms (proxy − direct). The advisory thresholds from Suite A (±0.5% decode, ±30 ms TTFT) are drawn for context; the TTFT panel is zoomed in to ±1.6 ms because the actual deltas are sub-millisecond.&lt;/p&gt;

&lt;p&gt;The first-token difference is under a millisecond on every platform, inside run-to-run noise. The Mac row even shows the proxy 0.6 ms &lt;em&gt;faster&lt;/em&gt; than direct, which is just noise. The loopback round-trip is sub-millisecond and disappears in the overall number. Decode is unchanged, because the proxy is not in the streaming path after the first byte.&lt;/p&gt;

&lt;p&gt;Full numbers and method: &lt;a href="https://github.com/llamastash/llamastash/tree/main/docs/benchmarks/proxy" rel="noopener noreferrer"&gt;docs/benchmarks/proxy/&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means in practice
&lt;/h2&gt;

&lt;p&gt;Pulling the three suites together.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The wrapper is honest.&lt;/strong&gt; LlamaStash spawns the same binary you would run by hand, with the same flags. With matched flags there is no measurable throughput cost on AMD APU, Apple Silicon, or NVIDIA, on every metric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out of the box you get good defaults for free.&lt;/strong&gt; The defaults turn on all GPU layers plus flash-attention where the model supports it. On the Mac Qwen run that is +7.3% decode over raw llama-server's stock defaults. On AMD the upstream defaults are already good and the two match. On NVIDIA the gap is wider than the visible flags explain (open question above). The right way to read this is not "LlamaStash is faster than llama.cpp." It is "LlamaStash gives most people the tuned defaults they would otherwise have to discover themselves."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy is free.&lt;/strong&gt; Sub-millisecond on every platform. Nothing on the timescale an LLM works at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama is slower&lt;/strong&gt; than raw llama.cpp on decode, and structurally worse on repeated-document RAG everywhere I measured. The gap is biggest on the AMD APU and the RTX 3050 Ti Laptop, smaller on the Mac.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio is fast where its bundled engine is well-tuned, slow where it falls back,&lt;/strong&gt; and always pays a first-token tax that hurts short-request agent work more than long-form generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can measure your own setup.&lt;/strong&gt; Do not take my hardware as yours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a single sentence: LlamaStash gives you the speed of raw &lt;code&gt;llama-server&lt;/code&gt;, plus defaults that pick the right GPU-layer and flash-attention settings for you, with a real tool wrapped around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats and limits
&lt;/h2&gt;

&lt;p&gt;This benchmark intentionally does not cover a few things. Calling them out so I'm not pretending they are settled.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quality is not measured here.&lt;/strong&gt; Decode throughput and TTFT are speed numbers, not quality numbers. Two tools can be at the same throughput and produce different outputs, especially on reasoning-mode prompts. A separate quality-first benchmark is in scope for a follow-up post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-backend determinism varies.&lt;/strong&gt; Different llama.cpp builds on different platforms produce different outputs for the same input, even at temperature 0. The methodology page explains why and what we control. Don't quote a single number; quote the full row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac and NVIDIA cover one model each.&lt;/strong&gt; The M1 16 GiB run used a 0.5 B small model, sized for the entry-level Apple Silicon target. The RTX 3050 Ti Laptop run used a 4 B Q3 model, sized for the 4 GiB VRAM ceiling. Larger Apple Silicon (M2 Pro, M3 Max, M4) and larger-VRAM NVIDIA hardware should re-run the full matrix; the harness is published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The NVIDIA 12-16% out-of-the-box gap is not fully explained.&lt;/strong&gt; The visible defaults for gemma3 on NVIDIA only turn on all GPU layers (gemma is not flash-attention-eligible). The measured gap is bigger than that one flag should buy. A follow-up should log and compare the full effective command line on both sides before anyone quotes the number as marketing. The Suite A (overhead) and Suite C (proxy) results on NVIDIA do not depend on this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVIDIA CUDA Suite A and Suite C used Vulkan only.&lt;/strong&gt; The overhead and proxy measurements on the RTX 3050 Ti Laptop used the Vulkan binary. The proxy is engine-agnostic and expected to give the same result on CUDA, but a CUDA-lane Suite A certification has not been run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVIDIA reproduction requires two local bench-harness patches&lt;/strong&gt; that are not yet upstream. Both are documented at the bottom of the &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/linux-nvidia-final.md" rel="noopener noreferrer"&gt;NVIDIA report&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CUDA vs Vulkan binaries were built differently&lt;/strong&gt; on NVIDIA. CUDA was a self-built &lt;code&gt;cmake -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86&lt;/code&gt; against b9360; Vulkan was the prebuilt b9360 release asset. Same upstream commit, but compiler flags and link-time options can differ. Interpret the CUDA vs Vulkan comparison as "these two specific builds on this hardware" rather than "CUDA vs Vulkan in general."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama and LM Studio versions move.&lt;/strong&gt; Ollama 0.24.0 and LM Studio 2.16.0 were the latest stable releases at run time. Newer versions will move the numbers. Rerun against current versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio version not captured on M1.&lt;/strong&gt; The &lt;code&gt;lms version&lt;/code&gt; CLI returned an ANSI banner instead of a semver string; the desktop app was current as of 2026-05-27.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The prompt-processing rate on rag_prefill is inflated&lt;/strong&gt; when the cache hits, because it divides the prompt tokens by the cached ~50 ms instead of the real cold-prefill time. On rag_prefill, trust decode and first-token, not the prompt rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio's rag_prefill decode is blank&lt;/strong&gt; on the small models. Its reasoning-mode parser eats the token count, and the benchmark's output cap leaves at most one real token to measure. First-token is still valid there; decode is dropped from those cells.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single hardware points are not the population.&lt;/strong&gt; Strix Halo, M1 16 GiB, and the RTX 3050 Ti Laptop are specific machines on specific power budgets. A different SoC, a different TDP, a different driver will move numbers. The publishable claim is "this is what these three machines do today." The harness lets you produce the same claim for your own machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproducing this
&lt;/h2&gt;

&lt;p&gt;Both suites are maintainer-run. Nothing in CI fires them. To run them yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/llamastash/llamastash
&lt;span class="nb"&gt;cd &lt;/span&gt;llamastash

&lt;span class="c"&gt;# Suite B (cross-tool)&lt;/span&gt;
make bench-end-to-end &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;    &lt;span class="c"&gt;# print the planned matrix first&lt;/span&gt;
make bench-end-to-end

&lt;span class="c"&gt;# Suite A (overhead)&lt;/span&gt;
make bench-overhead

&lt;span class="c"&gt;# Suite C (proxy)&lt;/span&gt;
make bench-proxy &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--model&lt;/span&gt; &amp;lt;path/to/model.gguf&amp;gt;

&lt;span class="c"&gt;# Pivot existing JSONs into the headline summary&lt;/span&gt;
make bench-table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prerequisites, per-backend gotchas, and honored environment variables: &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/methodology.md" rel="noopener noreferrer"&gt;docs/benchmarks/methodology.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Drop new host directories into &lt;a href="https://github.com/llamastash/llamastash/blob/main/docs/benchmarks/runs/" rel="noopener noreferrer"&gt;docs/benchmarks/runs/&lt;/a&gt; and re-render. No central database, no schema migration dance.&lt;/p&gt;

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

&lt;p&gt;The release post made a claim. This post backs it up, with one addition I did not see coming.&lt;/p&gt;

&lt;p&gt;LlamaStash is a wrapper around &lt;code&gt;llama-server&lt;/code&gt;, and the only honest kind of wrapper is one that adds no measurable overhead. I measured it. It does not. The whole architecture is built around staying out of llama-server's way, and the numbers say it works: with matched flags, the two are the same speed on every platform.&lt;/p&gt;

&lt;p&gt;The defaults are a side note worth keeping. Turning on all GPU layers and flash-attention (where the model supports it) at startup is there to save you from discovering which flags matter on your hardware. The Mac Qwen run shows that buys +7.3% decode over raw llama-server's stock defaults, same hardware and same binary, about what flash-attention on Qwen Metal should give. NVIDIA shows an even wider gap that the visible flags do not fully explain; the NVIDIA section above is honest about it, and a follow-up will dig into the cause.&lt;/p&gt;

&lt;p&gt;The cross-tool comparison turned out more interesting than the overhead claim. Ollama is consistently behind raw llama.cpp on decode, and its repeated-document RAG first-token is a structural problem across hardware. LM Studio's Vulkan path is well-tuned on some hardware and worth respecting, but its first-token tax is consistent and real for agent work. LlamaStash gives you raw-llama.cpp speed with matched flags, a little better out of the box, and a real tool around it that does not cost you throughput.&lt;/p&gt;

&lt;p&gt;If you want to run this yourself on your own hardware, the harness is published, the methodology is published, the per-cell JSONs are checked in. The whole thing is reproducible.&lt;/p&gt;

&lt;p&gt;The right tool for the right job. The right tool is whichever one you measured.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://bsky.app/profile/deepu105.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llamacpp</category>
      <category>benchmark</category>
      <category>llm</category>
    </item>
    <item>
      <title>My fully offline AI-assisted Linux development machine</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Mon, 11 May 2026 20:47:59 +0000</pubDate>
      <link>https://dev.to/deepu105/my-fully-offline-ai-assisted-linux-development-machine-3lnl</link>
      <guid>https://dev.to/deepu105/my-fully-offline-ai-assisted-linux-development-machine-3lnl</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://deepu.tech/my-fully-offline-ai-assisted-linux-development-machine/" rel="noopener noreferrer"&gt;deepu.tech&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One of my most popular posts of all time was when I wrote about &lt;a href="https://deepu.tech/my-beautiful-linux-development-environment/" rel="noopener noreferrer"&gt;my beautiful Linux development machine in 2019&lt;/a&gt;. I followed that up in 2021 with &lt;a href="https://deepu.tech/my-beautiful-linux-development-environment-2021/" rel="noopener noreferrer"&gt;my sleek and modern Linux development machine&lt;/a&gt;. Since then, a lot has changed in my setup.&lt;/p&gt;

&lt;p&gt;I moved from Fedora and KDE to a mostly vanilla &lt;a href="https://archlinux.org/" rel="noopener noreferrer"&gt;Arch Linux&lt;/a&gt; setup. I moved from a traditional desktop environment to &lt;a href="https://github.com/YaLTeR/niri" rel="noopener noreferrer"&gt;niri&lt;/a&gt;, a scrolling Wayland compositor. And of course, like every developer out there, my workflow now has AI in it. But this time, I wanted something a bit different: AI-assisted development that can run fully offline on my own machine.&lt;/p&gt;

&lt;p&gt;Yes, local AI coding on Linux. What is not to love here?&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g" class="crayons-story__hidden-navigation-link"&gt;Introducing LlamaStash: a zero-overhead, terminal-native llama.cpp launcher&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/deepu105" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" alt="deepu105 profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/deepu105" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Deepu K Sasidharan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Deepu K Sasidharan
                
              
              &lt;div id="story-author-preview-content-3802990" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/deepu105" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Deepu K Sasidharan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 2&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g" id="article-link-3802990"&gt;
          Introducing LlamaStash: a zero-overhead, terminal-native llama.cpp launcher
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llamacpp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llamacpp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/localllm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;localllm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rust"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rust&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/deepu105/introducing-llamastash-a-zero-overhead-terminal-native-llamacpp-launcher-4d2g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;This is not a tutorial on how to reproduce every single bit of my setup. My full personal configuration is private because it has too much machine-specific and personal stuff. But I'm making a stripped-down public version with the bare minimum needed for Arch, niri, DMS, OpenCode, and llama.cpp at &lt;a href="https://github.com/deepu105/archdots" rel="noopener noreferrer"&gt;deepu105/archdots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is more about the current shape of my Linux development machine and why I ended up with this stack.&lt;/p&gt;

&lt;p&gt;This is my primary machine for all of the below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust, JavaScript, TypeScript, Java, Go, Python, Linux, and web development&lt;/li&gt;
&lt;li&gt;Running multiple web applications locally&lt;/li&gt;
&lt;li&gt;Running Docker containers and local Kubernetes clusters&lt;/li&gt;
&lt;li&gt;Kubernetes, Terraform, and cloud CLI work&lt;/li&gt;
&lt;li&gt;Writing, blogging, presentations, and demos&lt;/li&gt;
&lt;li&gt;Heavy browser usage&lt;/li&gt;
&lt;li&gt;E-mail, chat, and video conferencing&lt;/li&gt;
&lt;li&gt;Screen recording, screenshots, video editing, and light media work&lt;/li&gt;
&lt;li&gt;Running local LLMs for coding, refactoring, and code review&lt;/li&gt;
&lt;li&gt;Testing AI tools without sending every prompt to a cloud provider&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Machine configuration
&lt;/h2&gt;

&lt;p&gt;The configuration of the machine is quite crucial for this setup. Running a browser, a few IDEs, Docker, terminals, and local LLMs is not exactly a light workload.&lt;/p&gt;

&lt;p&gt;My current machine is an &lt;a href="https://rog.asus.com/laptops/rog-flow/rog-flow-z13-2025/" rel="noopener noreferrer"&gt;ASUS ROG Flow Z13&lt;/a&gt; 2025 model. It is a weird little beast. It is technically a tablet, but it has enough CPU, GPU, and memory to behave like a mobile workstation.&lt;/p&gt;

&lt;p&gt;Here is the current setup.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: ASUS ROG Flow Z13 GZ302EA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processor&lt;/strong&gt;: AMD Ryzen AI Max+ 395, 16 cores and 32 threads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graphics&lt;/strong&gt;: AMD Radeon 8060S integrated GPU with 40 compute units&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: 128GB unified memory. I have assigned 64GB to the GPU and 64GB to the CPU. This is configurable in the BIOS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: 2TB NVMe SSD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in display&lt;/strong&gt;: 13-inch, 2560x1600, 180Hz&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External displays&lt;/strong&gt;: 34-inch 3440x1440 monitor and 27-inch 2560x1440 monitor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camera&lt;/strong&gt;: Razer Kiyo Pro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard and mouse&lt;/strong&gt;: Keychron K2 and Logitech MX Vertical&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The memory is the most interesting part here. For normal development work, 32GB is still fine and 64GB is great. But for local AI work, memory changes everything. A 27B quantized model, a large context window, Docker, Chrome, and an editor can happily eat memory like there is no tomorrow.&lt;/p&gt;

&lt;p&gt;Having that much unified memory means the machine can run a useful local coding model without feeling like a science experiment. That is a big deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating system
&lt;/h2&gt;

&lt;p&gt;I praised Fedora in the previous posts, and I still think Fedora is one of the best Linux distributions for most developers. Updates are smooth, new packages land often, and it mostly stays out of the way.&lt;/p&gt;

&lt;p&gt;But this time I went with vanilla &lt;a href="https://archlinux.org/" rel="noopener noreferrer"&gt;Arch Linux&lt;/a&gt;. So yes, I use Arch btw! 😉 I know, rolling release and all that. I have been using Linux long enough to know what I was signing up for.&lt;/p&gt;

&lt;p&gt;The main reason was simple: I wanted the latest kernel, &lt;a href="https://www.mesa3d.org/" rel="noopener noreferrer"&gt;Mesa&lt;/a&gt;, &lt;a href="https://rocm.docs.amd.com/" rel="noopener noreferrer"&gt;ROCm&lt;/a&gt;-adjacent bits, Wayland tools, and desktop packages without waiting for the next distro release. New hardware like the Flow Z13 usually benefits from being closer to the bleeding edge. Arch gives me that. Well, OK, I also fell in love with the sexy new compositors like niri and Hyprland, and Arch is a great way to run those without waiting for backports. I started with Hyprland, but I ended up liking niri better for my workflow, and Arch made it easy to switch and experiment.&lt;/p&gt;

&lt;p&gt;My installation is still fairly boring, and I mean that as a compliment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://btrfs.readthedocs.io/" rel="noopener noreferrer"&gt;Btrfs&lt;/a&gt; for root, home, cache, and log subvolumes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.gnu.org/software/grub/" rel="noopener noreferrer"&gt;GRUB&lt;/a&gt; for boot&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Morganamilo/paru" rel="noopener noreferrer"&gt;paru&lt;/a&gt; for pacman and AUR packages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/linuxmint/timeshift" rel="noopener noreferrer"&gt;Timeshift&lt;/a&gt; and &lt;code&gt;grub-btrfs&lt;/code&gt; for snapshots&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pipewire.org/" rel="noopener noreferrer"&gt;PipeWire&lt;/a&gt; for audio&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://networkmanager.dev/" rel="noopener noreferrer"&gt;NetworkManager&lt;/a&gt; for networking&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, &lt;a href="https://distrobox.it/" rel="noopener noreferrer"&gt;Distrobox&lt;/a&gt;, &lt;a href="https://flatpak.org/" rel="noopener noreferrer"&gt;Flatpak&lt;/a&gt;, and a bit of &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; where it makes sense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also use &lt;a href="https://github.com/topgrade-rs/topgrade" rel="noopener noreferrer"&gt;Topgrade&lt;/a&gt; to keep the system updated. My private config even wires it into &lt;a href="https://github.com/AvengeMedia/DankMaterialShell" rel="noopener noreferrer"&gt;DankMaterialShell&lt;/a&gt;, so I can see available updates from the bar and trigger an update for everything on the system from pacman/AUR, brew, cargo, npm, VS Code plugins, Docker images, and so on in Kitty.&lt;/p&gt;

&lt;p&gt;Again, quite simple, at least in my eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop environment, or lack of one
&lt;/h2&gt;

&lt;p&gt;This is probably the biggest change from my previous setup. I no longer run GNOME or KDE as my main desktop. I use &lt;a href="https://github.com/YaLTeR/niri" rel="noopener noreferrer"&gt;niri&lt;/a&gt;, which is a scrollable tiling Wayland compositor.&lt;/p&gt;

&lt;p&gt;If you have not used niri, the workflow is quite different from a regular tiling window manager. Instead of forcing everything into a fixed grid, windows live in columns and you scroll horizontally across them. It sounds odd until it clicks. Once it clicks, it feels very natural on ultrawide monitors and laptop displays. I especially love the touchpad gestures for switching workspaces and moving windows around. It is a very fluid way to manage windows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99u31eu09r3cxpruphmm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99u31eu09r3cxpruphmm.png" alt="Scrolling workspaces in niri" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My current session looks like this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Login manager&lt;/strong&gt;: &lt;a href="https://github.com/sddm/sddm" rel="noopener noreferrer"&gt;SDDM&lt;/a&gt; on Wayland&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window manager&lt;/strong&gt;: niri 26.04 on Wayland&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shell&lt;/strong&gt;: &lt;a href="https://github.com/AvengeMedia/DankMaterialShell" rel="noopener noreferrer"&gt;DankMaterialShell&lt;/a&gt;, usually called DMS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal&lt;/strong&gt;: &lt;a href="https://sw.kovidgoyal.net/kitty/" rel="noopener noreferrer"&gt;Kitty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme&lt;/strong&gt;: &lt;a href="https://catppuccin.com/" rel="noopener noreferrer"&gt;Catppuccin&lt;/a&gt; Macchiato everywhere. I just love this color theme.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts&lt;/strong&gt;: Inter Variable and JetBrainsMono Nerd Font&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Niri and DMS
&lt;/h2&gt;

&lt;p&gt;Niri gives me the compositor. DMS gives me the desktop shell pieces that I would otherwise have to stitch together myself.&lt;/p&gt;

&lt;p&gt;DMS replaces a lot of the usual Wayland desktop plumbing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top bar&lt;/li&gt;
&lt;li&gt;Application launcher, Control center, Media controls&lt;/li&gt;
&lt;li&gt;Clipboard manager, Notification center&lt;/li&gt;
&lt;li&gt;Process and system monitoring&lt;/li&gt;
&lt;li&gt;Power menu, Lock screen&lt;/li&gt;
&lt;li&gt;Screenshot and screen recording plugins&lt;/li&gt;
&lt;li&gt;Wallpaper and theme switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of stuff where I do not want to maintain five different tools and a bunch of scripts if one project does the job well enough. DMS is still young, but it is already quite useful, especially with niri. It's also quite extensible, and I have already started adding tools that I want. For example, a locally saved &lt;a href="https://github.com/deepu105/dms-dank-todo" rel="noopener noreferrer"&gt;TODO widget&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Flow Z13 also needs some special handling. I have fixes for ASUS hotkeys, touchpad behavior, keyboard backlight, Thunderbolt rescans, and Wi-Fi quirks in my private config. The public archdots repo will only carry the reusable bits. This is Linux on new hardware, so of course there are quirks. What is a Linux experience without glitches, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Development tools
&lt;/h2&gt;

&lt;p&gt;My development tools are still mostly boring, in a good way. These are subjective choices, and they do not matter as long as you are comfortable with your tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0g0qciweticq8jefldy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0g0qciweticq8jefldy.png" alt="My development tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shell&lt;/strong&gt;: I use &lt;a href="https://www.zsh.org/" rel="noopener noreferrer"&gt;Zsh&lt;/a&gt; with &lt;a href="https://github.com/zdharma-continuum/zinit" rel="noopener noreferrer"&gt;zinit&lt;/a&gt;, &lt;a href="https://github.com/romkatv/powerlevel10k" rel="noopener noreferrer"&gt;Powerlevel10k&lt;/a&gt;, &lt;a href="https://github.com/ajeetdsouza/zoxide" rel="noopener noreferrer"&gt;zoxide&lt;/a&gt;, and &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt;. I still use a bunch of aliases for Git, Docker, package management, Jekyll, and local AI tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal&lt;/strong&gt;: I use &lt;a href="https://sw.kovidgoyal.net/kitty/" rel="noopener noreferrer"&gt;Kitty&lt;/a&gt;. I have tabs, splits, clipboard bindings, quick access terminal, and a few custom keybindings. It is fast, it works well on Wayland, and it does not get in my way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editors&lt;/strong&gt;: I use &lt;a href="https://neovim.io/" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt; with &lt;a href="https://www.lazyvim.org/" rel="noopener noreferrer"&gt;LazyVim&lt;/a&gt; as my default editor. I still use &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; depending on the project and what I am testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toolchains&lt;/strong&gt;: I use &lt;a href="https://sdkman.io/" rel="noopener noreferrer"&gt;SDKMAN!&lt;/a&gt; for JDKs, &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;NVM&lt;/a&gt; for Node.js, &lt;a href="https://rustup.rs/" rel="noopener noreferrer"&gt;rustup&lt;/a&gt; for Rust, &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt;, Go, Python, Deno, and the usual Linux build tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DevOps&lt;/strong&gt;: Docker, Docker Compose, kubectl, kdash, Terraform, Distrobox, and so on. Some come from pacman or AUR, some from Homebrew, and some from language-specific installers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline AI-assisted development
&lt;/h2&gt;

&lt;p&gt;Now to the fun part.&lt;/p&gt;

&lt;p&gt;I use cloud AI tools as well, and they are useful. But I also wanted a setup where I can code with an AI assistant without sending code, prompts, logs, or half-written ideas to a remote API. Not because every project is secret, but because local-first tooling is a good capability to have especially in a world that's heading towards techno oligarchy.&lt;/p&gt;

&lt;p&gt;My current stack is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; as the coding agent&lt;/li&gt;
&lt;li&gt;A custom &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; build with HIP support. This is much more performant than Ollama or LM Studio.&lt;/li&gt;
&lt;li&gt;A local &lt;code&gt;llama-server&lt;/code&gt; exposing an OpenAI-compatible API on &lt;code&gt;127.0.0.1:18080/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Qwen3.6 27B and Gemma 4 31B for local models, depending on the task. I use different quantization levels based on need, from 4-bit to 8-bit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; for managing models and for offline chat.&lt;/li&gt;
&lt;li&gt;ROCm/HIP acceleration on the Radeon 8060S&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/grinev/opencode-telegram-bot" rel="noopener noreferrer"&gt;opencode-telegram-bot&lt;/a&gt; for managing OpenCode sessions remotely from Telegram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is my OpenCode provider config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://opencode.ai/config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"llama.cpp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@ai-sdk/openai-compatible"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llama.cpp ROCm (local)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"baseURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:18080/v1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"qwen3-6-27b-q8-0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Qwen3.6 27B Q8_0 (local ROCm)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;262144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"qwen3-6-27b-q6-k"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"qwen3-6-27b-q4-k-m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gemma-4-31b-it-q4-k-m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gemma-4-31b-it-q8-0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"openrouter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"moonshotai/kimi-k2.6"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kimi K2.6 (OpenRouter backup)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;262144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"deepseek/deepseek-v4-pro"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeepSeek V4 Pro (OpenRouter backup)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;384000&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I start the local model server with an alias.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That points to a small script. It lets me pick a GGUF model, context size, and reasoning mode. It remembers the last choice, so most of the time I just start it and get going.&lt;/p&gt;

&lt;p&gt;The default model and context right now are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qwen3.6-27B-Q8_0.gguf - 256k context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a quick &lt;code&gt;llama-bench&lt;/code&gt; comparison of the local models on my machine. The numbers are tokens per second with ROCm, full GPU offload, flash attention, f16 KV cache, a 4096-token prompt, a 256-token generation, and 3 repetitions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Quantization&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Prompt tokens/s&lt;/th&gt;
&lt;th&gt;Generation tokens/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;15.40 GiB&lt;/td&gt;
&lt;td&gt;260.06&lt;/td&gt;
&lt;td&gt;10.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q6_K&lt;/td&gt;
&lt;td&gt;20.56 GiB&lt;/td&gt;
&lt;td&gt;279.37&lt;/td&gt;
&lt;td&gt;8.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;26.62 GiB&lt;/td&gt;
&lt;td&gt;260.12&lt;/td&gt;
&lt;td&gt;7.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B IT&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;17.39 GiB&lt;/td&gt;
&lt;td&gt;209.57&lt;/td&gt;
&lt;td&gt;9.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B IT&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;30.38 GiB&lt;/td&gt;
&lt;td&gt;202.31&lt;/td&gt;
&lt;td&gt;6.19&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full context is 256k tokens. Here is a benchmark with full context for the Qwen variants.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Quantization&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Prompt+Generation tokens/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;15.40 GiB&lt;/td&gt;
&lt;td&gt;67.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q6_K&lt;/td&gt;
&lt;td&gt;20.56 GiB&lt;/td&gt;
&lt;td&gt;65.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.6 27B&lt;/td&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;26.62 GiB&lt;/td&gt;
&lt;td&gt;64.34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running Qwen3.6 27B Q8_0 with 256k context in reasoning mode loads around 70% of the GPU memory in my setup and gives around 64 tokens/s for prompt+generation. That is quite good for a local model with that much context.&lt;/p&gt;

&lt;p&gt;The llama.cpp build is also automated with a small script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-S&lt;/span&gt; /mnt/work/Workspace/llms/llama.cpp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-B&lt;/span&gt; /mnt/work/Workspace/llms/llama.cpp/build-hip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-G&lt;/span&gt; Ninja &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DGGML_HIP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DAMDGPU_TARGETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gfx1151 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DCMAKE_BUILD_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Release

cmake &lt;span class="nt"&gt;--build&lt;/span&gt; /mnt/work/Workspace/llms/llama.cpp/build-hip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; Release &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; llama-server llama-bench
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server runs like this under the hood.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ROCBLAS_USE_HIPBLASLT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alias&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$alias_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 18080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ctx-size&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--n-gpu-layers&lt;/span&gt; 999 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--flash-attn&lt;/span&gt; on &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-mmap&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache-type-k&lt;/span&gt; f16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache-type-v&lt;/span&gt; f16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--batch-size&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ubatch-size&lt;/span&gt; 512 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reasoning&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$reasoning&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the server is running, OpenCode talks to it like it would talk to any OpenAI-compatible provider. The difference is that the whole loop stays on my machine.&lt;/p&gt;

&lt;p&gt;It's very elegant IMO!&lt;/p&gt;

&lt;p&gt;I do not only use local models, though. For complex tasks, I also use frontier models through &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;, mostly &lt;a href="https://openrouter.ai/moonshotai/kimi-k2.6" rel="noopener noreferrer"&gt;Kimi K2.6&lt;/a&gt; and &lt;a href="https://openrouter.ai/deepseek/deepseek-v4" rel="noopener noreferrer"&gt;DeepSeek V4&lt;/a&gt;. Occasionally I use Copilot CLI and at work, I use Claude Code as well.&lt;/p&gt;

&lt;p&gt;For the harness, I prefer OpenCode. I do not see any noticeable performance difference between Claude Code and OpenCode with Kimi or DeepSeek for the kind of coding tasks I do, which is mostly open source projects in Rust and TypeScript. That might vary for other people, of course, but for me OpenCode has been quite good and I especially prefer its UX over others. I'm trying &lt;a href="https://github.com/earendil-works/pi" rel="noopener noreferrer"&gt;Pi&lt;/a&gt; on the side as well to see if I keep it in the mix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local AI coding matters to me
&lt;/h2&gt;

&lt;p&gt;Local AI is not a replacement for everything. The best hosted models are still better for many tasks, especially when you need maximum reasoning quality or very fast responses. But local models have their own sweet spot.&lt;/p&gt;

&lt;p&gt;For me, the advantages are clear.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Education/Fun&lt;/strong&gt;: Running a local model is a great way to learn about how these models work, what kind of hardware they need, and how to optimize them. It is a fun hobby in itself. This has already paid off in terms of understanding the AI landscape better and being able to troubleshoot issues that come up in my work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: I can use it on private code, half-baked ideas, local logs, and experiments without thinking about what leaves the machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline use&lt;/strong&gt;: It works without internet. That is great while travelling or when the network is flaky.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost control&lt;/strong&gt;: No token anxiety for long coding sessions and experiments. I can run it as long as I want without worrying about bills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hackability&lt;/strong&gt;: I can change model, context, cache type, build flags, server parameters, and client config whenever I want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are tradeoffs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model quality depends heavily on the model and quantization. Qwen3.6 27B, for example, has been good in most tasks I threw at it so far, but once context keeps growing the model compacts and starts hallucinating. So for long context tasks, I use Kimi or DeepSeek. I did some non-scientific benchmarks by giving Claude Opus 4.7, Qwen3.6 27B, and Kimi K2.6 the same set of prompts, and the difference in quality was noticeable but not too dramatic. The local model at times even did better than others, like catching stuff in review that Claude Opus missed.&lt;/li&gt;
&lt;li&gt;Large context is useful, but it is not free since it affects tokens/second. Qwen3.6 27B at 256k context is noticeably slow compared to a hosted frontier model. Like maybe twice or three times slower.&lt;/li&gt;
&lt;li&gt;ROCm on fresh AMD hardware can be a bit of a moving target but thanks to current models, a fix for any issue has been a prompt away.&lt;/li&gt;
&lt;li&gt;Some agent workflows are slower locally than with hosted models.&lt;/li&gt;
&lt;li&gt;You have to care about model storage, updates, server flags, GPU memory, and cooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So no, I do not think everyone should run a local coding model. But if you enjoy owning your stack and you have the hardware for it, it is a very satisfying setup.&lt;/p&gt;

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

&lt;p&gt;My usual workflow is quite simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the local model server with &lt;code&gt;llamaServer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pick the model and context preset if I want to change it.&lt;/li&gt;
&lt;li&gt;Start &lt;code&gt;opencode&lt;/code&gt; in the repository and pick a model if I want to change it.&lt;/li&gt;
&lt;li&gt;Ask it to inspect the codebase before making changes.&lt;/li&gt;
&lt;li&gt;Let it edit, test, and iterate, while I review the changes using the &lt;a href="https://github.com/grinev/opencode-telegram-bot" rel="noopener noreferrer"&gt;opencode-telegram-bot&lt;/a&gt; remotely from Telegram.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For small tasks, I turn reasoning off because it makes tool-heavy work faster. For design questions, debugging, or code review, I turn reasoning on. The script makes that a prompt instead of forcing me to remember a long command.&lt;/p&gt;

&lt;p&gt;This is the kind of boring automation I like. It removes friction without hiding what is actually happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productivity and media tools
&lt;/h2&gt;

&lt;p&gt;Most of my productivity stack did not change much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser&lt;/strong&gt;: Google Chrome is still my primary browser. I also keep Firefox around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password management&lt;/strong&gt;: I use Bitwarden and a YubiKey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Communication&lt;/strong&gt;: Zoom, Signal, Telegram, and the usual suspects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen capture&lt;/strong&gt;: DMS screenshot plugin, screen recorder plugin, and OBS Studio when I need more control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images and video&lt;/strong&gt;: Gimp, Inkscape, Kdenlive, and a few Flatpak utilities like Upscayl and Buzz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File manager&lt;/strong&gt;: Dolphin, because KDE apps are still excellent even when KDE is not my main desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is still not perfect
&lt;/h2&gt;

&lt;p&gt;Of course, not everything is perfect. This is bleeding-edge Linux, on a new ASUS convertible, with a new AMD chip, a Wayland compositor, and a local AI stack. If everything worked perfectly on day one, I would be suspicious.&lt;/p&gt;

&lt;p&gt;Some current rough edges are below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suspend and wake-up glitches at times when I have my dock connected. Usually, the monitors refuse to wake up and I have to reboot or rescan Thunderbolt devices. I have a workaround for this, but it is still a bit annoying.&lt;/li&gt;
&lt;li&gt;Hibernate is not practical because of the large memory.&lt;/li&gt;
&lt;li&gt;Screen recording with OBS can be a bit tricky to set up and get right on Wayland, especially with niri. The built-in DMS screen recorder is good for quick captures but not for longer sessions or when I need more control. I'm still tweaking my OBS setup for this.&lt;/li&gt;
&lt;li&gt;The Flow Z13 has a cool transparent window on the back with RGB lights. It doesn't work under Linux. I'm planning to try and fix that with Qwen3.6 27B and OpenCode. It would be a complex hardware project that could test the model's ability.&lt;/li&gt;
&lt;li&gt;ROCm/HIP support on new integrated GPUs needs patience. I haven't had any issues lately, so maybe it has stabilized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are deal breakers for me. Most are either already fixed in my private config or on my TODO list.&lt;/p&gt;

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

&lt;p&gt;This is easily the most interesting Linux machine I have used so far. My 2019 setup was beautiful, my 2021 setup was sleek, and this one feels like a proper local-first AI development workstation.&lt;/p&gt;

&lt;p&gt;Vanilla Arch gives me the latest bits. Niri gives me a workflow that fits both the tiny built-in screen and my ultrawide monitor. DMS gives me the desktop polish without a full desktop environment. And OpenCode plus llama.cpp gives me an AI coding assistant that can run without the cloud.&lt;/p&gt;

&lt;p&gt;It is not the right setup for everyone. If you want a machine that never asks you to think about kernels, ROCm, compositor configs, or model files, this is probably not it. But for me, this is exactly the kind of developer machine that sparks joy.&lt;/p&gt;

&lt;p&gt;The right tool for the right job.&lt;/p&gt;

&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://bsky.app/profile/deepu105.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>archlinux</category>
      <category>development</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Passwordless Future: Passkeys for Developers</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Fri, 16 Feb 2024 15:53:42 +0000</pubDate>
      <link>https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54</link>
      <guid>https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://auth0.com/blog/webauthn-and-passkeys-for-developers/" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, first things first, why do we even need to go passwordless? After all, passwords have been around for over 1000 years, right? And we were all happily sharing our Netflix passwords.&lt;/p&gt;

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

&lt;p&gt;The most important reason to go passwordless would be the password problem. According to &lt;a href="https://www.verizon.com/business/resources/reports/dbir/2023/summary-of-findings/" rel="noopener noreferrer"&gt;Verizon's 2023 Data Breach Investigations Report&lt;/a&gt;, stolen credentials and phishing account for over 65% of all data breaches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi45g43xkozuk7yzjz101.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi45g43xkozuk7yzjz101.jpg" alt="Anime character depicting a hacker" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the password problems are human problems in my opinion. Because passwords rely on us humans to remember them and to not share them and do a bunch of other stuff. History has repeatedly, a quadrillion times, shown us that we are not good at doing these things. So it is an &lt;strong&gt;us&lt;/strong&gt; problem rather than the technology itself.&lt;/p&gt;

&lt;p&gt;These are the main problems with passwords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge-based&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;People can be socially engineered, quite easily, to divulge passwords, or other information that can be used to get to the password.&lt;/li&gt;
&lt;li&gt;In this day and age there are just too many passwords to remember. If passwords are easy to remember they are also easy to guess. Complex passwords are not easy to remember, so we end up reusing passwords.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Phishing&lt;/strong&gt;: Phishing websites can easily harvest passwords from even the most tech-savvy.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Remote Replay&lt;/strong&gt;: Accounts can be accessed remotely using harvested passwords.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Data Breach&lt;/strong&gt;: Applications become a target for data breaches when they store passwords.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Share and Reuse&lt;/strong&gt;: Sharing and reusing passwords makes them even more vulnerable.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Password management&lt;/strong&gt;: Passwords are not just a hassle for the end users, they are a hassle on the server side as well. Because

&lt;ul&gt;
&lt;li&gt;We need to build password recovery and reset flows.&lt;/li&gt;
&lt;li&gt;We need multi-factor authentication flows to secure them further.&lt;/li&gt;
&lt;li&gt;They need to be reset regularly in some use cases.&lt;/li&gt;
&lt;/ul&gt;


&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.amazonaws.com%2Fuploads%2Farticles%2Fdscwklzcx9p3ykb1afjl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdscwklzcx9p3ykb1afjl.jpg" alt="Problems with passwords" width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.forbes.com/sites/forbestechcouncil/2023/03/23/embracing-the-end-of-the-password-here-and-now" rel="noopener noreferrer"&gt;Did you know it could cost around 70$ to reset a password?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, password managers help with some aspects of this and everyone should use one. But they are still an overhead and not very convenient for everyone, especially non-tech folks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Passwordless?
&lt;/h2&gt;

&lt;p&gt;The obvious solution for the password problem is to go passwordless. So what exactly is passwordless?&lt;/p&gt;

&lt;p&gt;If you can verify a user's identity with something other than a password as the first factor of authentication, it is passwordless. We are doing this every day to unlock our phones and laptops using our fingerprints, faces, and so on.&lt;/p&gt;

&lt;p&gt;There are a few &lt;a href="https://auth0.com/blog/what-is-passwordless-authentication/" rel="noopener noreferrer"&gt;passwordless methods&lt;/a&gt; that you might have seen here and there. Like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Biometric authentication&lt;/li&gt;
&lt;li&gt;Magic links&lt;/li&gt;
&lt;li&gt;SMS/Email One-Time Password (OTP)&lt;/li&gt;
&lt;li&gt;Push notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But most of these methods are not secure enough to replace a password + Multi-Factor Authentication (MFA) combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Passkeys are a password replacement that provide faster, easier, and more secure sign-ins to websites and apps across a user’s devices. Unlike passwords, passkeys are resistant to phishing, are always strong, and are designed so that there are no shared secrets.&lt;/p&gt;

&lt;p&gt;— FIDO Alliance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where passkeys come into the picture. A secure passwordless future is the one offered by passkeys in my opinion. You probably already encountered passkeys since Google and GitHub have been rolling it out to all users recently. If you haven't set them up yet, you should!&lt;/p&gt;

&lt;p&gt;A passkey is a unique cryptographic key pair that allows you to access online services without using passwords. It is based on &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography" rel="noopener noreferrer"&gt;asymmetric public-key cryptography&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we dive deep into passkeys let's look at some of the underlying technologies that make passkeys possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public-key cryptography
&lt;/h3&gt;

&lt;p&gt;Asymmetric public key cryptography involves a pair of mathematically linked keys: a public key, which is shared openly, and a private key, kept secret by the owner.&lt;/p&gt;

&lt;p&gt;This key pair can be used for encryption. When a message is encrypted with the public key, only the corresponding private key can decrypt it, ensuring confidentiality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uwim78h59ptfqgsrwjt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uwim78h59ptfqgsrwjt.jpeg" alt="Encryption using Public-key cryptography" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same key pair can also be used for digital signatures. A message signed with a private key can be verified with the public key, authenticating the sender's identity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpm1oxidq8q7g5uacdbbh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpm1oxidq8q7g5uacdbbh.jpg" alt="Signature verification using Public-key cryptography" width="799" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Passkeys use the signature verification mechanism. These keys are generated using a cryptographic algorithm, such as RSA or ECC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticator
&lt;/h3&gt;

&lt;p&gt;An authenticator is a hardware or software entity that can create and store public-private key pairs which can be used for user registration and authentication. There are two types of authenticators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platform authenticators&lt;/strong&gt;: An authenticator built into a user's device. For example, TouchID and FaceID from Apple, smartphone authenticators, Windows Hello, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roaming authenticators&lt;/strong&gt;: A removable authenticator usable with any device the user is trying to sign in from. They are attached using USB, NFC, and/or Bluetooth. For example, security keys like &lt;a href="https://www.yubico.com/products/how-the-yubikey-works/" rel="noopener noreferrer"&gt;YubiKey&lt;/a&gt;, &lt;a href="https://cloud.google.com/titan-security-key" rel="noopener noreferrer"&gt;Google Titan&lt;/a&gt; and smartphones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FIDO
&lt;/h3&gt;

&lt;p&gt;FIDO stands for Fast IDentity Online. FIDO is a global authentication standard based on public key cryptography developed by the &lt;a href="https://fidoalliance.org/" rel="noopener noreferrer"&gt;FIDO Alliance&lt;/a&gt;. It aims to solve all our password problems. FIDO Credentials are cryptographic key pairs that can be used for authentication.&lt;/p&gt;

&lt;p&gt;Passkeys are made possible by the &lt;a href="https://fidoalliance.org/fido2/" rel="noopener noreferrer"&gt;FIDO2&lt;/a&gt; standard which is made up of Web Authentication (WebAuthn) and Client to Authenticator Protocol (CTAP).&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Authentication
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://webauthn.me/introduction" rel="noopener noreferrer"&gt;Web Authentication&lt;/a&gt; is a &lt;a href="https://www.w3.org/TR/webauthn/" rel="noopener noreferrer"&gt;W3C recommendation&lt;/a&gt; that lets a webpage use a set of JavaScript APIs to talk to authenticators.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvemcm0y58zygf08rlq1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvemcm0y58zygf08rlq1.jpg" alt="WebAuthn architecture" width="799" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The WebAuthn architecture consists of three main entities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authenticator&lt;/strong&gt;: Platform or roaming authenticators that let a user authenticate by confirming their presence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relying Party&lt;/strong&gt;: A server (custom implementation or an Identity Provider like Auth0) that requires authentication. It issues challenges and stores public keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client&lt;/strong&gt;: A client consists of the user's browser. The client relays information between an authenticator and a relying party.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client to Authenticator Protocol
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html" rel="noopener noreferrer"&gt;FIDO Client to Authenticator Protocol&lt;/a&gt; is used for communications with authenticators over a variety of transports like USB, NFC, and Bluetooth. It is used to send requests from WebAuthn to authenticators.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Passkeys are passwordless FIDO credentials implemented using WebAuthn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Passkeys were originally called FIDO Multi-Device Credentials implemented with the WebAuthn. But recently that definition has evolved to mean any passwordless FIDO credentials that are discoverable by the browser. Passkeys are still evolving and hence this could change as well. But for simplicity let's stick to passkeys as that is the term used by the FIDO Alliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of passkeys
&lt;/h3&gt;

&lt;p&gt;Passkeys have two variants. Synced passkeys (sometimes referred to as multi-device passkeys) and device-bound passkeys (sometimes referred to as hardware-bound passkeys).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxmb51hfftlasdvyk3jp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxmb51hfftlasdvyk3jp.jpg" alt="Passkey types" width="799" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Synced passkeys
&lt;/h4&gt;

&lt;p&gt;Synced passkeys have a better user experience since the private keys are end-to-end encrypted and synced to the cloud. For example, on the Apple ecosystem, the private key is synced on your iCloud Keychain and you can register on one device and log in to any synced Apple device. The same goes for the Google ecosystem using the Chrome browser and Google Password Manager. Or you can use a password manager like BitWarden or 1Password to store your passkeys.&lt;br&gt;
This kind of passkeys can be restored on new devices. But they are less secure than single device-bound passkeys since your private key is on the cloud and theoretically can be breached.&lt;/p&gt;
&lt;h4&gt;
  
  
  Device-bound passkeys
&lt;/h4&gt;

&lt;p&gt;In the device-bound passkeys, the private key stays on the device itself and you need to authenticate using the same authenticator used for registration. It is slightly less convenient but more secure than synced passkeys. The relying party must support registering multiple credentials for a user so that backup keys can be registered, which is a best practice for device-bound passkeys.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For example, I use a YubiKey with a fingerprint reader since I'm on Linux, and my passkeys are device-bound to that YubiKey. I don't get any roaming or backup benefits like in the Apple or Google ecosystem. But it's not a big deal, in my opinion, since I can register multiple YubiKeys as backup and use them on any device, and it's more secure and way more convenient than password + MFA.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  How does it work?
&lt;/h3&gt;

&lt;p&gt;Let's see how user registration and authentication work with passkeys.&lt;/p&gt;
&lt;h4&gt;
  
  
  User registration
&lt;/h4&gt;

&lt;p&gt;First, let's see how the registration flow works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczqp7jczw2b16kij43m6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczqp7jczw2b16kij43m6.jpg" alt="Passkey registration flow" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user begins the registration flow. The relying party provides a randomly generated challenge string.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;navigator.credentials.create()&lt;/code&gt; method of the WebAuthn API is invoked and the user provides approval using their authenticator.&lt;/li&gt;
&lt;li&gt;The authenticator creates a private-public key pair which is unique for the relying party's domain and the user. The private key is used to sign the challenge.

&lt;ul&gt;
&lt;li&gt;The private key is stored on the authenticator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For synced passkeys&lt;/strong&gt;, the private key is also synced to a cloud service for backup and roaming (This is the only place where synced passkeys differ from device-bound passkeys).&lt;/li&gt;
&lt;li&gt;An attestation object is created which contains the public key, signed challenge, credential ID, and certificate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The attestation object and other metadata are then passed to the relying party by the client-side implementation. The relying party verifies the signed challenge using the public key and registers the user by storing the public key and credential ID along with the user details.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  User authentication
&lt;/h4&gt;

&lt;p&gt;Now, let's see how the login flow works, which is quite similar except for the third step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbue2699a1zczijdhuvy3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbue2699a1zczijdhuvy3.jpg" alt="Passkey login flow" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user begins the login flow. The relying party provides a randomly generated challenge string.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;navigator.credentials.get()&lt;/code&gt; method of the WebAuthn API is invoked and the user provides approval using their authenticator.&lt;/li&gt;
&lt;li&gt;The authenticator retrieves the private keys for the relying party's domain name.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For synced passkeys&lt;/strong&gt;, if the device is new, the private key is synced from a cloud service if available (This is the only place where synced passkeys differ from device-bound passkeys).&lt;/li&gt;
&lt;li&gt;The user selects the private key for their username. The private key is used to sign the challenge.&lt;/li&gt;
&lt;li&gt;An assertion object is created which contains the signed challenge and credential ID.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The assertion object and other metadata are then passed to the relying party by the client-side implementation. The relying party verifies the signed challenge using the public key stored for the user and authenticates the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why Passkeys?
&lt;/h2&gt;

&lt;p&gt;Let's see why we need passkeys to replace passwords.&lt;/p&gt;

&lt;p&gt;Passkeys are superior to password + traditional OTP MFA in terms of security and usability and they are as secure and more convenient than password + FIDO MFA. Most importantly, you don’t have to remember anything (unless you are like me and forget your YubiKeys all the time).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hms1aoy4h98durgeo79.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hms1aoy4h98durgeo79.jpg" alt="password vs passkeys" width="799" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discoverable&lt;/strong&gt;: Passwords are knowledge-based but passkeys are discoverable credentials and the browser can autofill them for a service making it unnecessary for you to remember even usernames. It doesn’t rely on something you know, instead, it relies on something you have or something you are which is more secure from hacking and social engineering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phishing resistant&lt;/strong&gt;: Passkeys cannot be phished as they rely on public key cryptography and are bound to the domain name of the website, making it impossible to work on a spoofed website.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote attack resistant&lt;/strong&gt;: Passkeys rely on physical keys, like biometric sensors of platform authenticators or roaming authenticators like YubiKey, hence cannot be remotely breached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breach resistant&lt;/strong&gt;: The website only stores the public key of a user which is useless to an attacker on a data breach on the server side. This makes the server less attractive to hackers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not reusable and shareable*&lt;/strong&gt;: They cannot be reused as they are unique per service and user combination and cannot be shared.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;*&lt;/strong&gt;except for Apple which lets you share a passkey by air-dropping it 🤷🏽.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier management&lt;/strong&gt;: Passkeys are scalable. Synced passkeys are backed up and replicated across your devices by services like iCloud Keychain, Google Password Manager, Bitwarden, and so on. This makes recovery part of the platform rather than the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-device authentication&lt;/strong&gt;: Passkeys can also perform cross-device authentication regardless of ecosystem or platform. For example, you can simply use your Android phone as an authenticator for your Apple laptop.&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.amazonaws.com%2Fuploads%2Farticles%2Fg655vyqez7le2fqmd919.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg655vyqez7le2fqmd919.jpg" alt="Security and usability spectrum of passkeys" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The security of passkeys is way better than most other combinations. When it comes to user experience, though it is subjective, I think it outperforms all other combinations.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Does Passkeys Differ from WebAuthn Multi-Factor Authentication?
&lt;/h2&gt;

&lt;p&gt;Technically they are very similar since both are implemented with WebAuthn and in the case of device-bound passkeys they are even more similar. However, some differences set them apart.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passkeys are &lt;strong&gt;&lt;a href="https://www.w3.org/TR/webauthn-2/#discoverable-credential" rel="noopener noreferrer"&gt;Discoverable Credentials&lt;/a&gt;&lt;/strong&gt; and are entirely stored on the authenticator. This means for hardware keys like YubiKey, the private key is stored on the key itself and hence can only hold what its memory allows. They are client-side discoverable during authentication ceremonies and can be used in the &lt;a href="https://passkeys.dev/docs/reference/terms/#autofill-ui" rel="noopener noreferrer"&gt;autofill UI&lt;/a&gt; of the browser. WebAuthn/FIDO-based MFA implementations are &lt;strong&gt;Non-Discoverable Credentials&lt;/strong&gt; or &lt;strong&gt;&lt;a href="https://www.w3.org/TR/webauthn-2/#server-side-credential" rel="noopener noreferrer"&gt;Server-side Credentials&lt;/a&gt;&lt;/strong&gt; and hence are not client-side discoverable. They are stored server-side and the private key is encrypted and sent to the relying party, hence there is no storage limitation on the hardware key.&lt;/li&gt;
&lt;li&gt;WebAuthn MFA does not have a synced option, passkeys do.&lt;/li&gt;
&lt;li&gt;In the case of synced passkeys there are even more differences in terms of usability and security. For example, enrollment needs to be done only once and the private keys are synced to a cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important difference is that passkeys can be used as first-factor authentication whereas WebAuthn MFA can only be used as a second-factor after user registration with a password.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;There are still some challenges when it comes to passkeys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OS/Browser support&lt;/strong&gt;: It is dependent on the OS and Browser to implement the specs, and we all know how that turns out right? This means the support may not be uniform and can become fragmented and we might never have the same experience everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud vendor reliance&lt;/strong&gt;: It relies on companies like Apple, Google, and Microsoft to save the private keys in their cloud securely and protect them from breaches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise use cases&lt;/strong&gt;: Enterprise users might want more control and flexibility which could be a problem. For example, if an enterprise doesn’t allow iCloud or Google Chrome on their computer, synced passkeys will not work there, only device-bound passkeys will work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reset &amp;amp; Recovery&lt;/strong&gt;: There are no default recovery flows for device-bound passkeys, and applications might still need to implement recovery &amp;amp; reset flows to accommodate all use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://passkeys.dev/device-support/" rel="noopener noreferrer"&gt;Browser and OS compatibility&lt;/a&gt; is still catching up. As of writing, Chrome has the best support and the Apple ecosystem has the most seamless experience, especially for platform authenticators. Linux does not have any support for platform authenticators. Roaming authenticators have great support on all platforms.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Passkeys are the future of secure authentication. They are more secure and more convenient than passwords and traditional MFA. They are also more secure and more convenient than other passwordless methods like magic links, SMS/Email OTP, and push notifications. The wider adoption of passkeys could finally solve the password problem and make the internet a safer place.&lt;/p&gt;

&lt;p&gt;Passkeys are not just for the future, they are here now. You can start using them today. If you are a developer, you can start implementing them in your applications &lt;a href="https://developer.auth0.com/resources/labs/authentication/passkeys#go-beyond-passwords-with-passkeys" rel="noopener noreferrer"&gt;using Auth0&lt;/a&gt;. If you are a user, you can start using them on &lt;a href="https://passkeys.directory/" rel="noopener noreferrer"&gt;services that support them&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Check out this follow-up blog post.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c" class="crayons-story__hidden-navigation-link"&gt;A Passwordless Future: Passkeys for Java Developers&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/oktadev"&gt;
            &lt;img alt="Okta logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F129%2F9284e7ae-5b8a-49ab-89de-0e5ebe85847b.jpg" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/deepu105" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" alt="deepu105 profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/deepu105" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Deepu K Sasidharan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Deepu K Sasidharan
                
              
              &lt;div id="story-author-preview-content-1714905" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/deepu105" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Deepu K Sasidharan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/oktadev" class="crayons-story__secondary fw-medium"&gt;Okta&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 2 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c" id="article-link-1714905"&gt;
          A Passwordless Future: Passkeys for Java Developers
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/spring"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;spring&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/passkeys"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;passkeys&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webauthn"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webauthn&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;I hope that you found this article helpful. Here are some additional resources to learn more about WebAuthn and passkeys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnpasskeys.io" rel="noopener noreferrer"&gt;learnpasskeys.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webauthn.me" rel="noopener noreferrer"&gt;webauthn.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://passkeys.dev" rel="noopener noreferrer"&gt;passkeys.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fidoalliance.org/passkeys/" rel="noopener noreferrer"&gt;fidoalliance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/our-take-on-passkeys/" rel="noopener noreferrer"&gt;Our Take On Passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/activate-passkeys-let-users-log-in-without-password/" rel="noopener noreferrer"&gt;Activate Passkeys and Let Your Users Log in without a Password&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/how-to-explain-public-key-cryptography-digital-signatures-to-anyone/" rel="noopener noreferrer"&gt;How to Explain Public-Key Cryptography and Digital Signatures to Anyone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>passkeys</category>
      <category>webauthn</category>
    </item>
    <item>
      <title>A Passwordless Future: Passkeys for Java Developers</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Tue, 02 Jan 2024 15:39:21 +0000</pubDate>
      <link>https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c</link>
      <guid>https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-3f0c</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://auth0.com/blog/webauthn-and-passkeys-for-java-developers/" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This blog post is a continuation of my previous &lt;a href="https://auth0.com/blog/webauthn-and-passkeys-for-developers/" rel="noopener noreferrer"&gt;blog post on passkeys&lt;/a&gt;. In this post, you will learn how to implement passkeys using Auth0 and the WebAuthn4j library on your Java applications.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54" class="crayons-story__hidden-navigation-link"&gt;A Passwordless Future: Passkeys for Developers&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/oktadev"&gt;
            &lt;img alt="Okta logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F129%2F9284e7ae-5b8a-49ab-89de-0e5ebe85847b.jpg" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/deepu105" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" alt="deepu105 profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/deepu105" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Deepu K Sasidharan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Deepu K Sasidharan
                
              
              &lt;div id="story-author-preview-content-1763303" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/deepu105" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F178939%2F38a82d99-3a0c-4a47-84fc-76fff3144cda.png" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Deepu K Sasidharan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/oktadev" class="crayons-story__secondary fw-medium"&gt;Okta&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Feb 16 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54" id="article-link-1763303"&gt;
          A Passwordless Future: Passkeys for Developers
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/passkeys"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;passkeys&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webauthn"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webauthn&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;11&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/oktadev/a-passwordless-future-passkeys-for-java-developers-1f54#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;If you have not read the previous blog post, I would highly recommend you read it first to understand the basics of passkeys and WebAuthn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys
&lt;/h2&gt;

&lt;p&gt;A passkey is a unique cryptographic key pair that allows you to access online services without using passwords. It is based on &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography" rel="noopener noreferrer"&gt;asymmetric public-key cryptography&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Passkeys are passwordless FIDO credentials implemented using WebAuthn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Passkeys?
&lt;/h2&gt;

&lt;p&gt;Passkeys are superior to password + traditional OTP MFA in terms of security and usability and they are as secure and more convenient than password + FIDO MFA. Most importantly, you don’t have to remember anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hms1aoy4h98durgeo79.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hms1aoy4h98durgeo79.jpg" alt="password vs passkeys" width="799" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's See Passkeys in Action with Auth0
&lt;/h2&gt;

&lt;p&gt;Let's build a Spring Boot web app and secure it using passkeys with the help of Auth0 by Okta. You can find a sample app &lt;a href="https://a0.to/jfall-passkey" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; if you just want to try passkeys.&lt;/p&gt;

&lt;p&gt;Before you get started, you will need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Java 17 or higher. You can use &lt;a href="https://sdkman.io/" rel="noopener noreferrer"&gt;SDKMAN!&lt;/a&gt; to install Java if you don't have it already.&lt;/li&gt;
&lt;li&gt;  A free Auth0 account. &lt;a href="https://a0.to/blog_signup" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; if you don't have one already.&lt;/li&gt;
&lt;li&gt;  The Auth0 CLI. &lt;a href="https://github.com/auth0/auth0-cli#installation" rel="noopener noreferrer"&gt;Install&lt;/a&gt; the CLI if you don't have it and log in to your Auth0 account using the &lt;code&gt;auth0 login&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a Spring Boot application
&lt;/h3&gt;

&lt;p&gt;Create a new Spring Boot application using the &lt;a href="https://start.spring.io/" rel="noopener noreferrer"&gt;Spring Initializr&lt;/a&gt;. You can use the web version or the curl command below. Use the default for most of the options. For the dependencies, select &lt;code&gt;web&lt;/code&gt;, and &lt;code&gt;okta&lt;/code&gt;. For the build tool, select &lt;code&gt;Gradle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-G&lt;/span&gt; https://start.spring.io/starter.tgz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web,okta &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;baseDir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;passkey-demo &lt;span class="se"&gt;\&lt;/span&gt;
 | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzvf&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;web&lt;/code&gt; dependency provides Spring Web MVC with basic HTTP REST functionality.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;okta&lt;/code&gt; dependency provides the Okta Spring Boot Starter, which provides the required dependencies and configuration to add OIDC authentication to your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add a web controller
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Imports are omitted for brevity so make sure to import them using your IDE.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Open the created starter application in your favorite IDE. Add a simple web controller to the application. Create a new file &lt;code&gt;src/main/java/com/example/demo/HomeController.java&lt;/code&gt; with the following content:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&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;"/"&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;String&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@AuthenticationPrincipal&lt;/span&gt; &lt;span class="nc"&gt;OidcUser&lt;/span&gt; &lt;span class="n"&gt;user&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="s"&gt;"Hello, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFullName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This controller will handle requests to the &lt;code&gt;/&lt;/code&gt; path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you run the application using &lt;code&gt;./gradlew bootRun&lt;/code&gt;, you will see a login page from the Okta Spring Boot starter instead of your home screen. This is OK, and you will be able to configure this soon. You can comment out the &lt;code&gt;okta-spring-boot-starter&lt;/code&gt; dependency in the &lt;code&gt;build.gradle&lt;/code&gt; file if you want to run the application at this point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Enable passkeys on your Auth0 tenant
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your &lt;a href="https://manage.auth0.com" rel="noopener noreferrer"&gt;Auth0 Dashboard&lt;/a&gt; and navigate to &lt;strong&gt;Authentication&lt;/strong&gt; &amp;gt; &lt;strong&gt;Database&lt;/strong&gt; &amp;gt; &lt;strong&gt;Username-Password-Authentication&lt;/strong&gt;.

&lt;ol&gt;
&lt;li&gt;If the second tab says &lt;strong&gt;Authentication Methods&lt;/strong&gt;, your tenant supports passkeys, proceed to the next step.&lt;/li&gt;
&lt;li&gt;If the second tab says &lt;strong&gt;Password Policy&lt;/strong&gt;, your tenant doesn't support passkeys, &lt;a href="https://auth0.com/docs/get-started/auth0-overview/create-tenants" rel="noopener noreferrer"&gt;Create a new tenant&lt;/a&gt; and proceed to the next step.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Navigate to &lt;strong&gt;Authentication&lt;/strong&gt; &amp;gt; &lt;strong&gt;Authentication Profile&lt;/strong&gt; and select &lt;strong&gt;Identifier First&lt;/strong&gt;. &lt;strong&gt;Save&lt;/strong&gt; your changes.&lt;/li&gt;

&lt;li&gt;Navigate to &lt;strong&gt;Authentication&lt;/strong&gt; &amp;gt; &lt;strong&gt;Database&lt;/strong&gt; &amp;gt; &lt;strong&gt;Username-Password-Authentication&lt;/strong&gt; and select the &lt;strong&gt;Authentication Methods&lt;/strong&gt; tab and enable &lt;strong&gt;Passkey&lt;/strong&gt;.&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configure OIDC Authentication with Auth0
&lt;/h3&gt;

&lt;p&gt;Configure the application to use Auth0 as the Identity Provider (IdP). You can use the Auth0 CLI to create a new authorization server application. Run the following command to create a new application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;auth0 apps create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Spring Boot Passkeys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Spring Boot Example"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; regular &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--callbacks&lt;/span&gt; http://localhost:8080/login/oauth2/code/okta &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--logout-urls&lt;/span&gt; http://localhost:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reveal-secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;--type&lt;/code&gt; option specifies that you use a regular web application.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;--callbacks&lt;/code&gt; option specifies the callback URL for the application.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;--logout-urls&lt;/code&gt; option specifies the logout URL for the application.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;--reveal-secrets&lt;/code&gt; option will display the client secret in the output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use the &lt;code&gt;auth0 apps update&lt;/code&gt; command to update the application with the callback and logout URLs.&lt;/p&gt;

&lt;p&gt;Note down the Auth0 issuer (for example, &lt;code&gt;https://dev-12345678.us.auth0.com/&lt;/code&gt;), &lt;code&gt;CLIENT ID&lt;/code&gt;, and &lt;code&gt;CLIENT SECRET&lt;/code&gt; from the output. You will use these values in the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the Spring Boot application
&lt;/h3&gt;

&lt;p&gt;Configure the application by creating an &lt;code&gt;application.properties&lt;/code&gt; file in the applications root folder with the following content:&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="c1"&gt;# trailing `/` is important for issuer URI&lt;/span&gt;
&lt;span class="s"&gt;okta.oauth2.issuer=https://&amp;lt;AUTH0_domain&amp;gt;/&lt;/span&gt;
&lt;span class="s"&gt;okta.oauth2.client-id=&amp;lt;AUTH0_clientId&amp;gt;&lt;/span&gt;
&lt;span class="s"&gt;okta.oauth2.client-secret=&amp;lt;AUTH0_clientSecret&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;application.properties&lt;/code&gt; file to the &lt;code&gt;.gitignore&lt;/code&gt; file to avoid committing the secrets to the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the application
&lt;/h3&gt;

&lt;p&gt;To run the application, execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application should start successfully. Navigate to &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser. You will be redirected to the Auth0 universal login page for authentication.&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Sign up&lt;/strong&gt; link to register a new user. Enter any email address and click &lt;strong&gt;Continue&lt;/strong&gt;. You will now be prompted to register a passkey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvuz6c89thzca7aoky7q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvuz6c89thzca7aoky7q.jpg" alt="Registration Screen" width="738" height="865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a passkey using your platform authenticator or roaming authenticator like YubiKey. Once you have registered a passkey, you should be redirected back to the application and see the welcome message.&lt;/p&gt;

&lt;p&gt;Open a new incognito window and navigate to &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;. You will be prompted to sign in using your passkey. Once you have signed in, you will see the welcome message.&lt;/p&gt;

&lt;p&gt;Isn't that cool? You just implemented passkeys in your Spring Boot application with so little effort thanks to Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebAuthn for Java
&lt;/h2&gt;

&lt;p&gt;Though Web Authentication’s user experience is a client-side implementation using JavaScript, the backend or Relying party can be a Java server. Ideally using an IdP like Auth0 would be the best option since it takes care of all the heavy lifting for you. But if you want to implement it yourself and walk the harder path, you can use one of the below libraries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/webauthn4j/webauthn4j" rel="noopener noreferrer"&gt;WebAuthn4j&lt;/a&gt;: A 100% FIDO2 conformant library with support for all attestation formats and validation. It is used by Keycloak and Spring Security.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/Yubico/java-webauthn-server" rel="noopener noreferrer"&gt;java-webauthn-server&lt;/a&gt;: A library from Yubico that supports many attestation format. But it is not 100% FIDO2 conformant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WebAuthn4j with Spring Security in Action
&lt;/h2&gt;

&lt;p&gt;Let's look at a simple Spring Boot application that uses passkeys for authentication without using an IdP. You can find the sample app &lt;a href="https://a0.to/jfall-webauthn" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clone and run the application
&lt;/h3&gt;

&lt;p&gt;Start by cloning the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/deepu105/webauthn4j-spring-boot-passkeys-demo.git

&lt;span class="nb"&gt;cd &lt;/span&gt;webauthn4j-spring-boot-passkeys-demo
./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;. You should see the below screen. Try registering a new user with passkeys and log in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl07chhyxssojchc6wm1o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl07chhyxssojchc6wm1o.jpg" alt="Sign up Screen" width="663" height="877"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  WebAuthn4j configuration
&lt;/h3&gt;

&lt;p&gt;Let's look at some of the important parts of the application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;webauthn4j-spring-security-core&lt;/code&gt; dependency, in &lt;code&gt;build.gradle&lt;/code&gt; file, provides the Spring Security integration for WebAuthn4j.&lt;/li&gt;
&lt;li&gt;  The required beans for WebAuthn4j are configured in &lt;code&gt;src/main/java/com/example/demo/config/WebSecurityBeanConfig.java&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;InMemoryWebAuthnAuthenticatorManager&lt;/code&gt; is used to keep things simple but it means authenticator data is lost on application restart. For production use, it is better to implement the &lt;code&gt;WebAuthnAuthenticatorManager&lt;/code&gt; interface and persist credential IDs for users.&lt;/li&gt;
&lt;li&gt;  WebAuthn4j is configured using the standard Spring Security filter chain in &lt;code&gt;src/main/java/com/example/demo/config/WebSecurityConfig.java&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableWebSecurity&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;WebSecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationManager&lt;/span&gt; &lt;span class="n"&gt;authenticationManager&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// WebAuthn Login&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebAuthnLoginConfigurer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;webAuthnLogin&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultSuccessUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureHandler&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login error"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRedirect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login?error=Login failed: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="o"&gt;})&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attestationOptionsEndpoint&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WebAuthn4J Passkeys Demo"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pubKeyCredParams&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="c1"&gt;// supported algorithms for cryptography&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PublicKeyCredentialParameters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PublicKeyCredentialType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;COSEAlgorithmIdentifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ES256&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PublicKeyCredentialParameters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PublicKeyCredentialType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;COSEAlgorithmIdentifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RS256&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attestation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AttestationConveyancePreference&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DIRECT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uvm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credProps&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extensionProviders&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertionOptionsEndpoint&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extensionProviders&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 'publickey-credentials-get *' allows getting WebAuthn credentials to all nested browsing contexts (iframes) regardless of their origin.&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;permissionsPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"publickey-credentials-get *"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="c1"&gt;// Disable "X-Frame-Options" to allow cross-origin iframe access&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;frameOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Customizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDefaults&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;disable&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Authorization&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authz&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;authz&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;permitAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/signup"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;permitAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;anyRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getWebExpressionAuthorizationManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@webAuthnSecurityExpression.isWebAuthnAuthenticated(authentication)"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exceptionHandling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eh&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;eh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accessDeniedHandler&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accessDeniedException&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Access denied"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accessDeniedException&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRedirect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}));&lt;/span&gt;

        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticationManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authenticationManager&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// As WebAuthn has its own CSRF protection mechanism (challenge), CSRF token is disabled here&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csrf&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;csrfTokenRepository&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CookieCsrfTokenRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withHttpOnlyFalse&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignoringRequestMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/webauthn/**"&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;http&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="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The endpoints are configured in &lt;code&gt;src/main/java/com/example/demo/web/WebAuthnSampleController.java&lt;/code&gt;. The &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/login&lt;/code&gt; endpoints are quite simple and self-explanatory. The &lt;code&gt;/signup&lt;/code&gt; endpoint handles the WebAuthn registration request using WebAuthn4j. The request is first validated using &lt;code&gt;WebAuthnRegistrationRequestValidator&lt;/code&gt; and then the authenticator is created using &lt;code&gt;WebAuthnAuthenticatorManager&lt;/code&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;@Controller&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;WebAuthnSampleController&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="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/signup"&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;String&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@ModelAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"userForm"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;UserCreateForm&lt;/span&gt; &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BindingResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RedirectAttributes&lt;/span&gt; &lt;span class="n"&gt;redirectAttributes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasErrors&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"errorMessage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Your input needs correction."&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User input validation failed."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;VIEW_LOGIN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;WebAuthnRegistrationRequestValidationResponse&lt;/span&gt; &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;registrationRequestValidator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClientDataJSON&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttestationObject&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransports&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClientExtensions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebAuthnException&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;WebAuthnAuthenticationException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"errorMessage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Authenticator registration request validation failed. Please try again."&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WebAuthn registration request validation failed."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;VIEW_LOGIN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userCreateForm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUsername&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authenticator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebAuthnAuthenticatorImpl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"authenticator"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttestationObject&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAuthenticatorData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttestedCredentialData&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttestationObject&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttestationStatement&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttestationObject&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAuthenticatorData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getSignCount&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransports&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRegistrationExtensionsClientOutputs&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;registrationRequestValidationResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttestationObject&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAuthenticatorData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getExtensions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;webAuthnAuthenticatorManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAuthenticator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"errorMessage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Registration failed. The user may already be registered."&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Registration failed."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;VIEW_LOGIN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"errorMessage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Registration failed by unexpected error."&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="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Registration failed."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;VIEW_LOGIN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"successMessage"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"User registration successful. Please login."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;VIEW_LOGIN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client-side configuration
&lt;/h3&gt;

&lt;p&gt;The file &lt;code&gt;src/main/resources/templates/login.html&lt;/code&gt; handles login and sign-up. The login button will invoke the &lt;code&gt;navigator.credentials.get()&lt;/code&gt; API and the register button will invoke the &lt;code&gt;navigator.credentials.create()&lt;/code&gt; API. The buttons submit the corresponding forms with the input data in them. All inputs except the &lt;code&gt;username&lt;/code&gt; field are hidden as their data will be set using JavaScript.&lt;/p&gt;

&lt;p&gt;WebAuthn4j exposes &lt;code&gt;/webauthn/attestation/options&lt;/code&gt; endpoint in the application to fetch the registration options. Some of the option parameters need to be decoded from base64URL. The &lt;a href="https://github.com/deepu105/base64url-arraybuffer" rel="noopener noreferrer"&gt;base64url-arraybuffer&lt;/a&gt; library is used for this. The options are then passed to the &lt;code&gt;navigator.credentials.create()&lt;/code&gt; API. The response from the API is then updated to the form fields and submitted to the &lt;code&gt;/signup&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userHandle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webauthn/attestation/options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;excludeCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excludeCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;})),&lt;/span&gt;
            &lt;span class="na"&gt;authenticatorSelection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;requireResidentKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discouraged&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clientDataJSON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientDataJSON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attestationObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attestationObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clientExtensions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClientExtensionResults&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error:%s, Message:%s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebAuthn4j exposes &lt;code&gt;/webauthn/assertion/options&lt;/code&gt; endpoint in the application to fetch the authentication options. Some of the option parameters need to be decoded from base64URL. The options are then passed to the &lt;code&gt;navigator.credentials.get()&lt;/code&gt; API. The response from the API is then updated to the form fields and submitted to the &lt;code&gt;/login&lt;/code&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webauthn/assertion/options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;optionsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preferred&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credentialId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loginClientDataJSON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientDataJSON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticatorData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticatorData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loginClientExtensions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClientExtensionResults&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error:%s, Message:%s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You have now learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  How to implement passkeys using an IdP like Auth0.&lt;/li&gt;
&lt;li&gt;  You also learned how to configure the application to use Auth0 as the Identity Provider and how to configure Auth0 for passkey support.&lt;/li&gt;
&lt;li&gt;  Roll your own passkey solution using WebAuthn4j and Spring Security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Passkeys are the future of authentication. They are more secure and convenient than traditional passwords and OTPs. Though you could roll your own solution using WebAuthn4j, it is always better to use an IdP like Auth0 to handle the heavy lifting for you and take care of all the security best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;I hope that you found this article helpful. Here are some additional resources to learn more about WebAuthn and passkeys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://learnpasskeys.io" rel="noopener noreferrer"&gt;learnpasskeys.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://webauthn.me" rel="noopener noreferrer"&gt;webauthn.me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://passkeys.dev" rel="noopener noreferrer"&gt;passkeys.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://fidoalliance.org/passkeys/" rel="noopener noreferrer"&gt;fidoalliance&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>passkeys</category>
      <category>webauthn</category>
    </item>
    <item>
      <title>Deploy Secure Spring Boot Microservices on Amazon EKS Using Terraform and Kubernetes</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Thu, 23 Nov 2023 14:34:44 +0000</pubDate>
      <link>https://dev.to/oktadev/deploy-secure-spring-boot-microservices-on-amazon-eks-using-terraform-and-kubernetes-3f0d</link>
      <guid>https://dev.to/oktadev/deploy-secure-spring-boot-microservices-on-amazon-eks-using-terraform-and-kubernetes-3f0d</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://auth0.com/blog/terraform-eks-java-microservices/" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When it comes to infrastructure, public clouds are the most popular choice these days, and Amazon Web Services (AWS) is the go-to option. If you are working with microservices, orchestrating their deployments becomes essential. Kubernetes is the de-facto choice for orchestrating microservices, and most public cloud providers offer managed Kubernetes services. For AWS, the managed Kubernetes service is Amazon Elastic Kubernetes Service (EKS).&lt;/p&gt;

&lt;p&gt;Deploying and managing microservices on the public cloud is not without its challenges. Each cloud service has its own complexities. Among them, Amazon EKS is one of the most flexible but also one of the most difficult Kubernetes services to use. EKS utilizes clever orchestrations on top of other AWS services like EC2, EBS, and more.&lt;/p&gt;

&lt;p&gt;To run a microservice stack on EKS, you will need to spend extra time and effort setting it up and managing it. This is where infrastructure as code (IaC) tools like &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; come in handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;So here is what you will learn to do today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffold a Java microservice stack using JHipster, Spring Boot, and Spring Cloud&lt;/li&gt;
&lt;li&gt;Create an EKS cluster, Virtual Private Cloud (VPC), subnets, and required Kubernetes add-ons using Terraform on AWS&lt;/li&gt;
&lt;li&gt;Set up Terraform scripts for OpenID Connect (OIDC) authentication for the microservice stack using Auth0 by Okta&lt;/li&gt;
&lt;li&gt;Build and deploy the microservice stack to the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://portal.aws.amazon.com/billing/signup" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt; with the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/security_iam_id-based-policy-examples.html" rel="noopener noreferrer"&gt;IAM permissions to create EKS clusters&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS CLI &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;installed&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html" rel="noopener noreferrer"&gt;configured&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed and configured&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.terraform.io/downloads" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; CLI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sdkman.io/usage" rel="noopener noreferrer"&gt;Java 11+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.github.io/auth0-cli/" rel="noopener noreferrer"&gt;Auth0 CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Optional] &lt;a href="https://www.jhipster.tech/installation/" rel="noopener noreferrer"&gt;JHipster&lt;/a&gt; CLI&lt;/li&gt;
&lt;li&gt;[Optional] &lt;a href="https://github.com/kdash-rs/kdash" rel="noopener noreferrer"&gt;KDash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Terraform, Why Not Cloud Formation?
&lt;/h2&gt;

&lt;p&gt;At this point, the first question that might pop up in your mind would be, "Why not use &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt;?". It's a good question; after all, CloudFormation is built by AWS and hence sounds like an excellent solution to manage AWS resources. But anyone who has tried both CloudFormation and Terraform will probably tell you to forget that CloudFormation even exists. I think CloudFormation is far more complex and less developer-friendly than Terraform. You also need to write a lot more boilerplate with CloudFormation in YAML or JSON. Yikes! In contrast, Terraform is elegant and concise, and the syntax is easier to read and write. It's cross-platform, developer-friendly, and does not require a lot of ramp-up time.&lt;/p&gt;

&lt;p&gt;Okay, now that we have that sorted, let's dive into the steps to deploy our microservice stack on EKS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold a Java Microservice Stack Using JHipster
&lt;/h2&gt;

&lt;p&gt;To start, you will scaffold a Java microservice stack using &lt;a href="https://www.jhipster.tech" rel="noopener noreferrer"&gt;JHipster&lt;/a&gt;, Spring Boot, and Consul. JHipster is an excellent tool to generate a microservice stack with Spring Boot, Angular/React/Vue.js, and other modern frameworks. You can use another microservice stack if you want. If you prefer using the same application as in this demo, then you can either scaffold it using JHipster &lt;a href="https://www.jhipster.tech/jdl/intro" rel="noopener noreferrer"&gt;JDL&lt;/a&gt; or clone the sample repository from &lt;a href="https://github.com/oktadev/auth0-jhipster-k8s-eks-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Here is how you can scaffold your microservice stack using JHipster:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fda1i2cc1xlz4jeehmctx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fda1i2cc1xlz4jeehmctx.jpg" alt="JHipster microservice architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Scaffold the microservice stack using JHipster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use JHipster version &lt;strong&gt;7.9.3&lt;/strong&gt; or &lt;strong&gt;8.0.0&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;&lt;span class="nb"&gt;mkdir &lt;/span&gt;jhipster-microservice-stack
&lt;span class="nb"&gt;cd &lt;/span&gt;jhipster-microservice-stack
&lt;span class="c"&gt;# download the JDL file.&lt;/span&gt;
jhipster download https://raw.githubusercontent.com/oktadev/auth0-jhipster-k8s-eks-microservices-example/main/apps.jdl
&lt;span class="c"&gt;# Update the `dockerRepositoryName` property to use your Docker Repository URI/Name.&lt;/span&gt;
&lt;span class="c"&gt;# scaffold the apps.&lt;/span&gt;
jhipster jdl apps.jdl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Clone the sample repository&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;git clone https://github.com/oktadev/auth0-jhipster-k8s-eks-microservices-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In either case, remember to change the Docker repository name in the JDL file and Kubernetes manifests to match your Docker repository.&lt;/p&gt;

&lt;p&gt;The JHipster scaffolded sample application has a gateway application and two microservices. It uses &lt;a href="https://www.consul.io/" rel="noopener noreferrer"&gt;Consul&lt;/a&gt; for service discovery and centralized configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an EKS Cluster Using Terraform
&lt;/h2&gt;

&lt;p&gt;Now let us move on to the important part of the tutorial. Creating an EKS cluster in AWS is not as straightforward as in other cloud platforms. You need to also create a lot more resources for everything to work correctly without surprises. You will be using a bunch of Terraform providers to help with this, and you will also use some prebuilt Terraform modules like &lt;a href="https://github.com/terraform-aws-modules/terraform-aws-vpc" rel="noopener noreferrer"&gt;AWS VPC Terraform module&lt;/a&gt; and &lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints" rel="noopener noreferrer"&gt;Amazon EKS Blueprints for Terraform&lt;/a&gt; to reduce the amount of boilerplate you need to write.&lt;/p&gt;

&lt;p&gt;These are the AWS resources and VPC architecture you will create:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmq5c1z4iyat12jpwq2b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmq5c1z4iyat12jpwq2b.jpg" alt="AWS EKS and VPC architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Terraform configuration
&lt;/h3&gt;

&lt;p&gt;First, make sure you use a specific version of the providers as different versions might use different attributes and features. Create a &lt;code&gt;versions.tf&lt;/code&gt; file:&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;mkdir &lt;/span&gt;terraform
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform
&lt;span class="nb"&gt;touch &lt;/span&gt;versions.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 4.47"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/kubernetes"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 2.20"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;helm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/helm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 2.9"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you need to define variables and configure the providers. Create a &lt;code&gt;config.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;config.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ##  To save state in s3. Update to suit your needs&lt;/span&gt;
&lt;span class="c1"&gt;# backend "s3" {&lt;/span&gt;
&lt;span class="c1"&gt;#   bucket = "create-an-s3-bucket-and-provide-name-here"&lt;/span&gt;
&lt;span class="c1"&gt;#   region = local.region&lt;/span&gt;
&lt;span class="c1"&gt;#   key    = "eks-cluster-with-new-vpc/terraform.tfstate"&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"okta-auth0-jhipster-eks"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.27"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_types&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t2.large"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# can be multiple, comma separated&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;azs&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Blueprint&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;GitHubRepo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws-ia/terraform-aws-eks-blueprints"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Kubernetes provider&lt;/span&gt;
&lt;span class="c1"&gt;# You should **not** schedule deployments and services in this workspace.&lt;/span&gt;
&lt;span class="c1"&gt;# This keeps workspaces modular (one for provision EKS, another for scheduling&lt;/span&gt;
&lt;span class="c1"&gt;# Kubernetes resources) as per best practices.&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1beta1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
    &lt;span class="c1"&gt;# This requires the awscli to be installed locally where Terraform is executed&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&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="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"helm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1beta1"&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
      &lt;span class="c1"&gt;# This requires the awscli to be installed locally where Terraform is executed&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can uncomment the &lt;code&gt;backend&lt;/code&gt; section above to save the state in AWS S3 instead of your local filesystem. This is recommended for production setup so that everyone in a team has the same state. This file defines configurable and local variables used across the workspace and configures some of the providers used. The Kubernetes provider is included in this file so the EKS module can complete successfully. Otherwise, it throws an error when creating &lt;code&gt;kubernetes_config_map.aws_auth&lt;/code&gt;. The Helm provider is used to install Kubernetes add-ons to the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the VPC
&lt;/h3&gt;

&lt;p&gt;Next up, you need a VPC, subnets, route tables, and other networking bits. You will use the &lt;code&gt;vpc&lt;/code&gt; module from the &lt;a href="https://github.com/terraform-aws-modules" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-modules&lt;/code&gt;&lt;/a&gt; repository. This module is a wrapper around the AWS VPC module. It makes it easier to configure VPCs and all the other required networking resources. Create a &lt;code&gt;vpc.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;vpc.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# VPC, Subnets, Internet gateway, Route tables, etc.&lt;/span&gt;
&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/vpc/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;

  &lt;span class="nx"&gt;azs&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;single_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Manage so we can name them&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_network_acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_network_acl_tags&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_route_table&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_route_table_tags&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_security_group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_security_group_tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;public_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/cluster/${local.name}"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"shared"&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/elb"&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;private_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/cluster/${local.name}"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"shared"&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new VPC, three private subnets, and three public subnets,&lt;/li&gt;
&lt;li&gt;An Internet gateway and NAT gateway for the public subnets,&lt;/li&gt;
&lt;li&gt;and AWS routes for the gateways, public/private route tables, and route table associations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build the EKS cluster
&lt;/h3&gt;

&lt;p&gt;Now that you have the networking part done, you can build configurations for the EKS cluster and its add-ons. You will use the &lt;a href="https://github.com/terraform-aws-modules" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-modules&lt;/code&gt;&lt;/a&gt; to create the EKS cluster and &lt;code&gt;eks_blueprints&lt;/code&gt; module from &lt;a href="https://aws-ia.github.io/terraform-aws-eks-blueprints/" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-eks-blueprints&lt;/code&gt;&lt;/a&gt;to configure EKS add-ons.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;eks-cluster.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;eks-cluster.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# EKS cluster, worker nodes, security groups, IAM roles, K8s add-ons, etc.&lt;/span&gt;
&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/eks/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 19.15"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_version&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_endpoint_public_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;

  &lt;span class="c1"&gt;# EKS Addons&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_addons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws-ebs-csi-driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;coredns&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;kube-proxy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;vpc-cni&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="nx"&gt;eks_managed_node_group_defaults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Needed by the aws-ebs-csi-driver&lt;/span&gt;
    &lt;span class="nx"&gt;iam_role_additional_policies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;AmazonEBSCSIDriverPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;eks_managed_node_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;instance_types&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_types&lt;/span&gt;
      &lt;span class="nx"&gt;min_size&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;max_size&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
      &lt;span class="nx"&gt;desired_size&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks_blueprints_addons"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.32.1"&lt;/span&gt;

  &lt;span class="nx"&gt;eks_cluster_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;eks_cluster_endpoint&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;eks_cluster_version&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_version&lt;/span&gt;
  &lt;span class="nx"&gt;eks_oidc_provider&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_provider&lt;/span&gt;
  &lt;span class="nx"&gt;eks_oidc_provider_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_provider_arn&lt;/span&gt;

  &lt;span class="c1"&gt;# K8S Add-ons&lt;/span&gt;
  &lt;span class="nx"&gt;enable_aws_load_balancer_controller&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_metrics_server&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_aws_cloudwatch_metrics&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# To update local kubeconfig with new cluster details&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"kubeconfig"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints_addons&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks --region ${local.region}  update-kubeconfig --name $AWS_CLUSTER_NAME"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;AWS_CLUSTER_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;eks&lt;/code&gt; module definition creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKS Cluster Control plane with one &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html" rel="noopener noreferrer"&gt;managed node group&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Cluster and node security groups and rules, IAM roles and policies required,&lt;/li&gt;
&lt;li&gt;Amazon EKS add-ons ebs-csi-driver, vpc-cni, CoreDNS, and kube-proxy,&lt;/li&gt;
&lt;li&gt;and AWS Key Management Service (KMS) configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;eks_blueprints_addons&lt;/code&gt; module definition creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Load Balancer Controller that provisions an AWS Network Load Balancer for distributing traffic,&lt;/li&gt;
&lt;li&gt;and the &lt;a href="https://github.com/kubernetes-sigs/metrics-server" rel="noopener noreferrer"&gt;Metrics Server&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;null_resource&lt;/code&gt; configuration updates your local &lt;code&gt;kubeconfig&lt;/code&gt; file with the new cluster details. It's not a required step for provisioning but just a handy hack.&lt;/p&gt;

&lt;p&gt;Finally, you can also define some outputs to be captured. Create a &lt;code&gt;outputs.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;outputs.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_private_subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC private subnet CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets_cidr_blocks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_public_subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC public subnet CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnets_cidr_blocks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr_block&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_cluster_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS cluster ID"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_cluster_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS cluster Name"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroups"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node groups"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_managed_node_groups&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"configure_kubectl"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks update-kubeconfig --name ${module.eks.cluster_name} --alias ${module.eks.cluster_name}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provision the cluster
&lt;/h3&gt;

&lt;p&gt;Our Terraform definitions are ready. Now you can provision the cluster.&lt;/p&gt;

&lt;p&gt;First, ensure you have configured your AWS CLI to use the correct AWS account and a region that supports EKS. If not, run the following:&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;# Visit https://console.aws.amazon.com/iam/home?#/security_credentials for creating access keys&lt;/span&gt;
aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, initialize Terraform workspace and plan the changes:&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;# Download modules and providers. Initialize state.&lt;/span&gt;
terraform init
&lt;span class="c"&gt;# see a preview of what will be done&lt;/span&gt;
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the plan and make sure everything is correct.&lt;/p&gt;

&lt;p&gt;Now you can apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm by typing &lt;code&gt;yes&lt;/code&gt; when prompted. This will take a while (15-20 minutes), so sit back and have a coffee or contemplate what led you to this point in life. 😉&lt;/p&gt;

&lt;p&gt;Once the EKS cluster is ready, you will see the output variables printed to the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;configure_kubectl&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks update-kubeconfig --name okta-auth0-jhipster-eks --alias okta-auth0-jhipster-eks"&lt;/span&gt;
&lt;span class="nx"&gt;eks_cluster_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"okta-auth0-jhipster-eks"&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroups&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"initial"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"autoscaling_group_schedule_arns"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="s2"&gt;"iam_role_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::216713166862:role/initial-eks-node-group-20230628170956397500000007"&lt;/span&gt;
    &lt;span class="s2"&gt;"iam_role_name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"initial-eks-node-group-20230628170956397500000007"&lt;/span&gt;
    &lt;span class="s2"&gt;"iam_role_unique_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AROATE5I4QAHNFCURF5WI"&lt;/span&gt;
    &lt;span class="s2"&gt;"launch_template_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:ec2:eu-west-1:216713166862:launch-template/lt-028654c46a256c879"&lt;/span&gt;
    &lt;span class="s2"&gt;"launch_template_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lt-028654c46a256c879"&lt;/span&gt;
    &lt;span class="s2"&gt;"launch_template_latest_version"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="s2"&gt;"launch_template_name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"initial-2023062817211846290000000e"&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:eks:eu-west-1:216713166862:nodegroup/okta-auth0-jhipster-eks/initial-20230628172118695900000010/f2c48183-0cd0-f970-d405-0869ccddad37"&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_autoscaling_group_names"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"eks-initial-20230628172118695900000010-f2c48183-0cd0-f970-d405-0869ccddad37"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"okta-auth0-jhipster-eks:initial-20230628172118695900000010"&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_labels"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tomap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cm"&gt;/* of string */&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_resources"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"autoscaling_groups"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eks-initial-20230628172118695900000010-f2c48183-0cd0-f970-d405-0869ccddad37"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="s2"&gt;"remote_access_security_group_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_status"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;
    &lt;span class="s2"&gt;"node_group_taints"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&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="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="nx"&gt;vpc_private_subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.10.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.11.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.12.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;vpc_public_subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the cluster details if you run &lt;code&gt;kdash&lt;/code&gt; or &lt;code&gt;kubectl get nodes&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdcfa46amzkmtr8yz3bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdcfa46amzkmtr8yz3bm.png" alt="EKS cluster in KDash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The EKS cluster defined here will not come under AWS free tier; hence, running this will cost money, so delete the cluster as soon as you finish the tutorial to keep the cost within a few dollars.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Set Up OIDC Authentication Using Auth0 by Okta
&lt;/h2&gt;

&lt;p&gt;You can proceed to deploy the sample application. You could skip this step if you used a sample that does not use Auth0 or OIDC for authentication.&lt;/p&gt;

&lt;p&gt;Since you are using Terraform, you can set up the Auth0 application using the &lt;a href="https://registry.terraform.io/providers/auth0/auth0/latest/docs" rel="noopener noreferrer"&gt;Auth0 Terraform provider&lt;/a&gt;. This will allow you to automate the setup of the Auth0 application and manage the addition of users, customizations, and such.&lt;/p&gt;

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

&lt;p&gt;Open the &lt;code&gt;versions.tf&lt;/code&gt; file in the &lt;code&gt;terraform&lt;/code&gt; folder and add the following content to the &lt;code&gt;required_providers&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;    &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"auth0/auth0"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.49.0"&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 create a Terraform script that creates an Auth0 web application and required customizations. Create a file named &lt;code&gt;auth0.tf&lt;/code&gt; in the &lt;code&gt;terraform&lt;/code&gt; folder and add the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"auth0"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domain&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;your_auth0_domain_uri&amp;gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;debug&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a new Auth0 application for the JHipster app&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_client"&lt;/span&gt; &lt;span class="s2"&gt;"java_ms_client"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JavaMicroservices"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Java Microservices Client Created Through Terraform"&lt;/span&gt;
  &lt;span class="nx"&gt;app_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"regular_web"&lt;/span&gt;
  &lt;span class="nx"&gt;callbacks&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/login/oauth2/code/oidc"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;allowed_logout_urls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;oidc_conformant&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;jwt_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;alg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RS256"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Configuring client_secret_post as an authentication method.&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_client_credentials"&lt;/span&gt; &lt;span class="s2"&gt;"java_ms_client_creds"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;java_ms_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;authentication_method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client_secret_post"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create roles for the JHipster app&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_role"&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ROLE_ADMIN"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Administrator"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_role"&lt;/span&gt; &lt;span class="s2"&gt;"user"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ROLE_USER"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"User"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create an action to customize the authentication flow to add the roles and the username to the access token claims expected by JHipster applications.&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_action"&lt;/span&gt; &lt;span class="s2"&gt;"jhipster_action"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jhipster_roles_claim"&lt;/span&gt;
  &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"node18"&lt;/span&gt;
  &lt;span class="nx"&gt;deploy&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;code&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
  /**
   * Handler that will be called during the execution of a PostLogin flow.
   *
   * @param {Event} event - Details about the user and the context in which they are logging in.
   * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
   */
   exports.onExecutePostLogin = async (event, api) =&amp;gt; {
     const namespace = 'https://www.jhipster.tech';
     if (event.authorization) {
         api.idToken.setCustomClaim(namespace + '/roles', event.authorization.roles);
         api.accessToken.setCustomClaim(namespace + '/roles', event.authorization.roles);
     }
   };
&lt;/span&gt;&lt;span class="no"&gt;  EOT

&lt;/span&gt;  &lt;span class="nx"&gt;supported_triggers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"post-login"&lt;/span&gt;
    &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v3"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Attach the action to the login flow&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_trigger_actions"&lt;/span&gt; &lt;span class="s2"&gt;"login_flow"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"post-login"&lt;/span&gt;

  &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jhipster_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;display_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jhipster_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create a test user. You can create more users here if needed&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_user"&lt;/span&gt; &lt;span class="s2"&gt;"test_user"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connection_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Username-Password-Authentication"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jhipster@test.com"&lt;/span&gt;
  &lt;span class="nx"&gt;email_verified&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"passpass$12$12"&lt;/span&gt; &lt;span class="c1"&gt;# Don't set passwords like this in production! Use env variables instead.&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roles&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_user_roles"&lt;/span&gt; &lt;span class="s2"&gt;"test_user_roles"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;user_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;roles&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;auth0_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth0_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"auth0_webapp_client_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Auth0 JavaMicroservices Client ID"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;java_ms_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"auth0_webapp_client_secret"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Auth0 JavaMicroservices Client Secret"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auth0_client_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;java_ms_client_creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_secret&lt;/span&gt;
  &lt;span class="nx"&gt;sensitive&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find your Auth0 domain in the &lt;a href="https://manage.auth0.com/" rel="noopener noreferrer"&gt;Auth0 dashboard&lt;/a&gt; or by running the &lt;code&gt;auth0 tenants list&lt;/code&gt; command. The script above does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;auth0_client&lt;/code&gt; resource definition creates an Auth0 Web application client conforming to the OIDC standard.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;auth0_client_credentials&lt;/code&gt; resource definition configures the client to use the &lt;code&gt;client_secret_post&lt;/code&gt; authentication method.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;auth0_role&lt;/code&gt; resource definition creates two roles for the JHipster application.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;auth0_action&lt;/code&gt; resource definition creates an action that will be executed during the Auth0 post-login flow. This action will add the roles and the username to the ID and access token claims as expected by JHipster applications.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;auth0_user&lt;/code&gt; resource definition creates a test user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provision the Auth0 application
&lt;/h3&gt;

&lt;p&gt;Now before you can run this script you need to create a machine-to-machine application in Auth0 so that Terraform can communicate with the Auth0 management API. This can be done using the Auth0 CLI. Please note that you also need to have &lt;a href="https://jqlang.github.io/jq/" rel="noopener noreferrer"&gt;jq&lt;/a&gt; installed to run the below commands. Run the following commands to create an application after logging into the CLI with the &lt;code&gt;auth0 login&lt;/code&gt; command:&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;# Create a machine-to-machine application on Auth0&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_M2M_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;auth0 apps create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Auth0 Terraform Provider"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Auth0 Terraform Provider M2M"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; m2m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reveal-secrets&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'. | {client_id: .client_id, client_secret: .client_secret}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Extract the client ID and client secret from the output.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$AUTH0_M2M_APP&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.client_id'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$AUTH0_M2M_APP&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.client_secret'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the application and set environment variables for the client ID and secret. This application needs to be authorized to use the Auth0 management API. This can be done using the commands below.&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;# Get the ID and IDENTIFIER fields of the Auth0 Management API&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_MANAGEMENT_API_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;auth0 apis list &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'map(select(.name == "Auth0 Management API"))[0].id'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_MANAGEMENT_API_IDENTIFIER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;auth0 apis list &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'map(select(.name == "Auth0 Management API"))[0].identifier'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Get the SCOPES to be authorized&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTH0_MANAGEMENT_API_SCOPES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;auth0 apis scopes list &lt;span class="nv"&gt;$AUTH0_MANAGEMENT_API_ID&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[].value'&lt;/span&gt; | jq &lt;span class="nt"&gt;-ncR&lt;/span&gt; &lt;span class="s1"&gt;'[inputs]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Authorize the Auth0 Terraform Provider application to use the Auth0 Management API&lt;/span&gt;
auth0 api post &lt;span class="s2"&gt;"client-grants"&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"client_id": "'&lt;/span&gt;&lt;span class="nv"&gt;$AUTH0_CLIENT_ID&lt;/span&gt;&lt;span class="s1"&gt;'", "audience": "'&lt;/span&gt;&lt;span class="nv"&gt;$AUTH0_MANAGEMENT_API_IDENTIFIER&lt;/span&gt;&lt;span class="s1"&gt;'", "scope":'&lt;/span&gt;&lt;span class="nv"&gt;$AUTH0_MANAGEMENT_API_SCOPES&lt;/span&gt;&lt;span class="s1"&gt;'}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run the Terraform script to create the Auth0 application. Run the following commands to initialize the script and apply it.&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;# Upgrade the terraform script&lt;/span&gt;
terraform init &lt;span class="nt"&gt;-upgrade&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the Auth0 application and the test user. You can find the client ID and secret in the output of the &lt;code&gt;terraform output&lt;/code&gt; command. You can also find the client ID and secret in the Auth0 dashboard.&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;# Client ID&lt;/span&gt;
terraform output &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.auth0_webapp_client_id.value'&lt;/span&gt;
&lt;span class="c"&gt;# Client Secret&lt;/span&gt;
terraform output &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.auth0_webapp_client_secret.value'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the client ID and client secret from the output. You will need these values in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the JHipster application to use Auth0
&lt;/h3&gt;

&lt;p&gt;Update &lt;code&gt;kubernetes/registry-k8s/application-configmap.yml&lt;/code&gt; with the Spring Security OIDC configuration using values from the previous step. This configuration is loaded into Consul, and it shares the values with the gateway and microservices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jhipster&lt;/span&gt;
&lt;span class="c1"&gt;#common configuration shared between all applications&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;application.yml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;configserver:&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;jhipster:&lt;/span&gt;
      &lt;span class="s"&gt;security:&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
        &lt;span class="s"&gt;oauth2:&lt;/span&gt;
          &lt;span class="s"&gt;audience:&lt;/span&gt;
            &lt;span class="s"&gt;- https://&amp;lt;your-auth0-domain&amp;gt;/api/v2/&lt;/span&gt;
    &lt;span class="s"&gt;spring:&lt;/span&gt;
      &lt;span class="s"&gt;security:&lt;/span&gt;
        &lt;span class="s"&gt;oauth2:&lt;/span&gt;
          &lt;span class="s"&gt;client:&lt;/span&gt;
            &lt;span class="s"&gt;provider:&lt;/span&gt;
              &lt;span class="s"&gt;oidc:&lt;/span&gt;
                &lt;span class="s"&gt;# make sure to include the trailing slash&lt;/span&gt;
                &lt;span class="s"&gt;issuer-uri: https://&amp;lt;your-auth0-domain&amp;gt;/&lt;/span&gt;
            &lt;span class="s"&gt;registration:&lt;/span&gt;
              &lt;span class="s"&gt;oidc:&lt;/span&gt;
                &lt;span class="s"&gt;client-id: &amp;lt;client-id&amp;gt;&lt;/span&gt;
                &lt;span class="s"&gt;client-secret: &amp;lt;client-secret&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;# app specific configuration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The microservice applications are now ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure secrets
&lt;/h3&gt;

&lt;p&gt;If you have noticed, you are setting secrets in plain text on the &lt;code&gt;application-configmap.yml&lt;/code&gt; file, which is not ideal and is not a best practice for security. The best way to do this securely would be to use &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;, an external service like &lt;a href="https://www.hashicorp.com/products/vault" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt;, or &lt;a href="https://github.com/bitnami-labs/sealed-secrets" rel="noopener noreferrer"&gt;Sealed Secrets&lt;/a&gt;. To learn more about these methods see the blog post &lt;a href="https://auth0.com/blog/kubernetes-secrets-management/" rel="noopener noreferrer"&gt;Shhhh... Kubernetes Secrets Are Not Really Secret!&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the Microservice Stack
&lt;/h2&gt;

&lt;p&gt;You are ready to deploy to our shiny new EKS cluster, but first, you need to build and push the Docker images to a container registry. You can use &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry (ECR)&lt;/a&gt; or any other container registry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Docker images
&lt;/h3&gt;

&lt;p&gt;You need to build Docker images for each app. This is specific to the JHipster application used in this tutorial which uses &lt;a href="https://github.com/GoogleContainerTools/jib" rel="noopener noreferrer"&gt;Jib&lt;/a&gt; to build the images. Make sure you are logged into Docker using &lt;code&gt;docker login&lt;/code&gt;. Navigate to each app folder (&lt;strong&gt;store&lt;/strong&gt;, &lt;strong&gt;invoice&lt;/strong&gt;, &lt;strong&gt;product&lt;/strong&gt;) and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;docker-repo-uri-or-name&amp;gt;/&amp;lt;image-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Image names should be &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;invoice&lt;/code&gt;, and &lt;code&gt;product&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the applications to EKS
&lt;/h3&gt;

&lt;p&gt;Start the deployment using the handy script provided by JHipster. You could also manually apply deployments using &lt;code&gt;kubectl apply -f &amp;lt;file&amp;gt;&lt;/code&gt; commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;kubernetes
./kubectl-apply.sh &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft93lf3o8qjyk0ykziaj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft93lf3o8qjyk0ykziaj2.png" alt="JHipster pods in KDash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also run the following command to see the status of the deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View the Consul registry using port-forwarding as follows, and you will be able to access the application at &lt;code&gt;http://localhost:8500&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/consul-ui &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster 8500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access the gateway application using port-forwarding as follows, and you will be able to access the application at &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/store &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, you can access the application via the load balancer exposed. Find the external IP of the &lt;code&gt;store&lt;/code&gt; service by navigating to the service tab in KDash or by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc store &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to add the external IP of the store to allowed callback URLs and allowed logout URLs in the Auth0 web application. You can do this by adding the values to the &lt;code&gt;callbacks&lt;/code&gt; and &lt;code&gt;allowed_logout_urls&lt;/code&gt; array in the &lt;code&gt;java_ms_client&lt;/code&gt; resource in &lt;code&gt;auth0.tf&lt;/code&gt; file like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"auth0_client"&lt;/span&gt; &lt;span class="s2"&gt;"java_ms_client"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"JavaMicroservices"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Java Microservices Client Created Through Terraform"&lt;/span&gt;
  &lt;span class="nx"&gt;app_type&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"regular_web"&lt;/span&gt;
  &lt;span class="nx"&gt;callbacks&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/login/oauth2/code/oidc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://aws-elb-id.region.elb.amazonaws.com:8080/login/oauth2/code/oidc"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;allowed_logout_urls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://aws-elb-id.region.elb.amazonaws.com:8080"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;oidc_conformant&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;jwt_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;alg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RS256"&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;Run &lt;code&gt;terraform apply -target="auth0_client.java_ms_client"&lt;/code&gt; to update the configuration in your Auth0 tenant.&lt;/p&gt;

&lt;p&gt;Now you should be able to visit the external IP of the &lt;code&gt;store&lt;/code&gt; service on port 8080 and see the application, and you should be able to log in using your Auth0 test user credentials.&lt;/p&gt;

&lt;p&gt;If you encounter an issue where the Consul pods do not start, you might have issues with the AWS EBS addon for EKS. Run &lt;code&gt;kubectl describe pvc -n jhipster&lt;/code&gt; to see if there are any errors. If you see &lt;code&gt;could not create volume in EC2: UnauthorizedOperation&lt;/code&gt; in errors, then you need to troubleshoot by following the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managing-ebs-csi.html" rel="noopener noreferrer"&gt;Managing the Amazon EBS CSI driver as an Amazon EKS add-on&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tear Down the Cluster with Terraform
&lt;/h2&gt;

&lt;p&gt;Once you are done with the tutorial, you can delete the cluster and all the resources created using Terraform by running the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;terraform
&lt;span class="c"&gt;# The commands below might take a while to finish.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.eks_blueprints_addons"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;span class="c"&gt;# If deleting the VPC fails, then manually delete the load balancers and the security groups&lt;/span&gt;
&lt;span class="c"&gt;# for the load balancer associated with the VPC from the AWS EC2 console and try again.&lt;/span&gt;
&lt;span class="c"&gt;# This is due to the fact that EKS creates a load balancer for Kubernetes service and it is not known to Terraform.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.eks"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.vpc"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;span class="c"&gt;# Clean up Auth0 apps and anything left over.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find all the code from this example on &lt;a href="https://github.com/oktadev/auth0-jhipster-k8s-eks-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>terraform</category>
      <category>kubernetes</category>
      <category>aws</category>
    </item>
    <item>
      <title>Mastodon for Developers: Everything You Need to Know</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Mon, 06 Feb 2023 13:34:14 +0000</pubDate>
      <link>https://dev.to/oktadev/mastodon-for-developers-everything-you-need-to-know-2hhf</link>
      <guid>https://dev.to/oktadev/mastodon-for-developers-everything-you-need-to-know-2hhf</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://auth0.com/blog/mastdon-for-developers/" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Mastodon?
&lt;/h2&gt;

&lt;p&gt;Mastodon is an open-source, distributed micro-blogging platform that can host social networking sites. It was created by &lt;a href="https://en.wikipedia.org/wiki/Eugen_Rochko" rel="noopener noreferrer"&gt;Eugen Rochko&lt;/a&gt; and was first released in 2016. It is similar to Twitter regarding features, target audience, and user experience. But unlike Twitter, it is run on a decentralized network of servers, each of which is called an &lt;strong&gt;instance&lt;/strong&gt;. This does not mean that you have fragmented silos; instances are federated, which means you can talk to other instances, follow people, and see content from other instances, making Mastodon the ideal decentralized social network.&lt;/p&gt;

&lt;p&gt;Any single company does not own the Mastodon network, and users can join any instance they wish. The federation allows people to connect and interact with each other across different instances, making Mastodon more open, secure, and free than something like Twitter. This means no single entity has absolute control over Mastodon, which is a big advantage over traditional social networks like Twitter, which is owned by a single company and is not open source.&lt;/p&gt;

&lt;p&gt;After the recent &lt;a href="https://twitterisgoinggreat.com" rel="noopener noreferrer"&gt;Twitter saga&lt;/a&gt;, it's clear why this is a major benefit. However, this does not mean there is no moderation. Quite the contrary, actually. Each instance can have its own rules and can moderate content as they see fit. This means you can join an instance that is more aligned with your values and beliefs or even hosts your own if you have the necessary resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should You Care about Mastodon?
&lt;/h2&gt;

&lt;p&gt;Platforms like Twitter, Facebook, Instagram, and TikTok make most of their revenue from advertisements. This means they are selling our attention to advertisers.&lt;/p&gt;

&lt;p&gt;How do you keep someones attention? Our brains are naturally wired to seek dopamine reward pathways and &lt;a href="https://www.iomcworld.org/open-access/neurotransmitter-dopamine-da-and-its-role-in-the-development-of-social-media-addiction-59222.html" rel="noopener noreferrer"&gt;algorithms&lt;/a&gt; used by these social media often target dopamine pathways to create a feeling of reward and reinforcement that encourages people to keep using their services. This is done by providing users with a steady stream of new content and positive feedback, such as likes and comments, which stimulates dopamine production in the brain. This can lead to people becoming addicted to social media, as they constantly seek out the reward of dopamine that comes with using the platform.&lt;/p&gt;

&lt;p&gt;In addition, many platforms use algorithms to personalize content and increase the chance of a positive outcome, further encouraging people to use the service. But that's not all; these algorithms also use tactics like amplifying content that is reactionary, sensational, or &lt;a href="https://en.wikipedia.org/wiki/Mean_world_syndrome" rel="noopener noreferrer"&gt;frightening&lt;/a&gt;, which grabs our attention.&lt;/p&gt;

&lt;p&gt;Platforms like Mastodon, which &lt;a href="https://arstechnica.com/tech-policy/2022/12/twitter-rival-mastodon-rejects-funding-to-preserve-nonprofit-status/" rel="noopener noreferrer"&gt;does not operate for profit&lt;/a&gt;, do not have any incentives to follow similar tactics. This does not mean that it is not possible as a Mastodon instance can choose to serve advertisements. But since you have control as a user, you can choose to join an instance that does not serve advertisements. This means that algorithms are not manipulating you to keep you hooked on the platform. This is a big advantage over platforms like Twitter which are designed to keep you hooked on the platform, making it easier to sow division and hatred by nefarious actors.&lt;/p&gt;

&lt;p&gt;Mastodon is an important platform for anyone who values their freedom, security, mental health, and privacy online. It is a great alternative to Twitter for those who are looking for a more open and secure platform to connect with others without having to worry about being harassed or bullied. It is quite customizable and provides greater control over your data. With its growing user base, Mastodon is quickly becoming the go-to platform for many people looking for a better way to connect with others and get out of the grasp of BigTech.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Fediverse, and How Does It Work?
&lt;/h2&gt;

&lt;p&gt;Fediverse is the term used to describe a network of interconnected servers that can communicate with each other using decentralized networking protocols. Fediverse is bigger than Mastodon and can include, among others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mastodon servers (social networking and microblogging)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://friendi.ca/" rel="noopener noreferrer"&gt;Friendica&lt;/a&gt; servers (social networking and microblogging)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://joinpeertube.org/" rel="noopener noreferrer"&gt;PeerTube&lt;/a&gt; servers (video hosting)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pleroma.social/" rel="noopener noreferrer"&gt;Pleroma&lt;/a&gt; servers (social networking and microblogging)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fediverse networks can be used for social networks, file hosting services, and so on.&lt;/p&gt;

&lt;p&gt;Fediverse works using several different communication protocols. The most important ones are &lt;a href="https://activitypub.rocks/" rel="noopener noreferrer"&gt;ActivityPub&lt;/a&gt;, &lt;a href="https://www.w3.org/community/ostatus/wiki/Main_Page" rel="noopener noreferrer"&gt;OStatus&lt;/a&gt;, and &lt;a href="https://diaspora.github.io/diaspora_federation/" rel="noopener noreferrer"&gt;diaspora&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ActivityPub is a protocol that allows servers to communicate with each other. It is a decentralized protocol based on the &lt;a href="https://www.w3.org/TR/activitystreams-core/" rel="noopener noreferrer"&gt;ActivityStreams&lt;/a&gt; standard. Mastodon, PeerTube, and Pleroma use ActivityPub.&lt;/p&gt;

&lt;p&gt;OStatus is a decentralized protocol based on the &lt;a href="https://www.rfc-editor.org/rfc/rfc4287" rel="noopener noreferrer"&gt;Atom Syndication Format&lt;/a&gt;. OStatus is a predecessor to ActivityPub and is used by older instances of Mastodon and Pleroma.&lt;/p&gt;

&lt;p&gt;Diaspora is a decentralized protocol. Mastodon, Friendica, and Pleroma use it.&lt;/p&gt;

&lt;p&gt;Any server that supports one of these protocols can communicate with other servers that support the same protocol.&lt;/p&gt;

&lt;p&gt;It is difficult to estimate the number of users in Fediverse due to the distributed nature, but &lt;a href="https://fediverse.observer/stats" rel="noopener noreferrer"&gt;rough third-party estimates&lt;/a&gt; put it at around 8 million users. This is a small fraction of the number of users on Twitter, but it is growing rapidly, as well as the number of instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Servers
&lt;/h2&gt;

&lt;p&gt;One of the biggest strengths of Mastodon, decentralization, is also its biggest hurdle when it comes to adoption. This means that there is no single place to sign up for Mastodon. Instead, you have to choose an instance to join. Choosing a server could be a daunting task, especially if you are new to Mastodon. There are some factors to consider when choosing a server. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quality and reliability of the server&lt;/li&gt;
&lt;li&gt;Community and moderation&lt;/li&gt;
&lt;li&gt;Rules and Policies&lt;/li&gt;
&lt;li&gt;Non-profit status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choosing a server based on this is important, but it's not as critical as it seems. This is because you can follow people from other servers and see their content in your timeline. This means you can join a server that is more aligned with your values and beliefs and still follow people from other servers. This is one of the biggest advantages of Mastodon over Twitter, where you are forced to follow people from the same server. Not just that, Mastodon lets you move from one server to another in case you choose a server that turns out to be unreliable, or you realize that you disagree with the server's policies. You can also easily import your data, like your followers and people you follow, to the new server. So choose a server you feel comfortable with and stick around to see how it goes. You can always move to another server if you don't like it.&lt;/p&gt;

&lt;p&gt;As of this writing, there are over 17,000 instances of Mastodon, which is growing daily. This means that there is a Mastodon instance for everyone. You can find a list of Mastodon instances on &lt;a href="https://instances.social/" rel="noopener noreferrer"&gt;instances.social&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended servers
&lt;/h3&gt;

&lt;p&gt;For technical folks like developers, you could consider joining one of these servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fosstodon.org/" rel="noopener noreferrer"&gt;fosstodon.org&lt;/a&gt;: Ideal for developers, especially if you are an open-source enthusiast&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mstdn.social/" rel="noopener noreferrer"&gt;mstdn.social&lt;/a&gt;: It's a general-purpose server with a good community and is quite reliable&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mastodon.social/" rel="noopener noreferrer"&gt;mastodon.social&lt;/a&gt;: The official Mastodon server with a good community and is quite reliable&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hachyderm.io/" rel="noopener noreferrer"&gt;hachyderm.io&lt;/a&gt;: A server for tech industry professionals&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techhub.social/" rel="noopener noreferrer"&gt;techhub.social&lt;/a&gt;: A server for technology enthusiasts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building a Timeline Based on Hashtags and Follows
&lt;/h2&gt;

&lt;p&gt;Unlike Twitter, when you join a Mastodon server, you will not be greeted by a timeline with interesting posts and recommendations for people to follow. Instead, you are going to be greeted by an empty timeline. This is by design, as Mastodon does not have any algorithm or recommendation system, and you will not be following people from the same server. This means that you have to build your timeline by following people and hashtags. This is a good thing as it gives you more control over your timeline, and you will not be bombarded with content you are not interested in. You can find and follow people from other instances by searching for their usernames. For example, if you want to follow me, you can search for my username &lt;code&gt;@deepu105&lt;/code&gt; or &lt;code&gt;@deepu105@mastodon.social&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Similarly, you can also follow hashtags; look for the &lt;strong&gt;+&lt;/strong&gt; button on the top right corner of the screen when you are on the hashtag page. This works on the web version and some mobile clients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1jiyxru13e2wk75yxry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1jiyxru13e2wk75yxry.png" alt="Follow hashtags" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to follow people and hashtags you are interested in to have interesting content on your timeline. You can mute people you are not interested in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl7d0yrs3jshaxgp5tkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl7d0yrs3jshaxgp5tkf.png" alt="Mute accounts" width="799" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also filter hashtags or words you don't want to see on your timeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38tfpw5szflkrzpr89ty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38tfpw5szflkrzpr89ty.png" alt="Filter hashtags" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a great way to keep your timeline clean and free of unwanted content.&lt;/p&gt;

&lt;p&gt;If you are migrating from Twitter, &lt;a href="https://www.movetodon.org/" rel="noopener noreferrer"&gt;Movetodon.org&lt;/a&gt; is a great tool to help you find and follow people from Twitter on Mastodon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Posting
&lt;/h2&gt;

&lt;p&gt;If you prefer to keep your Twitter account and cross-post to Mastodon and vice versa, you can use some tools to do so, including writing your own scripts using Twitter and Mastodon APIs. My personal favorite is &lt;a href="https://moa.party/" rel="noopener noreferrer"&gt;moa.party&lt;/a&gt;. It supports cross-posting to and from Twitter and Mastodon and is simple to setup and use. It can also post from Instagram to Mastodon. It is &lt;a href="https://gitlab.com/fedstoa/moa" rel="noopener noreferrer"&gt;open-source&lt;/a&gt;, and you can host it yourself if you don't want to give the service access to your Twitter/Mastodon accounts &lt;a href="https://moaparty.com/oauth/" rel="noopener noreferrer"&gt;using OAuth&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing the Account
&lt;/h2&gt;

&lt;p&gt;As usual, it is important to keep your Mastodon account secure. This includes using a strong password, using a password manager, and enabling two-factor authentication (2FA). Mastodon supports 2FA using TOTP (Time-based One-time Password Algorithm) authenticator apps like Google Authenticator and FIDO security keys like Yubikey.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbiqqn07wqb7fwf7aumd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbiqqn07wqb7fwf7aumd.png" alt="Adding 2FA" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also verify your Mastodon account by linking your official website, GitHub profile, and so on. This is a great way to prove that you are the owner of the account and prevent impersonation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcq6dnj627slu5xbtcc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcq6dnj627slu5xbtcc1.png" alt="Verify your Mastodon account" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this, go to &lt;strong&gt;Preferences&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Appearance&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Profile metadata&lt;/strong&gt; and copy the verification URL and add it to your website as instructed. For the GitHub profile, add your Mastodon profile URL in the GitHub profile's website field. For example, my Mastodon profile URL is &lt;code&gt;https://mastodon.social/@deepu105&lt;/code&gt;. Now add your website or GitHub profile URL to your Mastodon profile metadata and save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4jwost56dotdc3waj4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz4jwost56dotdc3waj4o.png" alt="Verified links on Mastodon account" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving between Servers
&lt;/h2&gt;

&lt;p&gt;If you decide to move from one Mastodon server to another, here are a few tips to make the process smooth.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, create a profile on the new server you want to use. Note that this will be a new username, as Mastodon usernames include the server name, and you need to choose a username that is available on the new server.&lt;/li&gt;
&lt;li&gt;Export your data from the old server by going to &lt;strong&gt;Preferences&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Import and export&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Data export&lt;/strong&gt;. You will get CSV files for each item you export, like Follows, mutes, and so on.&lt;/li&gt;
&lt;li&gt;Create an account alias in your new account by going to &lt;strong&gt;Preferences&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Account&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Account settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Moving from a different account&lt;/strong&gt;. This will allow you to redirect your old account and move followers from your old account to the new account.&lt;/li&gt;
&lt;li&gt;Now, from your old account, redirect to the new account by going to &lt;strong&gt;Preferences&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Account&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Account settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Moving to a different account&lt;/strong&gt;. This will redirect your old account to the new account, and all your followers will be moved to the new account.&lt;/li&gt;
&lt;li&gt;Now, from your new account, go to &lt;strong&gt;Preferences&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Import and export&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Import&lt;/strong&gt; and import the CSV files that you exported from your old account. This will import all your follows, mutes, lists, and so on to the new account.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Mastodon is a great alternative to Twitter and is a great way to connect with people from around the world without worrying about a single entity dictating what you can and cannot do with your social media. It is a great way to build a community around your projects and share your thoughts and ideas. It is also a great way to connect with people from the open-source community. I hope this guide will help you get started with Mastodon.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>social</category>
      <category>mastodon</category>
    </item>
    <item>
      <title>Shhhh... Kubernetes Secrets Are Not Really Secret!</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Thu, 15 Dec 2022 11:46:30 +0000</pubDate>
      <link>https://dev.to/oktadev/shhhh-kubernetes-secrets-are-not-really-secret-3h38</link>
      <guid>https://dev.to/oktadev/shhhh-kubernetes-secrets-are-not-really-secret-3h38</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://auth0.com/blog/kubernetes-secrets-management/" rel="noopener noreferrer"&gt;auth0.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes has become an inevitable part of the modern software infrastructure. Hence managing sensitive data on Kubernetes is also an essential aspect of modern software engineering so that you can put the security back into DevSecOps. Kubernetes offers a way to store sensitive data using the &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/" rel="noopener noreferrer"&gt;Secret&lt;/a&gt; object. While it's better than nothing, it is not really a secret, as it is just &lt;a href="https://en.wikipedia.org/wiki/Base64" rel="noopener noreferrer"&gt;base64&lt;/a&gt; encoded strings that anyone with access to the cluster or the code can decode.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;&lt;br&gt;
Kubernetes Secrets are, by default, stored unencrypted in the API server's underlying data store (etcd). Anyone with API access can retrieve or modify a Secret, and so can anyone with access to etcd. Additionally, anyone authorized to create a Pod in a namespace can use that access to read any Secret in that namespace; this includes indirect access, such as the ability to create a Deployment.&lt;br&gt;
— &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/" rel="noopener noreferrer"&gt;Kubernetes docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem of reading secrets from the cluster can be fixed using proper &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="noopener noreferrer"&gt;RBAC&lt;/a&gt; configuration and by securing the API server, check out &lt;a href="https://developer.okta.com/blog/2021/12/02/k8s-security-best-practices" rel="noopener noreferrer"&gt;How to Secure Your Kubernetes Clusters With Best Practices&lt;/a&gt; to learn more about RBAC and cluster API security. Securing secrets on the source code is the bigger problem. Everyone who has access to the repositories containing those secret definitions can also decode them. This makes it quite tricky to manage Kubernetes secrets in Git, like every other resource.&lt;/p&gt;

&lt;p&gt;Let's see how to setup more secure secrets using the;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sealed Secrets,&lt;/li&gt;
&lt;li&gt;External Secrets Operator,&lt;/li&gt;
&lt;li&gt;Secrets Store CSI driver.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would need a Kubernetes cluster to run the samples. I used &lt;a href="https://k3d.io/" rel="noopener noreferrer"&gt;k3d&lt;/a&gt; to create a local cluster. You can also use &lt;a href="https://kind.sigs.k8s.io/" rel="noopener noreferrer"&gt;kind&lt;/a&gt; or &lt;a href="https://minikube.sigs.k8s.io/docs/" rel="noopener noreferrer"&gt;minikube&lt;/a&gt; for this purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sealed Secrets
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bitnami-labs/sealed-secrets" rel="noopener noreferrer"&gt;Sealed Secrets&lt;/a&gt; is an open-source Kubernetes controller and a client-side CLI tool from Bitnami that aims to solve the "&lt;strong&gt;storing secrets in Git&lt;/strong&gt;" part of the problem, using asymmetric crypto encryption. Sealed Secrets with an RBAC configuration preventing non-admins from reading secrets is an excellent solution for the entire problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wsii5h6myb5t2tfenj0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wsii5h6myb5t2tfenj0.jpg" alt="Sealed Secrets Architecture" width="799" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works as below;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypt the secret on the developer machine using a public key and the &lt;code&gt;kubeseal&lt;/code&gt; CLI. This encodes the encrypted secret into a Kubernetes Custom Resource Definition (CRD)&lt;/li&gt;
&lt;li&gt;Deploy the CRD to the target cluster&lt;/li&gt;
&lt;li&gt;The Sealed Secret controller decrypts the secret using a private key on the target cluster to produce a standard Kubernetes secret.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The private key is only available to the Sealed Secrets controller on the cluster, and the public key is available to the developers. This way, &lt;strong&gt;only the cluster can decrypt the secrets, and the developers can only encrypt them&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Supports template definition so that metadata can be added to the unsealed secrets. For example, you can add labels and annotations to the unsealed secrets using the template definition.&lt;/li&gt;
&lt;li&gt;The unsealed secrets will be owned by the sealed secret CRD and updated when the sealed secrets are updated.&lt;/li&gt;
&lt;li&gt;Certificates are rotated every 30 days by default, and this can be customized.&lt;/li&gt;
&lt;li&gt;Secrets are encrypted using unique keys for each cluster, namespace, and secret combination (private key + namespace name + secret name), preventing any loopholes in decryption. This behavior is configurable using scopes &lt;code&gt;strict&lt;/code&gt;, &lt;code&gt;namespace-wide&lt;/code&gt;, and &lt;code&gt;cluster-wide&lt;/code&gt; during the sealing process.&lt;/li&gt;
&lt;li&gt;Can be used to manage existing secrets in the cluster.&lt;/li&gt;
&lt;li&gt;Has a &lt;a href="https://marketplace.visualstudio.com/items?itemName=codecontemplator.kubeseal" rel="noopener noreferrer"&gt;VSCode extension&lt;/a&gt; to make it easier to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Since it unseals the sealed secrets into regular secrets, you can still decode them if you have access to the cluster and namespace.&lt;/li&gt;
&lt;li&gt;Requires resealing for each cluster environment, as the key pair will be unique for each cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Install the controller on the cluster and the CLI on the local machine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the &lt;code&gt;controller.yaml&lt;/code&gt; manifest file from the &lt;a href="https://github.com/bitnami-labs/sealed-secrets/releases" rel="noopener noreferrer"&gt;release&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;Deploy the controller using &lt;code&gt;kubectl apply -f controller.yaml&lt;/code&gt; to your cluster. The controller will be installed on the &lt;code&gt;kube-system&lt;/code&gt; namespace. The controller will start and be ready in a few moments.&lt;/li&gt;
&lt;li&gt;Install the CLI on your local machine using &lt;code&gt;brew install kubeseal&lt;/code&gt; (Linux &amp;amp; macOS) or using the pre-built binaries on the &lt;a href="https://github.com/bitnami-labs/sealed-secrets/releases" rel="noopener noreferrer"&gt;release&lt;/a&gt; page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Let's create a sealed secret.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a secret using the &lt;code&gt;kubectl create secret&lt;/code&gt; command or by hand coding a YAML file as follows:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; secretvalue | kubectl create secret generic mysecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/stdin &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will produce a secret definition like the one below;&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="c1"&gt;# my-secret.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c2VjcmV0dmFsdWU=&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;mysecret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Seal the secret using the &lt;code&gt;kubeseal&lt;/code&gt; CLI. This will encrypt the secret using the public key fetched from the server and produce a sealed secret definition. The &lt;code&gt;my-secret.yaml&lt;/code&gt; file can be discarded now. You can also download the public key and use it locally in offline mode.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubeseal &lt;span class="nt"&gt;--format&lt;/span&gt; yaml &amp;lt; my-secret.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my-sealed-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will produce a sealed secret definition, &lt;code&gt;my-sealed-secret.yaml&lt;/code&gt;, like the one below;&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="c1"&gt;# my-sealed-secret.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami.com/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SealedSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;mysecret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;encryptedData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AgA6a4AGzd7qzR8mTPqTPFNor8tTtT5...==&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;mysecret&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is safe to commit to Git or to share with other developers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finally, you can deploy this to the cluster to be unsealed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; my-sealed-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now, you can see the unsealed secret in the cluster.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe secret mysecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use this secret in deployments like any other Kubernetes secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  External Secrets Operator
&lt;/h2&gt;

&lt;p&gt;Sealed Secrets are a great starting point for securing secrets, but there is an even better way. Using the &lt;a href="https://external-secrets.io/" rel="noopener noreferrer"&gt;External Secrets Operator (ESO)&lt;/a&gt; and an external secret management system like &lt;a href="https://www.vaultproject.io/" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt;, &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;, &lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;Google Secrets Manager&lt;/a&gt;, or &lt;a href="https://azure.microsoft.com/en-us/services/key-vault/" rel="noopener noreferrer"&gt;Azure Key Vault&lt;/a&gt;. While this is a bit more involved to set up, it is a better approach if you use a cloud provider to host your Kubernetes cluster. ESO supports many such secret managers and watches for changes to external secret stores, and keeps Kubernetes secrets in sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11upqnxxvmyrdca9kjzj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11upqnxxvmyrdca9kjzj.jpg" alt="External Secrets Operator Architecture" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ESO provides four CRDs to manage secrets. The &lt;code&gt;ExternalSecret&lt;/code&gt; and &lt;code&gt;ClusterExternalSecret&lt;/code&gt; CRD define what data needs to be fetched and how it should be transformed. The &lt;code&gt;SecretStore&lt;/code&gt; and &lt;code&gt;ClusterSecretStore&lt;/code&gt; CRD define the connection details to the external secret stores. The &lt;code&gt;Cluster&lt;/code&gt; variations can be used cluster-wide.&lt;/p&gt;

&lt;p&gt;It works as below;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;SecretStore&lt;/code&gt; CRD to define the connection details to the external secret store.&lt;/li&gt;
&lt;li&gt;Create secrets in the external secret store.&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;ExternalSecret&lt;/code&gt; CRD to define what data needs to be fetched from the external secret store.&lt;/li&gt;
&lt;li&gt;Deploy the CRDs to the target cluster.&lt;/li&gt;
&lt;li&gt;The ESO controller will fetch the data from the external secret store and create a Kubernetes secret.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Secrets are stored in a secure external secret manager, not the code repository.&lt;/li&gt;
&lt;li&gt;Keeps secrets in sync with the external secret manager.&lt;/li&gt;
&lt;li&gt;Works with many external secret managers.&lt;/li&gt;
&lt;li&gt;Can use multiple secret stores in the same cluster.&lt;/li&gt;
&lt;li&gt;Provides &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; metrics for monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Needs an elaborate setup to use.&lt;/li&gt;
&lt;li&gt;Creates a Kubernetes secret object which can be decoded if you have access to the cluster and namespace.&lt;/li&gt;
&lt;li&gt;Relies on the external secret manager and its access policies to be secure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;ESO can be installed via &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add external-secrets https://charts.external-secrets.io

helm &lt;span class="nb"&gt;install &lt;/span&gt;external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  external-secrets/external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to include ESO in your Helm releases, add the &lt;code&gt;--set installCRDs=true&lt;/code&gt; flag to the above command.&lt;/p&gt;

&lt;p&gt;Let's see how you can use ESO with different secret managers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using HashiCorp Vault
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.vaultproject.io/" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt; is a popular secret manager providing different secret engines. ESO can only be used with the &lt;a href="https://www.vaultproject.io/docs/secrets/kv" rel="noopener noreferrer"&gt;KV Secrets Engine&lt;/a&gt; offered by Vault. Vault provides a free and open-source version that you can self-manage and a managed version with a free tier on the HashiCorp Cloud Platform (HCP).&lt;/p&gt;

&lt;p&gt;Make sure you have a Key-value secret store setup in &lt;a href="https://developer.hashicorp.com/vault/tutorials/getting-started" rel="noopener noreferrer"&gt;your local Vault instance&lt;/a&gt; or on the &lt;a href="https://developer.hashicorp.com/vault/tutorials/cloud" rel="noopener noreferrer"&gt;HCP cloud&lt;/a&gt;. You can also &lt;a href="https://blog.container-solutions.com/tutorialexternal-secrets-with-hashicorp-vault" rel="noopener noreferrer"&gt;deploy Vault to your Kubernetes cluster&lt;/a&gt; using the &lt;a href="https://www.vaultproject.io/docs/platform/k8s/helm" rel="noopener noreferrer"&gt;Vault Helm chart&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new &lt;code&gt;SecretStore&lt;/code&gt; CRD, &lt;code&gt;vault-backend.yaml&lt;/code&gt;, to define the connection details to Vault.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vault-backend.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-backend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_VAULT_ADDRESS"&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret"&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v2"&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin"&lt;/span&gt; &lt;span class="c1"&gt;# required for HCP Vault&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# points to a secret that contains a vault token&lt;/span&gt;
        &lt;span class="c1"&gt;# https://www.vaultproject.io/docs/auth/token&lt;/span&gt;
        &lt;span class="na"&gt;tokenSecretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vault-token"&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a secret resource to hold the Vault token. Use a token that has &lt;a href="https://www.vaultproject.io/docs/concepts/policies" rel="noopener noreferrer"&gt;policies&lt;/a&gt; with read access to the &lt;code&gt;secret/&lt;/code&gt; path in the Vault KV store.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret generic vault-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_VAULT_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a secret in Vault. If you are using the Vault CLI, you can use the below command to create a secret. Make sure you are logged in to the vault instance from the CLI with appropriate policies.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vault kv put secret/mysecret my-value&lt;span class="o"&gt;=&lt;/span&gt;supersecret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create an &lt;code&gt;ExternalSecret&lt;/code&gt; CRD to define what data needs to be fetched from Vault.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vault-secret.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-example&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;refreshInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15s"&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&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;vault-backend&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretStore&lt;/span&gt;
  &lt;span class="na"&gt;target&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;vault-example-sync&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-from-vault&lt;/span&gt;
      &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret/mysecret&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Apply the above CRDs to the cluster, and it should create a Kubernetes secret named &lt;code&gt;vault-example-sync&lt;/code&gt; with the data fetched from Vault.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; vault-backend.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; vault-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the secret in the cluster using the &lt;code&gt;kubectl describe&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe secret vault-example-sync

&lt;span class="c"&gt;# output should have the below data&lt;/span&gt;
Name:         vault-example-sync
Namespace:    default
Labels:       &amp;lt;none&amp;gt;
Annotations:  reconcile.external-secrets.io/data-hash: ...

Type:  Opaque

Data
&lt;span class="o"&gt;====&lt;/span&gt;
secret-from-vault:  16 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you have issues creating the secret&lt;/strong&gt;, check the events section of the describe output of the &lt;code&gt;ExternalSecret&lt;/code&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe externalsecret vault-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see permission errors, make sure you use tokens with the right policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other secret managers
&lt;/h3&gt;

&lt;p&gt;Setting up other secret managers is similar to the above steps. The only difference would be the &lt;code&gt;SecretStore&lt;/code&gt; CRD and the &lt;code&gt;remoteRef&lt;/code&gt; section in the &lt;code&gt;ExternalSecret&lt;/code&gt; CRD. You can find official guides for different providers in the &lt;a href="https://external-secrets.io/" rel="noopener noreferrer"&gt;ESO documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets Store CSI Driver
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://secrets-store-csi-driver.sigs.k8s.io/" rel="noopener noreferrer"&gt;Secrets Store CSI Driver&lt;/a&gt; is a native upstream Kubernetes driver that can be used to abstract where the secret is stored from the workload. If you want to use a cloud provider's secret manager without exposing the secrets as Kubernetes Secret objects, you can use the CSI Driver to mount secrets as volumes in your pods. This is a great option if you use a cloud provider to host your Kubernetes cluster. The driver supports many cloud providers and can be used with different secret managers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj73oax5x6x4ww75pd7s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj73oax5x6x4ww75pd7s.jpg" alt="Secrets Store CSI Driver Architecture" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Secrets Store CSI Driver is a daemonset that communicates with the secret provider to retrieve secrets specified in a &lt;code&gt;SecretProviderClass&lt;/code&gt; custom resource.&lt;/p&gt;

&lt;p&gt;It works as below;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;SecretProviderClass&lt;/code&gt; CRD to define the details of the secret to be fetched from the secret provider.&lt;/li&gt;
&lt;li&gt;Create deployments and reference the &lt;code&gt;SecretProviderClass&lt;/code&gt; in the pod's volume spec.&lt;/li&gt;
&lt;li&gt;The driver will fetch the secret from the secret provider and mount it as a &lt;code&gt;tmpfs&lt;/code&gt; volume in the pod during pod startup. This volume will be removed during pod deletion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The driver can also sync changes to secrets. The driver currently supports &lt;a href="https://github.com/hashicorp/secrets-store-csi-driver-provider-vault" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;, &lt;a href="https://github.com/aws/secrets-store-csi-driver-provider-aws" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, &lt;a href="https://github.com/Azure/secrets-store-csi-driver-provider-azure" rel="noopener noreferrer"&gt;Azure&lt;/a&gt;, and &lt;a href="https://github.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp" rel="noopener noreferrer"&gt;GCP&lt;/a&gt; providers. Secrets Store CSI Driver can also sync provider secrets as Kubernetes secrets; if required, this behavior needs to be explicitly enabled during installation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Secrets are stored in a secure external secret manager, not the code repository.&lt;/li&gt;
&lt;li&gt;Keeps secrets in sync with the external secret manager. It also supports the rotation of secrets.&lt;/li&gt;
&lt;li&gt;Works with all major external secret managers.&lt;/li&gt;
&lt;li&gt;Mounts secrets as volumes in the pod so they are not exposed as Kubernetes secrets. It can be configured to create Kubernetes secrets as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Needs an elaborate setup to use and is more complex than ESO.&lt;/li&gt;
&lt;li&gt;Uses more resources than ESO as this needs to run in every node.&lt;/li&gt;
&lt;li&gt;Relies on the external secret store and its access policies to be secure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using Google Secret Manager provider
&lt;/h3&gt;

&lt;p&gt;Let us see how to configure the driver to use Google Secret Manager (GSM) as the secret provider.&lt;/p&gt;

&lt;p&gt;Make sure you are using a Google Kubernetes Engine (GKE) cluster with the &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity" rel="noopener noreferrer"&gt;Workload Identity&lt;/a&gt; feature enabled. Workload Identity allows workloads in your GKE clusters to impersonate Identity and Access Management (IAM) service accounts to access Google Cloud services. You would also need to enable Kubernetes Engine API, Secret Manager API, and Billing for the project. The &lt;code&gt;gcloud&lt;/code&gt; CLI should prompt you to enable these APIs if they are not enabled.&lt;/p&gt;

&lt;p&gt;The below command can be used to create a new cluster with Workload Identity enabled using the &lt;code&gt;gcloud&lt;/code&gt; CLI.&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;PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your gcp project&amp;gt;
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project &lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;

gcloud container clusters create hello-hipster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--workload-pool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;.svc.id.goog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Install the Secrets Store CSI Driver
&lt;/h4&gt;

&lt;p&gt;Secrets Store CSI Driver can be installed on the cluster with Helm using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts

helm &lt;span class="nb"&gt;install &lt;/span&gt;csi-secrets-store &lt;span class="se"&gt;\&lt;/span&gt;
    secrets-store-csi-driver/secrets-store-csi-driver &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--namespace&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the driver and CRDs on the &lt;code&gt;kube-system&lt;/code&gt; namespace. You also need to install the provider required into the cluster.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install the GSM provider
&lt;/h4&gt;

&lt;p&gt;Let us install the GSM provider into the cluster. The provider can be installed using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/main/deploy/provider-gcp-plugin.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a Secret
&lt;/h4&gt;

&lt;p&gt;First, you need to setup a workload identity service account.&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;# Create a service account for workload identity&lt;/span&gt;
gcloud iam service-accounts create gke-workload

&lt;span class="c"&gt;# Allow "default/mypod" to act as the new service account&lt;/span&gt;
gcloud iam service-accounts add-iam-policy-binding &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt; roles/iam.workloadIdentityUser &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt; &lt;span class="s2"&gt;"serviceAccount:&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;.svc.id.goog[default/mypodserviceaccount]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    gke-workload@&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;.iam.gserviceaccount.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create a secret that this service account can access.&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;# Create a secret with 1 active version&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"mysupersecret"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; secret.data
gcloud secrets create testsecret &lt;span class="nt"&gt;--replication-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;automatic &lt;span class="nt"&gt;--data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret.data
&lt;span class="nb"&gt;rm &lt;/span&gt;secret.data

&lt;span class="c"&gt;# grant the new service account permission to access the secret&lt;/span&gt;
gcloud secrets add-iam-policy-binding testsecret &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;serviceAccount:gke-workload@&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;.iam.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;roles/secretmanager.secretAccessor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can create a &lt;code&gt;SecretProviderClass&lt;/code&gt; resource that will be used to fetch the secret from GSM. Remember to replace &lt;code&gt;$PROJECT_ID&lt;/code&gt; with your GCP project ID.&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="c1"&gt;# secret-provider-class.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.x-k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-secrets&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcp&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;- resourceName: "projects/$PROJECT_ID/secrets/testsecret/versions/latest"&lt;/span&gt;
        &lt;span class="s"&gt;path: "good1.txt"&lt;/span&gt;
      &lt;span class="s"&gt;- resourceName: "projects/$PROJECT_ID/secrets/testsecret/versions/latest"&lt;/span&gt;
        &lt;span class="s"&gt;path: "good2.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a Pod
&lt;/h4&gt;

&lt;p&gt;Now you can create a pod that will use the &lt;code&gt;SecretProviderClass&lt;/code&gt; resource to fetch the secret from GSM. Remember to replace &lt;code&gt;$PROJECT_ID&lt;/code&gt; with your GCP project ID.&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="c1"&gt;# my-pod.yaml&lt;/span&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypodserviceaccount&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;iam.gke.io/gcp-service-account&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke-workload@$PROJECT_ID.iam.gserviceaccount.com&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypodserviceaccount&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google.com/cloudsdktool/cloud-sdk:slim&lt;/span&gt;
      &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&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;mypod&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
      &lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;stdinOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;terminationMessagePath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/termination-log&lt;/span&gt;
      &lt;span class="na"&gt;terminationMessagePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;File&lt;/span&gt;
      &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/secrets"&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;mysecret&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecret&lt;/span&gt;
      &lt;span class="na"&gt;csi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.k8s.io&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;volumeAttributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretProviderClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app-secrets"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the above resources to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; secret-provider-class.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; my-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the pod to start, and then &lt;code&gt;exec&lt;/code&gt; into the pod and check the contents of the mounted files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mypod /bin/bash
&lt;span class="c"&gt;# execute the below command in the pod to see the contents of the mounted secret file&lt;/span&gt;
root@mypod:/# &lt;span class="nb"&gt;cat&lt;/span&gt; /var/secrets/good1.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Other secret managers
&lt;/h3&gt;

&lt;p&gt;You can find similar guides for the &lt;a href="https://github.com/aws/secrets-store-csi-driver-provider-aws" rel="noopener noreferrer"&gt;AWS CSI provider&lt;/a&gt;, &lt;a href="https://azure.github.io/secrets-store-csi-driver-provider-azure/docs/getting-started/usage/" rel="noopener noreferrer"&gt;Azure CSI provider&lt;/a&gt; and &lt;a href="https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-secret-store-driver" rel="noopener noreferrer"&gt;Vault CSI provider&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Sealed Secrets are a great solution for small teams and projects to secure secrets in Git. For larger teams and projects, the External Secrets Operator or the Secrets Store CSI Driver is a better solution to manage secrets securely. The External Secrets Operator can be used with many secret management systems and is not limited to the ones mentioned above. Of course, this should be used with RBAC to prevent non-admins from reading secrets in the cluster. The Secrets Store CSI Driver might be more involved than ESO, but it is a more native solution.&lt;/p&gt;

&lt;p&gt;Cover image created using &lt;a href="https://midjourney.gitbook.io/" rel="noopener noreferrer"&gt;Midjourney&lt;/a&gt; under &lt;a href="https://creativecommons.org/licenses/by-nc/4.0/" rel="noopener noreferrer"&gt;CC BY-NC 4.0&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://mastodon.social/@deepu105" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Rust Easy! Modern Cross-platform Command Line Tools to Supercharge Your Terminal</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Mon, 07 Nov 2022 09:36:44 +0000</pubDate>
      <link>https://dev.to/deepu105/rust-easy-modern-cross-platform-command-line-tools-to-supercharge-your-terminal-4dd3</link>
      <guid>https://dev.to/deepu105/rust-easy-modern-cross-platform-command-line-tools-to-supercharge-your-terminal-4dd3</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://deepu.tech/rust-terminal-tools-linux-mac-windows-fish-zsh/" rel="noopener noreferrer"&gt;deepu.tech&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Rust is taking over the terminal. Rust is a general-purpose programming language that is blazing fast and memory safe. It is the fastest-growing and most loved programming language in the world. It is used to build everything from operating systems to web servers to command-line tools. Recently there has been a surge of command line tools and utilities written in Rust, and many of them are intended to replace standard Unix commands. They are faster, more user-friendly, and have more features than their standard Unix counterparts. In this post, I will cover some of the best Rust command line tools I have used for a while. You can also use these to supercharge your terminal.&lt;/p&gt;

&lt;p&gt;These tools are available for both GNU/Linux and macOS. I have not tested them on Windows, but most should also work on Windows. I recommend aliasing the commands to replace the standard commands based on your preferences. If you have &lt;a href="https://doc.rust-lang.org/cargo/" rel="noopener noreferrer"&gt;Cargo&lt;/a&gt;, the rust package manager, you can install all these using Cargo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alacritty
&lt;/h2&gt;

&lt;p&gt;Let us start with the terminal itself. &lt;a href="https://github.com/alacritty/alacritty" rel="noopener noreferrer"&gt;Alacritty&lt;/a&gt; is a cross-platform modern terminal emulator with sensible defaults. It is &lt;strong&gt;GPU accelerated&lt;/strong&gt;, super fast, and highly configurable. You can use it on Linux, macOS, and Windows. It doesn't have much in terms of a UI, and hence all &lt;a href="https://github.com/alacritty/alacritty/releases/download/v0.11.0/alacritty.yml" rel="noopener noreferrer"&gt;configurations&lt;/a&gt; are done through YAML files. I don't use it as my primary terminal as I love &lt;a href="https://invent.kde.org/utilities/yakuake" rel="noopener noreferrer"&gt;Yakuake&lt;/a&gt; too much for all its cool features. We can get most of those features (tabs, split panes, dropdown mode) using &lt;a href=""&gt;tmux&lt;/a&gt; and &lt;a href="https://github.com/noctuid/tdrop" rel="noopener noreferrer"&gt;tdrop&lt;/a&gt; if really needed. I use Alacrity when I need speed and GPU acceleration. There is an excellent tutorial on &lt;a href="https://arslan.io/2018/02/05/gpu-accelerated-terminal-alacritty/" rel="noopener noreferrer"&gt;using Alacritty with tmux&lt;/a&gt;. You could also use &lt;a href="https://github.com/zellij-org/zellij" rel="noopener noreferrer"&gt;Zellij&lt;/a&gt;, a modern terminal multiplexer written in Rust, with Alacritty.&lt;/p&gt;

&lt;p&gt;There is also the &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt; terminal, but it is not open source. It is a great terminal, but I prefer open source software. Thanks to &lt;a href="https://dev.to/francisc"&gt;Fran Sancisco&lt;/a&gt; for the suggestion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F146swkh3py5vt6fdwa2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F146swkh3py5vt6fdwa2d.png" alt="Alacritty" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; alacritty
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf copr &lt;span class="nb"&gt;enable &lt;/span&gt;atim/alacritty
dnf &lt;span class="nb"&gt;install &lt;/span&gt;alacritty
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
add-apt-repository ppa:aslatter/ppa
apt &lt;span class="nb"&gt;install &lt;/span&gt;alacritty
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; alacritty
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop bucket add extras
scoop &lt;span class="nb"&gt;install &lt;/span&gt;alacritty
&lt;span class="c"&gt;# Cargo on any&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;alacritty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Starship
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://starship.rs/" rel="noopener noreferrer"&gt;Starship&lt;/a&gt; is the best terminal prompt I have ever used. Forget &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Oh My Zsh&lt;/a&gt; and stuff like that. Starship is fast, highly customizable, and has a great default theme and settings. I didn't even change most of the default settings, as things were perfect as it is. Starship works on shells like zsh, fish, and bash and can also work alongside other prompts like Oh My Zsh, in case you still want to use Oh My Zsh for other plugins like autosuggestions and so on. Starship works best with a &lt;a href="https://www.nerdfonts.com/" rel="noopener noreferrer"&gt;Nerd Font&lt;/a&gt; as it can show icons and ligatures based on context. I used Oh My Zsh for many years with the &lt;a href="https://github.com/romkatv/powerlevel10k" rel="noopener noreferrer"&gt;powerlevel10k&lt;/a&gt; theme, but the prompt was a bit slow. Starship is blazing fast with more features and an excellent UX.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs8z9rt8jnyprfvvxr95.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs8z9rt8jnyprfvvxr95.gif" alt="starship" width="720" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; starship
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;starship
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://starship.rs/install.sh | sh
&lt;span class="c"&gt;# macOS/Linux Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;starship
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;starship
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;starship
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;starship &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  bat
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sharkdp/bat" rel="noopener noreferrer"&gt;bat&lt;/a&gt; is one of my favorite tools from this list. It's a replacement for &lt;code&gt;cat&lt;/code&gt;, and once you have used &lt;code&gt;bat&lt;/code&gt;, you will never go back. It provides features like syntax highlight, line numbers, Git change highlight, shows special chars, paging, and so on. It is super fast and looks beautiful. I have aliased &lt;code&gt;cat&lt;/code&gt; to &lt;code&gt;bat&lt;/code&gt; immediately after trying it for the first time. By default, bat behaves similarly to &lt;code&gt;less&lt;/code&gt; by paging large output, but that can be disabled to make it work precisely like &lt;code&gt;cat&lt;/code&gt;. It can be used as a drop-in replacement for &lt;code&gt;cat&lt;/code&gt; even in scripts. &lt;code&gt;bat&lt;/code&gt; can also be used as a previewer for &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt;. It can also be combined with many other commands and tools like &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;man&lt;/code&gt;, and &lt;code&gt;git&lt;/code&gt;, among others, to add syntax highlighting to outputs. Syntax highlighting themes are configurable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1664rl1g63gb5wdazxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1664rl1g63gb5wdazxl.png" alt="bat" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; bat
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;bat
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;bat
&lt;span class="c"&gt;# macOS/Linux Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;bat
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;bat
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;bat
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;bat &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  LSD and exa
&lt;/h2&gt;

&lt;p&gt;Both &lt;a href="https://github.com/Peltoche/lsd" rel="noopener noreferrer"&gt;LSD&lt;/a&gt; and &lt;a href="https://github.com/ogham/exa" rel="noopener noreferrer"&gt;exa&lt;/a&gt; are replacements for the &lt;code&gt;ls&lt;/code&gt; command. They both look gorgeous with nice colors and icons and have features like headers, sorting, tree views, and so on. Exa is a bit faster than LSD for tree views and can show the Git status of files and folders. I prefer exa due to the Git support and faster tree views. I have set up my &lt;code&gt;ls&lt;/code&gt; alias to use exa by default. Both can be configured to show custom columns and sorting behaviors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcea43sr3gzu2on8q1kxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcea43sr3gzu2on8q1kxo.png" alt="lsd-exa" width="799" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  exa Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; exa
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;exa
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;exa
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;exa
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;exa

&lt;span class="c"&gt;# Alias ls to exa&lt;/span&gt;
&lt;span class="nb"&gt;alias ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'exa --git --icons --color=always --group-directories-first'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  LSD Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; lsd
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;lsd
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; lsd_0.23.1_amd64.deb &lt;span class="c"&gt;# get .deb file from https://github.com/Peltoche/lsd/releases&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;lsd
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;lsd
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scop &lt;span class="nb"&gt;install &lt;/span&gt;lsd
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;lsd

&lt;span class="c"&gt;# Alias ls to lsd&lt;/span&gt;
&lt;span class="nb"&gt;alias ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'lsd --header --color=always --group-directories-first'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  rip
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nivekuil/rip" rel="noopener noreferrer"&gt;rip&lt;/a&gt; is an improved version of the &lt;code&gt;rm&lt;/code&gt; command. It is faster, safer, and user-friendly. rip sends deleted files to a temp location so they can be recovered using &lt;code&gt;rip -u&lt;/code&gt;. I really like the simplicity and the revert feature, as I don't have to worry about accidentally deleting something using &lt;code&gt;rm&lt;/code&gt;. While rip can be aliased to replace &lt;code&gt;rm&lt;/code&gt;, the creators advise not doing that as you might get used to it and do &lt;code&gt;rm&lt;/code&gt; on other systems where you cannot revert the delete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; rm-improved
&lt;span class="c"&gt;# Fedora/CentOS/Debian/Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# Install from binary or build locally using Cargo&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;rm-improved
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;rm-improved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  xcp
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tarka/xcp" rel="noopener noreferrer"&gt;xcp&lt;/a&gt; is a partial clone of the &lt;code&gt;cp&lt;/code&gt; command. It is faster and more user-friendly with progress bars, parallel copying, &lt;code&gt;.gitignore&lt;/code&gt; support, and so on. I like its simplicity and developer experience, especially the progress bars. I have aliased &lt;code&gt;cp&lt;/code&gt; to &lt;code&gt;xcp&lt;/code&gt; so I can use it everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwm7f5fe4uxsole1igeod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwm7f5fe4uxsole1igeod.png" alt="xcp" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; xcp
&lt;span class="c"&gt;# Fedora/CentOS/Debian/Ubuntu/macOS&lt;/span&gt;
&lt;span class="c"&gt;# Install from binary or build locally using Cargo&lt;/span&gt;
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;xcp

&lt;span class="c"&gt;# Alias cp to xcp&lt;/span&gt;
&lt;span class="nb"&gt;alias cp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'xcp'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  zoxide
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ajeetdsouza/zoxide" rel="noopener noreferrer"&gt;zoxide&lt;/a&gt; is a smarter &lt;code&gt;cd&lt;/code&gt; replacement. It remembers the directories you visit, and you can jump to them without providing a full path. You can provide partial paths or even a word from the path. When there are similar paths, zoxide offers an interactive selection using &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt;. It is super fast and works with all major shells. I like how it works, and I have aliased &lt;code&gt;cd&lt;/code&gt; to &lt;code&gt;z&lt;/code&gt; so I can use it everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22zz445stdv6p5zfvpod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22zz445stdv6p5zfvpod.png" alt="zoxide" width="724" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; zoxide
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;span class="c"&gt;# macOS/Linux Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;zoxide &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, you must add the following to your shell config file. For other shells, refer the &lt;a href="https://github.com/ajeetdsouza/zoxide#step-2-add-zoxide-to-your-shell" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# bash (~/.bashrc)&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;zoxide init bash&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# zsh (~/.zshrc)&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;zoxide init zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# fish (~/.config/fish/config.fish)&lt;/span&gt;
zoxide init fish | &lt;span class="nb"&gt;source&lt;/span&gt;

&lt;span class="c"&gt;# Alias cd to z&lt;/span&gt;
&lt;span class="nb"&gt;alias cd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'z'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  dust
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bootandy/dust" rel="noopener noreferrer"&gt;Dust&lt;/a&gt; is an alternative for the &lt;code&gt;du&lt;/code&gt; command. It is fast and has a better UX with nice visualization for disk usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaprw73j9zu7p6rc4dro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaprw73j9zu7p6rc4dro.png" alt="dust" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; dust
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from https://github.com/bootandy/dust/releases&lt;/span&gt;
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
deb-get &lt;span class="nb"&gt;install &lt;/span&gt;du-dust
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;dust
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;dust
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;dust
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;du-dust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ripgrep
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer"&gt;ripgrep (rg)&lt;/a&gt; is a line-oriented search tool that recursively searches your current directory for a regex pattern. It is faster than &lt;code&gt;grep&lt;/code&gt; and has many features like compressed files search, colorized output, smart case, file type filtering, multi-threading, and so on. It understands &lt;code&gt;.gitignore&lt;/code&gt; files and skips hidden and ignored files. &lt;a href="https://beyondgrep.com/feature-comparison/" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is a feature comparison with other similar tools, and yes, it is faster than all the other tools in the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylxrtekh5ktzurnssxzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylxrtekh5ktzurnssxzw.png" alt="ripgrep" width="799" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; ripgrep
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;span class="c"&gt;# macOS/Linux Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;ripgrep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  fd
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sharkdp/fd" rel="noopener noreferrer"&gt;fd&lt;/a&gt; is a simpler alternative to &lt;code&gt;find&lt;/code&gt;. It is more intuitive to use and comes with sensible defaults. It is extremely fast due to parallel traversing and shows a modern colorized output and supports patterns and regex, parallel commands, smart case, understands &lt;code&gt;.gitignore&lt;/code&gt; files, and so on. I have aliased &lt;code&gt;find&lt;/code&gt; to &lt;code&gt;fd&lt;/code&gt; as I could never remember what options to pass to get a basic find command working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1ov1ao4nvx76wiev0d7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1ov1ao4nvx76wiev0d7.png" alt="fd" width="692" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; fd
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;fd-find
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;fd-find
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;fd
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;fd
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;fd
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;fd-find
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  sd
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/chmln/sd" rel="noopener noreferrer"&gt;sd&lt;/a&gt; is a find-and-replace CLI, and you can use it as a replacement for &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt;. It is way more user-friendly and modern. It is also magnitudes faster than &lt;code&gt;sed&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; sd
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;sd
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from the release page&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;sd
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
choco &lt;span class="nb"&gt;install &lt;/span&gt;sd-cli
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;sd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  procs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/dalance/procs" rel="noopener noreferrer"&gt;procs&lt;/a&gt; is a &lt;code&gt;ps&lt;/code&gt; replacement. It provides colorized human-readable output, multi-column search, more information than &lt;code&gt;ps&lt;/code&gt;, docker support, paging, watch mode, and tree view. It is a much more user-friendly and modern alternative to &lt;code&gt;ps&lt;/code&gt;. You can filter by name and PID and use logical and/or operators to combine multiple filters. It also has a tree view which is very useful for seeing the process hierarchy. It can also show docker container names for the process running docker containers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjbl6tb33p1gn1a1j64x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjbl6tb33p1gn1a1j64x.png" alt="procs" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; procs
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;procs
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from the release page&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;procs
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;procs
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;procs
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;procs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  bottom
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ClementTsang/bottom" rel="noopener noreferrer"&gt;bottom&lt;/a&gt; is a &lt;code&gt;top&lt;/code&gt; replacement with a nice terminal UI. It's quite feature-rich and customizable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp14mbg348jd82mz1t8g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp14mbg348jd82mz1t8g6.png" alt="bottom" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; bottom
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf copr &lt;span class="nb"&gt;enable &lt;/span&gt;atim/bottom &lt;span class="nt"&gt;-y&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;bottom
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; bottom_0.6.8_amd64.deb
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;bottom
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;bottom
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;bottom
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;bottom &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Topgrade
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/topgrade-rs/topgrade" rel="noopener noreferrer"&gt;Topgrade&lt;/a&gt; is a fantastic utility if you prefer to keep your system up-to-date, like me. It detects most of the package managers on your system and triggers updates. It is configurable, so you can configure it to ignore certain package managers. On my system, it detected pacman, SDKMAN, Flatpak, snap, Homebrew, rustup, Linux firmware, Pip, and so on. Topgrade is cross-platform; you can use it on Windows, macOS, and Linux.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80jx39qz6x459tb55tci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80jx39qz6x459tb55tci.png" alt="topgrade" width="760" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; topgrade
&lt;span class="c"&gt;# Fedora/CentOS/Debian/Ubuntu/Windows&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from the release page&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;topgrade
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;topgrade
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;topgrade &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Broot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Canop/broot" rel="noopener noreferrer"&gt;Broot&lt;/a&gt; is a &lt;code&gt;tree&lt;/code&gt; alternative with a better user experience, and you can use it to navigate a file structure. It's fast and respects &lt;code&gt;.gitignore&lt;/code&gt;. You can cd into a directory from the tree view, open sub-directories in a panel, and even preview files. It has excellent keyboard navigation as well. It has many more features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco3kb7dfr1l0wqz5jkal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco3kb7dfr1l0wqz5jkal.png" alt="broot" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; broot
&lt;span class="c"&gt;# Fedora/CentOS/Debian/Ubuntu/Windows&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from release page https://dystroy.org/broot/install/&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;broot
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;broot
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;broot &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tokei
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/XAMPPRocky/tokei" rel="noopener noreferrer"&gt;Tokei&lt;/a&gt; is a nice utility to count lines and stats of code. It is very fast, accurate, and has a nice output. It supports over 150 languages and can output in JSON, YAML, CBOR, and human-readable tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtpck4oakx2hfq296ovy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtpck4oakx2hfq296ovy.png" alt="tokei" width="726" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Arch Linux&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; tokei
&lt;span class="c"&gt;# Fedora/CentOS&lt;/span&gt;
dnf &lt;span class="nb"&gt;install &lt;/span&gt;tokei
&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# Install binary from the release page&lt;/span&gt;
&lt;span class="c"&gt;# macOS Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;tokei
&lt;span class="c"&gt;# macOS MacPorts&lt;/span&gt;
port &lt;span class="nb"&gt;install &lt;/span&gt;tokei
&lt;span class="c"&gt;# Windows Scoop&lt;/span&gt;
scoop &lt;span class="nb"&gt;install &lt;/span&gt;tokei
&lt;span class="c"&gt;# Cargo&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;tokei
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other notable tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kdash-rs/kdash/" rel="noopener noreferrer"&gt;kdash&lt;/a&gt;: A fast and simple dashboard for Kubernetes. Its created by me :)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/zellij-org/zellij" rel="noopener noreferrer"&gt;Zellij&lt;/a&gt;: A feature rich modern terminal multiplexer with batteries included.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nushell/nushell" rel="noopener noreferrer"&gt;Nushell&lt;/a&gt;: A modern shell written in Rust. Looks quite promising.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ducaale/xh" rel="noopener noreferrer"&gt;xh&lt;/a&gt;: A HTTPie alternative with better performance.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/y2z/monolith" rel="noopener noreferrer"&gt;monolith&lt;/a&gt;: Convert any webpage into a single HTML file with all assets inlined.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dandavison/delta" rel="noopener noreferrer"&gt;delta&lt;/a&gt;: A syntax-highlighting pager for git, diff, and grep output.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sirwart/ripsecrets" rel="noopener noreferrer"&gt;ripsecrets&lt;/a&gt;: Find secret keys in your code before committing them to git.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nerdypepper/eva" rel="noopener noreferrer"&gt;eva&lt;/a&gt;: A CLI REPL calculator.&lt;/li&gt;
&lt;li&gt;You can find a list of other Rust CLI tools &lt;a href="https://gist.github.com/sts10/daadbc2f403bdffad1b6d33aff016c0a" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://twitter.com/deepu105" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>terminal</category>
      <category>linux</category>
      <category>macos</category>
    </item>
    <item>
      <title>What the Heck Is Project Loom for Java?</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Sat, 27 Aug 2022 14:29:25 +0000</pubDate>
      <link>https://dev.to/oktadev/what-the-heck-is-project-loom-for-java-21hh</link>
      <guid>https://dev.to/oktadev/what-the-heck-is-project-loom-for-java-21hh</guid>
      <description>&lt;p&gt;Java has had good multi-threading and concurrency capabilities from early on in its evolution and can effectively utilize multi-threaded and multi-core CPUs. Java Development Kit (JDK) 1.1 had basic support for platform threads (or Operating System (OS) threads), and JDK 1.5 had more utilities and updates to improve concurrency and multi-threading. JDK 8 brought asynchronous programming support and more concurrency improvements. While things have continued to improve over multiple versions, there has been nothing groundbreaking in Java for the last three decades, apart from support for concurrency and multi-threading using OS threads.&lt;/p&gt;

&lt;p&gt;Though the concurrency model in Java is powerful and flexible as a feature, it was not the easiest to use, and the developer experience hasn't been great. This is primarily due to the shared state concurrency model used by default. One has to resort to synchronizing threads to avoid issues like data races and thread blocking. I wrote more about Java concurrency in my &lt;a href="https://deepu.tech/concurrency-in-modern-languages-java/" rel="noopener noreferrer"&gt;Concurrency in modern programming languages: Java&lt;/a&gt; post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Project Loom?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Project Loom aims to drastically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications that make the best use of available hardware.&lt;/p&gt;

&lt;p&gt;— Ron Pressler (Tech lead, Project Loom)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OS threads are at the core of Java's concurrency model and have a very mature ecosystem around them, but they also come with some drawbacks and are expensive computationally. Let's look at the two most common use cases for concurrency and the drawbacks of the current Java concurrency model in these cases.&lt;/p&gt;

&lt;p&gt;One of the most common concurrency use cases is serving requests over the wire using a server. For this, the preferred approach is the thread-per-request model, where a separate thread handles each request. Throughput of such systems can be explained using &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law" rel="noopener noreferrer"&gt;Little's law&lt;/a&gt;, which states that in a &lt;strong&gt;stable system&lt;/strong&gt;, the average concurrency (number of requests concurrently processed by the server), &lt;strong&gt;L&lt;/strong&gt;, is equal to the throughput (average rate of requests), &lt;strong&gt;λ&lt;/strong&gt;, times the latency (average duration of processing each request), &lt;strong&gt;W&lt;/strong&gt;. With this, you can derive that throughput equals average concurrency divided by latency (&lt;strong&gt;λ = L/W&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;So in a thread-per-request model, the throughput will be limited by the number of OS threads available, which depends on the number of physical cores/threads available on the hardware. To work around this, you have to use shared thread pools or asynchronous concurrency, both of which have their drawbacks. Thread pools have many limitations, like thread leaking, deadlocks, resource thrashing, etc. Asynchronous concurrency means you must adapt to a more complex programming style and handle data races carefully. There are also chances for memory leaks, thread locking, etc.&lt;/p&gt;

&lt;p&gt;Another common use case is parallel processing or multi-threading, where you might split a task into subtasks across multiple threads. Here you have to write solutions to avoid data corruption and data races. In some cases, you must also ensure thread synchronization when executing a parallel task distributed over multiple threads. The implementation becomes even more fragile and puts a lot more responsibility on the developer to ensure there are no issues like thread leaks and cancellation delays.&lt;/p&gt;

&lt;p&gt;Project Loom aims to fix these issues in the current concurrency model by introducing two new features: &lt;em&gt;virtual threads&lt;/em&gt; and &lt;em&gt;structured concurrency&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual threads
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Java 19 is scheduled to be released in September 2022, and Virtual threads will be a preview feature. Yayyy!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://openjdk.org/jeps/425" rel="noopener noreferrer"&gt;Virtual threads&lt;/a&gt; are lightweight threads that are not tied to OS threads but are managed by the JVM. They are suitable for thread-per-request programming styles without having the limitations of OS threads. You can create millions of virtual threads without affecting throughput. This is quite similar to coroutines, like &lt;a href="https://go.dev/tour/concurrency/1" rel="noopener noreferrer"&gt;goroutines&lt;/a&gt;, made famous by the Go programming language (Golang).&lt;/p&gt;

&lt;p&gt;The new virtual threads in Java 19 will be pretty easy to use. Compare the below with Golang's goroutines or Kotlin's coroutines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual thread&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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startVirtualThread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, Project Loom!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Goroutine&lt;/strong&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;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, Goroutines!"&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;&lt;strong&gt;Kotlin coroutine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;runBlocking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, Kotlin coroutines!"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun fact: before JDK 1.1, Java had support for green threads (aka virtual threads), but the feature was removed in JDK 1.1 as that implementation was not any better than platform threads.&lt;/p&gt;

&lt;p&gt;The new implementation of virtual threads is done in the JVM, where it maps multiple virtual threads to one or more OS threads, and the developer can use virtual threads or platform threads as per their needs. A few other important aspects of this implementation of virtual threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a &lt;code&gt;Thread&lt;/code&gt; in code, runtime, debugger, and profiler&lt;/li&gt;
&lt;li&gt;It's a Java entity and not a wrapper around a native thread&lt;/li&gt;
&lt;li&gt;Creating and blocking them are cheap operations&lt;/li&gt;
&lt;li&gt;They should not be pooled&lt;/li&gt;
&lt;li&gt;Virtual threads use a work-stealing &lt;code&gt;ForkJoinPool&lt;/code&gt; scheduler&lt;/li&gt;
&lt;li&gt;Pluggable schedulers can be used for asynchronous programming&lt;/li&gt;
&lt;li&gt;A virtual thread will have its own stack memory&lt;/li&gt;
&lt;li&gt;The virtual threads API is very similar to platform threads and hence easier to adopt/migrate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at some examples that show the power of virtual threads.&lt;/p&gt;

&lt;h4&gt;
  
  
  Total number of threads
&lt;/h4&gt;

&lt;p&gt;First, let's see how many platform threads vs. virtual threads we can create on a machine. My machine is Intel Core i9-11900H with 8 cores, 16 threads, and 64GB RAM running Fedora 36.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform threads&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Thread count = "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;LockSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;park&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}).&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine, the code crashed after &lt;strong&gt;32_539&lt;/strong&gt; platform threads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual threads&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicInteger&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startVirtualThread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Thread count = "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;LockSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;park&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine, the process hung after &lt;strong&gt;14_625_956&lt;/strong&gt; virtual threads but didn't crash, and as memory became available, it kept going slowly. You may be wondering why this behavior! It's due to the parked virtual threads being garbage collected, and the JVM is able to create more virtual threads and assign them to the underlying platform thread.&lt;/p&gt;

&lt;h4&gt;
  
  
  Task throughput
&lt;/h4&gt;

&lt;p&gt;Let's try to run 100,000 tasks using platform threads.&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="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultThreadFactory&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;IntStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the &lt;code&gt;newThreadPerTaskExecutor&lt;/code&gt; with the default thread factory and thus uses a thread group. When I ran this code and timed it, I got the numbers shown here. I get better performance when I use a thread pool with &lt;code&gt;Executors.newCachedThreadPool()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 'newThreadPerTaskExecutor' with 'defaultThreadFactory'&lt;/span&gt;
0:18.77 real,   18.15 s user,   7.19 s sys,     135% 3891pu,    0 amem,         743584 mmem
&lt;span class="c"&gt;# 'newCachedThreadPool' with 'defaultThreadFactory'&lt;/span&gt;
0:11.52 real,   13.21 s user,   4.91 s sys,     157% 6019pu,    0 amem,         2215972 mmem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not so bad. Now, let's do the same using virtual threads.&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="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;IntStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I run and time it, I get the following numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0:02.62 real,   6.83 s user,    1.46 s sys,     316% 14840pu,   0 amem,         350268 mmem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is far more performant than using platform threads with thread pools. Of course, these are simple use cases; both thread pools and virtual thread implementations can be further optimized for better performance, but that's not the point of this post.&lt;/p&gt;

&lt;p&gt;Running Java Microbenchmark Harness (JMH) with the same code gives the following results, and you can see that virtual threads outperform platform threads by a huge margin.&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;# Throughput&lt;/span&gt;
Benchmark                             Mode  Cnt  Score   Error  Units
LoomBenchmark.platformThreadPerTask  thrpt    5  0.362 ± 0.079  ops/s
LoomBenchmark.platformThreadPool     thrpt    5  0.528 ± 0.067  ops/s
LoomBenchmark.virtualThreadPerTask   thrpt    5  1.843 ± 0.093  ops/s

&lt;span class="c"&gt;# Average time&lt;/span&gt;
Benchmark                             Mode  Cnt  Score   Error  Units
LoomBenchmark.platformThreadPerTask   avgt    5  5.600 ± 0.768   s/op
LoomBenchmark.platformThreadPool      avgt    5  3.887 ± 0.717   s/op
LoomBenchmark.virtualThreadPerTask    avgt    5  1.098 ± 0.020   s/op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the benchmark &lt;a href="https://github.com/deepu105/java-loom-benchmarks" rel="noopener noreferrer"&gt;source code on GitHub&lt;/a&gt;. Here are some other meaningful benchmarks for virtual threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An interesting benchmark using ApacheBench &lt;a href="https://github.com/ebarlas/project-loom-comparison" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; by &lt;a href="https://twitter.com/ElliotBarlas" rel="noopener noreferrer"&gt;Elliot Barlas&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A benchmark using Akka actors &lt;a href="https://medium.com/@zakgof/a-simple-benchmark-for-jdk-project-looms-virtual-threads-4f43ef8aeb1" rel="noopener noreferrer"&gt;on Medium&lt;/a&gt; by &lt;a href="https://medium.com/@zakgof" rel="noopener noreferrer"&gt;Alexander Zakusylo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JMH benchmarks for I/O and non-I/O tasks &lt;a href="https://github.com/colincachia/loom-benchmark" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt; by &lt;a href="https://twitter.com/colincachia" rel="noopener noreferrer"&gt;Colin Cachia&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Structured concurrency
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Structured concurrency will be an incubator feature in Java 19.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://openjdk.org/jeps/428" rel="noopener noreferrer"&gt;Structured concurrency&lt;/a&gt; aims to simplify multi-threaded and parallel programming. It treats multiple tasks running in different threads as a single unit of work, streamlining error handling and cancellation while improving reliability and observability. This helps to avoid issues like thread leaking and cancellation delays. Being an incubator feature, this might go through further changes during stabilization.&lt;/p&gt;

&lt;p&gt;Consider the following example using &lt;code&gt;java.util.concurrent.ExecutorService&lt;/code&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleOrder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ExecutionException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;esvc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ScheduledThreadPoolExecutor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;updateInventory&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esvc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;updateOrder&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;theInventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Join updateInventory&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;theOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;           &lt;span class="c1"&gt;// Join updateOrder&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Inventory "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theInventory&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" updated for order "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theOrder&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want &lt;code&gt;updateInventory()&lt;/code&gt; and &lt;code&gt;updateOrder()&lt;/code&gt; subtasks to be executed concurrently. Each of those can succeed or fail independently. Ideally, the &lt;code&gt;handleOrder()&lt;/code&gt; method should fail if any subtask fails. However, if a failure occurs in one subtask, things get messy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imagine that &lt;code&gt;updateInventory()&lt;/code&gt; fails and throws an exception. Then, the &lt;code&gt;handleOrder()&lt;/code&gt; method throws an exception when calling &lt;code&gt;inventory.get()&lt;/code&gt;. So far this is fine, but what about &lt;code&gt;updateOrder()&lt;/code&gt;? Since it runs on its own thread, it can complete successfully. But now we have an issue with a mismatch in inventory and order. Suppose the &lt;code&gt;updateOrder()&lt;/code&gt; is an expensive operation. In that case, we are just wasting the resources for nothing, and we will have to write some sort of guard logic to revert the updates done to order as our overall operation has failed.&lt;/li&gt;
&lt;li&gt;Imagine that &lt;code&gt;updateInventory()&lt;/code&gt; is an expensive long-running operation and &lt;code&gt;updateOrder()&lt;/code&gt; throws an error. The &lt;code&gt;handleOrder()&lt;/code&gt; task will be blocked on &lt;code&gt;inventory.get()&lt;/code&gt; even though &lt;code&gt;updateOrder()&lt;/code&gt; threw an error. Ideally, we would like the &lt;code&gt;handleOrder()&lt;/code&gt; task to cancel &lt;code&gt;updateInventory()&lt;/code&gt; when a failure occurs in &lt;code&gt;updateOrder()&lt;/code&gt; so that we are not wasting time.&lt;/li&gt;
&lt;li&gt;If the thread executing &lt;code&gt;handleOrder()&lt;/code&gt; is interrupted, the interruption is not propagated to the subtasks. In this case &lt;code&gt;updateInventory()&lt;/code&gt; and &lt;code&gt;updateOrder()&lt;/code&gt; will leak and continue to run in the background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these situations, we would have to carefully write workarounds and failsafe, putting all the burden on the developer.&lt;/p&gt;

&lt;p&gt;We can achieve the same functionality with structured concurrency using the code below.&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleOrder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ExecutionException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InterruptedException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StructuredTaskScope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ShutdownOnFailure&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fork&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;updateInventory&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fork&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;updateOrder&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;           &lt;span class="c1"&gt;// Join both forks&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;throwIfFailed&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// ... and propagate errors&lt;/span&gt;

        &lt;span class="c1"&gt;// Here, both forks have succeeded, so compose their results&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Inventory "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resultNow&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" updated for order "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resultNow&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the previous sample using &lt;code&gt;ExecutorService&lt;/code&gt;, we can now use &lt;code&gt;StructuredTaskScope&lt;/code&gt; to achieve the same result while confining the lifetimes of the subtasks to the lexical scope, in this case, the body of the &lt;em&gt;try-with-resources&lt;/em&gt; statement. The code is much more readable, and the intent is also clear. &lt;code&gt;StructuredTaskScope&lt;/code&gt; also ensures the following behavior automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error handling with short-circuiting&lt;/strong&gt; — If either the &lt;code&gt;updateInventory()&lt;/code&gt; or &lt;code&gt;updateOrder()&lt;/code&gt; fails, the other is canceled unless its already completed. This is managed by the cancellation policy implemented by &lt;code&gt;ShutdownOnFailure()&lt;/code&gt;; other policies are possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cancellation propagation&lt;/strong&gt; — If the thread running &lt;code&gt;handleOrder()&lt;/code&gt; is interrupted before or during the call to &lt;code&gt;join()&lt;/code&gt;, both forks are canceled automatically when the thread exits the scope.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability&lt;/strong&gt; — A thread dump would clearly display the task hierarchy, with the threads running &lt;code&gt;updateInventory()&lt;/code&gt; and &lt;code&gt;updateOrder()&lt;/code&gt; shown as children of the scope.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  State of Project Loom
&lt;/h2&gt;

&lt;p&gt;The Loom project started in 2017 and has undergone many changes and proposals. Virtual threads were initially called fibers, but later on they were renamed to avoid confusion. Today with Java 19 getting closer to release, the project has delivered the two features discussed above. One as a preview and another as an incubator. Hence the path to stabilization of the features should be more precise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean to regular Java developers?
&lt;/h2&gt;

&lt;p&gt;When these features are production ready, it should not affect regular Java developers much, as these developers may be using libraries for concurrency use cases. But it can be a big deal in those rare scenarios where you are doing a lot of multi-threading without using libraries. Virtual threads could be a no-brainer replacement for all use cases where you use thread pools today. This will increase performance and scalability in most cases based on the benchmarks out there. Structured concurrency can help simplify the multi-threading or parallel processing use cases and make them less fragile and more maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean to Java library developers?
&lt;/h2&gt;

&lt;p&gt;When these features are production ready, it will be a big deal for libraries and frameworks that use threads or parallelism. Library authors will see huge performance and scalability improvements while simplifying the codebase and making it more maintainable. Most Java projects using thread pools and platform threads will benefit from switching to virtual threads. Candidates include Java server software like Tomcat, Undertow, and Netty; and web frameworks like Spring and Micronaut. I expect most Java web technologies to migrate to virtual threads from thread pools. Java web technologies and trendy reactive programming libraries like RxJava and Akka could also use structured concurrency effectively. This doesn't mean that virtual threads will be the one solution for all; there will still be use cases and benefits for asynchronous and reactive programming.&lt;/p&gt;




&lt;p&gt;If you like this article, please leave a like or a comment.&lt;/p&gt;

&lt;p&gt;You can follow me on &lt;a href="https://twitter.com/deepu105" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/deepu05/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cover image was created using a photo by &lt;a href="https://unsplash.com/@tama66" rel="noopener noreferrer"&gt;Peter Herrmann&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post was originally published on the &lt;a href="https://developer.okta.com/blog/2022/08/26/state-of-java-project-loom" rel="noopener noreferrer"&gt;Okta Developer Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>concurrency</category>
      <category>loom</category>
      <category>openjdk</category>
    </item>
    <item>
      <title>How to Deploy JHipster Microservices on Amazon EKS Using Terraform and Kubernetes</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Tue, 05 Jul 2022 13:50:34 +0000</pubDate>
      <link>https://dev.to/jhipster/how-to-deploy-jhipster-microservices-on-amazon-eks-using-terraform-and-kubernetes-49a5</link>
      <guid>https://dev.to/jhipster/how-to-deploy-jhipster-microservices-on-amazon-eks-using-terraform-and-kubernetes-49a5</guid>
      <description>&lt;p&gt;When it comes to infrastructure, public clouds are the most popular choice these days, especially Amazon Web Services (AWS). If you are in one of those lucky or unlucky (depending on how you see it) teams running microservices, then you need a way to orchestrate their deployments. When it comes to orchestrating microservices, Kubernetes is the de-facto choice. Most public cloud providers also provide managed Kubernetes as a service; for example, Google provides Google Kubernetes Engine (GKE), Microsoft provides Azure Kubernetes Service (AKS), and Amazon provides Amazon Elastic Kubernetes Service (EKS).&lt;/p&gt;

&lt;p&gt;This doesn't mean that deploying and managing microservices on the public cloud is easy; each cloud service comes with its own challenges and pain. This is especially true for Amazon EKS, which, in my opinion, is the most difficult Kubernetes service to use, but also one of the most flexible. This is because EKS consists of some clever orchestrations doing a complex dance on top of other AWS services like EC2, EBS, etc.&lt;/p&gt;

&lt;p&gt;If you want to run a microservice stack on EKS, you will need to spend some extra time and effort setting it up and managing it. This is where infrastructure as code (IaC) tools like &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; come in handy.&lt;/p&gt;

&lt;p&gt;So here is what you will learn to do today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffold a Java microservice stack using JHipster, Spring Boot, and Spring Cloud&lt;/li&gt;
&lt;li&gt;Create an EKS cluster, Virtual Private Cloud (VPC), subnets, and required Kubernetes add-ons using Terraform on AWS&lt;/li&gt;
&lt;li&gt;Set up OIDC authentication for the microservice stack using Okta&lt;/li&gt;
&lt;li&gt;Build and deploy the microservice stack to the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://portal.aws.amazon.com/billing/signup" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt; with the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/security_iam_id-based-policy-examples.html" rel="noopener noreferrer"&gt;IAM permissions to create EKS clusters&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS CLI &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;installed&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html" rel="noopener noreferrer"&gt;configured&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html" rel="noopener noreferrer"&gt;AWS IAM Authenticator&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed and configured&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.terraform.io/downloads" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://sdkman.io/usage" rel="noopener noreferrer"&gt;Java 11+&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cli.okta.com/" rel="noopener noreferrer"&gt;Okta CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jhipster.tech/installation/" rel="noopener noreferrer"&gt;JHipster&lt;/a&gt; CLI installed&lt;/li&gt;
&lt;li&gt;[Optional] &lt;a href="https://github.com/kdash-rs/kdash" rel="noopener noreferrer"&gt;KDash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Terraform, why not CloudFormation?
&lt;/h2&gt;

&lt;p&gt;At this point, the first question that might pop up in your mind would be, "Why not use &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt;?". It's a good question; after all, CloudFormation is built by AWS and hence sounds like an excellent solution to manage AWS resources. But anyone who has tried both CloudFormation and Terraform will probably tell you to forget that CloudFormation even exists. I think CloudFormation is far more complex and less developer-friendly than Terraform. You also need to write a lot more boilerplate with CloudFormation in YAML or JSON. Yikes! And most importantly, Terraform is far more powerful and flexible than CloudFormation. It's cross-platform, which means you can take care of all your infrastructure management needs on any platform with one tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold a Java microservice stack using JHipster
&lt;/h2&gt;

&lt;p&gt;You need a microservice stack to deploy to the cluster. I'm using a microservice stack scaffolded for demo purposes using &lt;a href="https://www.jhipster.tech" rel="noopener noreferrer"&gt;JHipster&lt;/a&gt;. You can use another microservice stack if you want. If you prefer using the same application as in this demo, then you can either scaffold it using JHipster &lt;a href="https://www.jhipster.tech/jdl/intro" rel="noopener noreferrer"&gt;JDL&lt;/a&gt; or clone the sample repository from &lt;a href="https://github.com/oktadev/okta-jhipster-k8s-eks-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lbqsamb41i5wpmuht5c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lbqsamb41i5wpmuht5c.jpg" alt="JHipster microservice architecture" width="799" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Scaffold the microservice stack using JHipster&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;&lt;span class="nb"&gt;mkdir &lt;/span&gt;jhipster-microservice-stack
&lt;span class="nb"&gt;cd &lt;/span&gt;jhipster-microservice-stack
&lt;span class="c"&gt;# download the JDL file.&lt;/span&gt;
jhipster download https://raw.githubusercontent.com/oktadev/okta-jhipster-k8s-eks-microservices-example/main/apps.jdl
&lt;span class="c"&gt;# Update the `dockerRepositoryName` property to use your Docker Repository URI/Name.&lt;/span&gt;
&lt;span class="c"&gt;# scaffold the apps.&lt;/span&gt;
jhipster jdl apps.jdl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Clone the sample repository&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;git clone https://github.com/oktadev/okta-jhipster-k8s-eks-microservices-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In either case, remember to change the Docker repository name to match your Docker repository.&lt;/p&gt;

&lt;p&gt;The JHipster scaffolded sample application has a gateway application, two microservices, and uses &lt;a href="https://www.jhipster.tech/jhipster-registry/" rel="noopener noreferrer"&gt;JHipster Registry&lt;/a&gt; for service discovery and centralized configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an EKS cluster using Terraform
&lt;/h2&gt;

&lt;p&gt;Now let us move on to the important part of the tutorial. Creating an EKS cluster in AWS is not as straightforward as in Google Cloud Platform (GCP). You need to also create a lot more resources for everything to work correctly without surprises. You will be using a bunch of Terraform providers to help with this, and you will also use some prebuilt Terraform modules like &lt;a href="https://github.com/terraform-aws-modules/terraform-aws-vpc" rel="noopener noreferrer"&gt;AWS VPC Terraform module&lt;/a&gt; and &lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints" rel="noopener noreferrer"&gt;Amazon EKS Blueprints for Terraform&lt;/a&gt; to reduce the amount of boilerplate you need to write.&lt;/p&gt;

&lt;p&gt;These are the AWS resources and VPC architecture you will create:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wk8rdzsjdbub2um2s76.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wk8rdzsjdbub2um2s76.jpg" alt="AWS EKS and VPC architecture" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Terraform configuration
&lt;/h3&gt;

&lt;p&gt;First, make sure you use a specific version of the providers as different versions might use different attributes and features. Create a &lt;code&gt;versions.tf&lt;/code&gt; file:&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;mkdir &lt;/span&gt;terraform
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform
&lt;span class="nb"&gt;touch &lt;/span&gt;versions.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 3.72"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/kubernetes"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 2.10"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;helm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/helm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 2.4.1"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/random"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 3.2.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;nullres&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/null"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 3.1"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you need to define variables and configure the providers. Create a &lt;code&gt;config.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;config.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ##  To save state in s3. Update to suit your needs&lt;/span&gt;
&lt;span class="c1"&gt;# backend "s3" {&lt;/span&gt;
&lt;span class="c1"&gt;#   bucket = "create-an-s3-bucket-and-provide-name-here"&lt;/span&gt;
&lt;span class="c1"&gt;#   region = local.region&lt;/span&gt;
&lt;span class="c1"&gt;#   key    = "eks-cluster-with-new-vpc/terraform.tfstate"&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_string"&lt;/span&gt; &lt;span class="s2"&gt;"suffix"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"okta-jhipster-eks-${random_string.suffix.result}"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.22"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_types&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t2.large"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# can be multiple, comma separated&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;azs&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Blueprint&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="nx"&gt;GitHubRepo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws-ia/terraform-aws-eks-blueprints"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Kubernetes provider&lt;/span&gt;
&lt;span class="c1"&gt;# You should **not** schedule deployments and services in this workspace.&lt;/span&gt;
&lt;span class="c1"&gt;# This keeps workspaces modular (one for provision EKS, another for scheduling&lt;/span&gt;
&lt;span class="c1"&gt;# Kubernetes resources) as per best practices.&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1alpha1"&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
    &lt;span class="c1"&gt;# This requires the awscli to be installed locally where Terraform is executed&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_id&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="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"helm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_endpoint&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_ca_certificate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_certificate_authority_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"client.authentication.k8s.io/v1alpha1"&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
      &lt;span class="c1"&gt;# This requires the awscli to be installed locally where Terraform is executed&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"get-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--cluster-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_id&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can uncomment the &lt;code&gt;backend&lt;/code&gt; section above to save state in S3 instead of your local filesystem. This is recommended for production setup so that everyone in a team has the same state. This file defines configurable and local variables used across the workspace and configures some of the providers used. The Kubernetes provider is included in this file so the EKS module can complete successfully. Otherwise, it throws an error when creating &lt;code&gt;kubernetes_config_map.aws_auth&lt;/code&gt;. The helm provider is used to install Kubernetes add-ons to the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the VPC
&lt;/h3&gt;

&lt;p&gt;Next up, you need a VPC, subnets, route tables, and other networking bits. You will use the &lt;code&gt;vpc&lt;/code&gt; module from the &lt;a href="https://github.com/terraform-aws-modules" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-modules&lt;/code&gt;&lt;/a&gt; repository. This module is a wrapper around the AWS VPC module. It makes it easier to configure VPCs and all the other required networking resources. Create a &lt;code&gt;vpc.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;vpc.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# VPC, Subnets, Internet gateway, Route tables, etc.&lt;/span&gt;
&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/vpc/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.0"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;

  &lt;span class="nx"&gt;azs&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;single_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Manage so we can name&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_network_acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_network_acl_tags&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_route_table&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_route_table_tags&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;manage_default_security_group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;default_security_group_tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${local.name}-default"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;public_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/cluster/${local.name}"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"shared"&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/elb"&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;private_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/cluster/${local.name}"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"shared"&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new VPC, three private subnets, and three public subnets,&lt;/li&gt;
&lt;li&gt;Internet gateway and NAT gateway for the public subnets,&lt;/li&gt;
&lt;li&gt;and AWS routes for the gateways, public/private route tables, and route table associations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build the EKS cluster
&lt;/h3&gt;

&lt;p&gt;Now that you have the networking part done, you can build configurations for the EKS cluster and its add-ons. You will use the &lt;code&gt;eks_blueprints&lt;/code&gt; module from &lt;a href="https://aws-ia.github.io/terraform-aws-eks-blueprints/v4.0.9/" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-eks-blueprints&lt;/code&gt;&lt;/a&gt;, which is a wrapper around the &lt;a href="https://github.com/terraform-aws-modules" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-aws-modules&lt;/code&gt;&lt;/a&gt; and provides additional modules to configure EKS add-ons.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;eks-cluster.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;eks-cluster.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# EKS cluster, worker nodes, security groups, IAM roles, K8s add-ons, etc.&lt;/span&gt;
&lt;span class="c1"&gt;#---------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks_blueprints"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws-ia/terraform-aws-eks-blueprints?ref=v4.0.9"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_version&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;

  &lt;span class="nx"&gt;managed_node_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;node_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"managed-ondemand"&lt;/span&gt;
      &lt;span class="nx"&gt;instance_types&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_types&lt;/span&gt;
      &lt;span class="nx"&gt;min_size&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_ids&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks_blueprints_kubernetes_addons"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.0.9"&lt;/span&gt;

  &lt;span class="nx"&gt;eks_cluster_id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_id&lt;/span&gt;
  &lt;span class="nx"&gt;eks_cluster_endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;eks_oidc_provider&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_provider&lt;/span&gt;
  &lt;span class="nx"&gt;eks_cluster_version&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_version&lt;/span&gt;

  &lt;span class="c1"&gt;# EKS Managed Add-ons&lt;/span&gt;
  &lt;span class="nx"&gt;enable_amazon_eks_vpc_cni&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_amazon_eks_coredns&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_amazon_eks_kube_proxy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# K8S Add-ons&lt;/span&gt;
  &lt;span class="nx"&gt;enable_aws_load_balancer_controller&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_metrics_server&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_cluster_autoscaler&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_aws_cloudwatch_metrics&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# To update local kubeconfig with new cluster details&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"kubeconfig"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints_kubernetes_addons&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks --region ${local.region}  update-kubeconfig --name $AWS_CLUSTER_NAME"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;AWS_CLUSTER_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;eks_blueprints&lt;/code&gt; module definition creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKS Cluster Control plane with one &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html" rel="noopener noreferrer"&gt;managed node group&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate-profile.html" rel="noopener noreferrer"&gt;fargate profile&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Cluster and node security groups and rules, IAM roles and policies required,&lt;/li&gt;
&lt;li&gt;and AWS Key Management Service (KMS) configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;eks_blueprints_kubernetes_addons&lt;/code&gt; module definition creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon EKS add-ons vpc-cni, CoreDNS, and kube-proxy,&lt;/li&gt;
&lt;li&gt;AWS Load Balancer Controller that provisions an AWS Network Load Balancer for distributing traffic,&lt;/li&gt;
&lt;li&gt;and &lt;a href="https://github.com/kubernetes-sigs/metrics-server" rel="noopener noreferrer"&gt;Metrics Server&lt;/a&gt;, and Cluster Autoscaler for scaling your workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;null_resource&lt;/code&gt; configuration updates your local kubeconfig with the new cluster details. It's not a required step for provisioning but just a handy hack.&lt;/p&gt;

&lt;p&gt;Finally, you can also define some outputs to be captured. Create a &lt;code&gt;outputs.tf&lt;/code&gt; file:&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;touch &lt;/span&gt;outputs.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_private_subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC private subnet CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets_cidr_blocks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_public_subnet_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC public subnet CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnets_cidr_blocks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC CIDR"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr_block&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_cluster_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS cluster ID"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroups"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node groups"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_node_groups&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroup_ids"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node group ids"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_node_groups_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroup_arns"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node group arns"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_node_group_arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroup_role_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node group role name"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_node_group_iam_role_names&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"eks_managed_nodegroup_status"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"EKS managed node group status"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed_node_groups_status&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"configure_kubectl"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_blueprints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configure_kubectl&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provision the cluster
&lt;/h3&gt;

&lt;p&gt;Our Terraform definitions are ready. Now you can provision the cluster. First, initialize Terraform workspace and plan the changes:&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;# download modules and providers. Initialize state.&lt;/span&gt;
terraform init
&lt;span class="c"&gt;# see a preview of what will be done&lt;/span&gt;
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the plan and make sure everything is correct. Ensure you have configured your AWS CLI and IAM Authenticator to use the correct AWS account. If not, run the following:&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;# Visit https://console.aws.amazon.com/iam/home?#/security_credentials for creating access keys&lt;/span&gt;
aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm by typing &lt;code&gt;yes&lt;/code&gt; when prompted. This will take a while (15-20 minutes), so sit back and have a coffee or contemplate what led you to this point in life. 😉&lt;/p&gt;

&lt;p&gt;Once the EKS cluster is ready, you will see the output variables printed to the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;configure_kubectl&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks --region eu-west-1 update-kubeconfig --name okta-tf-demo"&lt;/span&gt;
&lt;span class="nx"&gt;eks_cluster_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"okta-tf-demo"&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroup_arns&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"arn:aws:eks:eu-west-1:216713166862:nodegroup/okta-tf-demo/managed-ondemand-20220610125341399700000010/f0c0a6d6-b8e1-cf91-3d21-522552d6bc2e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroup_ids&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"okta-tf-demo:managed-ondemand-20220610125341399700000010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroup_role_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"okta-tf-demo-managed-ondemand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroup_status&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;eks_managed_nodegroups&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tolist&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"node"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"arn:aws:eks:eu-west-1:216713166862:nodegroup/okta-tf-demo/managed-ondemand-20220610125341399700000010/f0c0a6d6-b8e1-cf91-3d21-522552d6bc2e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_iam_instance_profile_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"arn:aws:iam::216713166862:instance-profile/okta-tf-demo-managed-ondemand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_iam_instance_profile_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"okta-tf-demo-managed-ondemand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_iam_role_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"arn:aws:iam::216713166862:role/okta-tf-demo-managed-ondemand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_iam_role_name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"okta-tf-demo-managed-ondemand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"okta-tf-demo:managed-ondemand-20220610125341399700000010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_launch_template_arn"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_launch_template_id"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_launch_template_latest_version"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="s2"&gt;"managed_nodegroup_status"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"ACTIVE"&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="nx"&gt;vpc_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="nx"&gt;vpc_private_subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.10.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.11.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.12.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;vpc_public_subnet_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"10.0.2.0/24"&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;You should see the cluster details if you run &lt;code&gt;kdash&lt;/code&gt; or &lt;code&gt;kubectl get nodes&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ukr6v224k21n7whazi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ukr6v224k21n7whazi.png" alt="EKS cluster in KDash" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The EKS cluster defined here will not come under AWS free tier; hence, running this will cost money, so delete the cluster as soon as you finish the tutorial to keep the cost within a few dollars.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Set up OIDC authentication using Okta
&lt;/h2&gt;

&lt;p&gt;You can proceed to deploy the sample application. You could skip this step if you used a sample that does not use Okta or OIDC for authentication.&lt;/p&gt;

&lt;p&gt;First, navigate to the &lt;strong&gt;store&lt;/strong&gt; application folder.&lt;/p&gt;

&lt;p&gt;Before you begin, you’ll need a free Okta developer account. Install the &lt;a href="https://cli.okta.com/" rel="noopener noreferrer"&gt;Okta CLI&lt;/a&gt; and run &lt;code&gt;okta register&lt;/code&gt; to sign up for a new account. If you already have an account, run &lt;code&gt;okta login&lt;/code&gt;. Then, run &lt;code&gt;okta apps create jhipster&lt;/code&gt;. Select the default app name, or change it as you see fit. Accept the default Redirect URI values provided for you.&lt;/p&gt;

&lt;p&gt;The Okta CLI streamlines configuring a JHipster app and does several things for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates an OIDC app with the correct redirect URIs:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;login: &lt;code&gt;http://localhost:8080/login/oauth2/code/oidc&lt;/code&gt; and &lt;code&gt;http://localhost:8761/login/oauth2/code/oidc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;logout: &lt;code&gt;http://localhost:8080&lt;/code&gt; and &lt;code&gt;http://localhost:8761&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Creates &lt;code&gt;ROLE_ADMIN&lt;/code&gt; and &lt;code&gt;ROLE_USER&lt;/code&gt; groups that JHipster expects&lt;/li&gt;
&lt;li&gt;Adds your current user to the &lt;code&gt;ROLE_ADMIN&lt;/code&gt; and &lt;code&gt;ROLE_USER&lt;/code&gt; groups&lt;/li&gt;
&lt;li&gt;Creates a groups claim in your default authorization server and adds the user’s groups to it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The &lt;a href="http://localhost:8761%5C*" rel="noopener noreferrer"&gt;http://localhost:8761\*&lt;/a&gt; redirect URIs are for the JHipster Registry, which is often used when creating microservices with JHipster. The Okta CLI adds these by default.&lt;/p&gt;

&lt;p&gt;The OIDC app configuration will be written to &lt;code&gt;.okta.env&lt;/code&gt; file in the folder where you ran the command.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Make sure to add the newly created &lt;code&gt;.okta.env&lt;/code&gt; file to your &lt;code&gt;.gitignore&lt;/code&gt; file so that you don't accidentally expose your credentials to the public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Update &lt;code&gt;kubernetes/registry-k8s/application-configmap.yml&lt;/code&gt; with the OIDC configuration from the &lt;code&gt;.okta.env&lt;/code&gt; file. The Spring Cloud Config server reads from this file and shares the values with the gateway and microservices.&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;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;application.yml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;spring:&lt;/span&gt;
      &lt;span class="s"&gt;security:&lt;/span&gt;
        &lt;span class="s"&gt;oauth2:&lt;/span&gt;
          &lt;span class="s"&gt;client:&lt;/span&gt;
            &lt;span class="s"&gt;provider:&lt;/span&gt;
              &lt;span class="s"&gt;oidc:&lt;/span&gt;
                &lt;span class="s"&gt;issuer-uri: https://&amp;lt;your-okta-domain&amp;gt;/oauth2/default&lt;/span&gt;
            &lt;span class="s"&gt;registration:&lt;/span&gt;
              &lt;span class="s"&gt;oidc:&lt;/span&gt;
                &lt;span class="s"&gt;client-id: &amp;lt;client-id&amp;gt;&lt;/span&gt;
                &lt;span class="s"&gt;client-secret: &amp;lt;client-secret&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, configure the JHipster Registry to use OIDC for authentication. Modify &lt;code&gt;kubernetes/registry-k8s/jhipster-registry.yml&lt;/code&gt; to enable the &lt;code&gt;oauth2&lt;/code&gt; profile, which is preconfigured in the JHipster Registry application.&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;SPRING_PROFILES_ACTIVE&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod,k8s,oauth2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application is now ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure secrets
&lt;/h3&gt;

&lt;p&gt;If you have noticed, you are setting secrets in plain text on the &lt;code&gt;application-configmap.yml&lt;/code&gt; file, which is not ideal and is not a best practice for security. For the specific JHipster application, you can use the encrypt functionality provided by the JHipster Registry to encrypt the secrets. See &lt;a href="https://developer.okta.com/blog/2021/06/01/kubernetes-spring-boot-jhipster#encrypt-your-secrets-with-spring-cloud-config" rel="noopener noreferrer"&gt;Encrypt Your Secrets with Spring Cloud Config&lt;/a&gt; to learn how to do this. But that would also rely on a base64 encoded encryption key added as a Kubernetes Secret, which still can be decoded. The best way to do this would be to use &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;, an external service like &lt;a href="https://www.hashicorp.com/products/vault" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt;, or &lt;a href="https://github.com/bitnami-labs/sealed-secrets" rel="noopener noreferrer"&gt;Sealed Secrets&lt;/a&gt;. To learn more about these methods see &lt;a href="https://developer.okta.com/blog/2021/06/01/kubernetes-spring-boot-jhipster#encrypt-your-kubernetes-secrets" rel="noopener noreferrer"&gt;Encrypt Your Kubernetes Secrets&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the microservice stack
&lt;/h2&gt;

&lt;p&gt;You are ready to deploy to our shiny new EKS cluster, but first, you need to build and push the Docker images to a container registry. You can use &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry (ECR)&lt;/a&gt; or any other container registry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Docker images
&lt;/h3&gt;

&lt;p&gt;You need to build Docker images for each app. This is specific to the JHipster application used in this tutorial which uses &lt;a href="https://github.com/GoogleContainerTools/jib" rel="noopener noreferrer"&gt;Jib&lt;/a&gt; to build the images. Make sure you are logged into Docker using &lt;code&gt;docker login&lt;/code&gt;. Navigate to each app folder (&lt;strong&gt;store&lt;/strong&gt;, &lt;strong&gt;invoice&lt;/strong&gt;, &lt;strong&gt;product&lt;/strong&gt;) and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;docker-repo-uri-or-name&amp;gt;/&amp;lt;image-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Image names should be &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;invoice&lt;/code&gt;, and &lt;code&gt;product&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the applications to EKS
&lt;/h3&gt;

&lt;p&gt;Start the deployment using the handy script provided by JHipster. You could also manually apply deployments using &lt;code&gt;kubectl apply -f &amp;lt;file&amp;gt;&lt;/code&gt; commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;kubernetes
./kubectl-apply.sh &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzjl4sbkienmdyngbc7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzjl4sbkienmdyngbc7t.png" alt="EKS pods in KDash" width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also run the following command to see the status of the deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View the registry using port-forwarding as follows, and you will be able to access the application at &lt;code&gt;http://localhost:8761&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/jhipster-registry &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster 8761
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access the gateway application using port-forwarding as follows, and you will be able to access the application at &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/store &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, you can access the application via the load balancer exposed. Find the external IP of the &lt;code&gt;store&lt;/code&gt; service by navigating to the service tab in KDash or by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc store &lt;span class="nt"&gt;-n&lt;/span&gt; jhipster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the Okta Admin Console and go to &lt;strong&gt;Applications&lt;/strong&gt; &amp;gt; &lt;strong&gt;Applications&lt;/strong&gt; from left-hand navigation. Find the application you created earlier with &lt;code&gt;okta apps create jhipster&lt;/code&gt; and add the external IP from &lt;code&gt;kubectl get svc&lt;/code&gt; command to the &lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt; and &lt;strong&gt;Sign-out redirect URIs&lt;/strong&gt;. Make sure to use the same paths as the current &lt;code&gt;localhost&lt;/code&gt; entries.&lt;/p&gt;

&lt;p&gt;Now you should be able to visit the external IP of the &lt;code&gt;store&lt;/code&gt; service on port 8080 and see the application, and you should be able to log in using your Okta credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tear down the cluster with Terraform
&lt;/h2&gt;

&lt;p&gt;Once you are done with the tutorial, you can delete the cluster and all the resources created using Terraform by running the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;terraform
&lt;span class="c"&gt;# The commands below might take a while to finish.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.eks_blueprints_kubernetes_addons"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;span class="c"&gt;# If deleting VPC fails, then manually delete the load balancers and security groups&lt;/span&gt;
&lt;span class="c"&gt;# for the load balancer associated with the VPC from AWS EC2 console and try again.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.eks_blueprints"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module.vpc"&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;span class="c"&gt;# cleanup anything left over.&lt;/span&gt;
terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learn more about Java Microservices, EKS, Kubernetes, and JHipster
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about Kubernetes, OIDC, or using OIDC with Kubernetes, and security in general, check out these additional resources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2022/06/09/cloud-native-java-microservices-with-istio" rel="noopener noreferrer"&gt;Cloud Native Java Microservices with JHipster and Istio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/06/01/kubernetes-spring-boot-jhipster#encrypt-your-kubernetes-secrets" rel="noopener noreferrer"&gt;Kubernetes to the Cloud with Spring Boot and JHipster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/11/08/k8s-api-server-oidc" rel="noopener noreferrer"&gt;How to Secure Your Kubernetes Cluster with OpenID Connect and RBAC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/12/02/k8s-security-best-practices" rel="noopener noreferrer"&gt;How to Secure Your Kubernetes Clusters With Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/10/08/secure-access-to-aws-eks" rel="noopener noreferrer"&gt;Secure Access to AWS EKS Clusters for Admins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2022/06/06/microservices-digitalocean-kubernetes" rel="noopener noreferrer"&gt;Run Microservices on DigitalOcean with Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2022/05/05/kubernetes-microservices-azure" rel="noopener noreferrer"&gt;Kubernetes Microservices on Azure with Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find all the code from this example on &lt;a href="https://github.com/oktadev/okta-jhipster-k8s-eks-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you liked this tutorial, chances are you'll enjoy the others we publish. Please follow &lt;a href="https://twitter.com/oktadev" rel="noopener noreferrer"&gt;@oktadev on Twitter&lt;/a&gt; and &lt;a href="https://youtube.com/oktadev" rel="noopener noreferrer"&gt;subscribe to our YouTube channel&lt;/a&gt; to get notified when we publish new developer tutorials.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>jhipster</category>
      <category>eks</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Cloud Native Java Microservices with JHipster and Istio</title>
      <dc:creator>Deepu K Sasidharan</dc:creator>
      <pubDate>Tue, 28 Jun 2022 10:24:09 +0000</pubDate>
      <link>https://dev.to/jhipster/cloud-native-java-microservices-with-jhipster-and-istio-1emb</link>
      <guid>https://dev.to/jhipster/cloud-native-java-microservices-with-jhipster-and-istio-1emb</guid>
      <description>&lt;p&gt;Microservices are not everyone's cup of tea, and they shouldn't be. Not every problem can or should be solved by microservices. Sometimes building a simple monolith is a far better option. Microservices are solutions for use cases where scale and scalability are important. A few years ago, microservices were all the rage, made popular, especially by companies like Netflix, Spotify, Google, etc. While the hype has died down a bit, genuine use cases still exist. With ongoing advances in cloud computing technologies, building microservices as cloud-native services is the way to go due to many benefits.&lt;/p&gt;

&lt;p&gt;Today we will look at building a cloud-native Java microservice stack that utilizes a service mesh to provide most of the distributed system needs and we'll deploy it to the cloud using Kubernetes.&lt;/p&gt;

&lt;p&gt;So here is what we will do today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a Java microservice stack using JHipster, Spring Boot, and Spring Cloud&lt;/li&gt;
&lt;li&gt;Create a Google Kubernetes Engine (GKE) cluster&lt;/li&gt;
&lt;li&gt;Deploy Istio service mesh to the cluster&lt;/li&gt;
&lt;li&gt;Set up monitoring and observability&lt;/li&gt;
&lt;li&gt;Deploy and monitor the microservices to the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Platform&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; installed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jhipster.tech/installation/" rel="noopener noreferrer"&gt;JHipster&lt;/a&gt; installed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/sdk/docs/install" rel="noopener noreferrer"&gt;Google Cloud SDK&lt;/a&gt; installed and configured on your machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; or &lt;a href="https://github.com/kdash-rs/kdash" rel="noopener noreferrer"&gt;KDash&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Basic understanding of Java, Spring, Containers, and Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you prefer to follow along by watching a video, see [How to Build Low-Code Microservices on the Cloud Using Istio, JHipster, and Kubernetes] on the &lt;a href="https://youtube.com/oktadev" rel="noopener noreferrer"&gt;OktaDev YouTube channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/zGpnIhRgMaM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why build cloud-native microservices using a service mesh?
&lt;/h2&gt;

&lt;p&gt;Before we dive into building a cloud-native microservice stack, let's look at what a service mesh is and the benefits of using one.&lt;/p&gt;

&lt;p&gt;A service mesh provides features to help with common distributed microservice challenges. Like service discovery, routing, load balancing, and so on. Today we will be using &lt;a href="https://istio.io/" rel="noopener noreferrer"&gt;Istio&lt;/a&gt;, one of the most popular service mesh solutions available. Istio is tailored for distributed application architectures, especially those you might run in Kubernetes. Istio plays nicely with Kubernetes, so nicely that you might think that it's part of the Kubernetes platform itself. Istio isn't the only service mesh around; we also have platforms like &lt;a href="https://linkerd.io/" rel="noopener noreferrer"&gt;Linkerd&lt;/a&gt; and &lt;a href="https://www.consul.io/" rel="noopener noreferrer"&gt;Consul&lt;/a&gt;, which are also quite popular.&lt;/p&gt;

&lt;p&gt;Istio specifically provides the following features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure service-to-service communication over TLS. Of course, with support for identity-based authentication and authorization.&lt;/li&gt;
&lt;li&gt;Service discovery, so that your microservices can discover each other.&lt;/li&gt;
&lt;li&gt;Automatic load balancing for the services&lt;/li&gt;
&lt;li&gt;Traffic control features like routing, circuit breaking, retries, fail-overs, and fault injection.&lt;/li&gt;
&lt;li&gt;A pluggable policy layer that can enforce stuff like access control, rate limiting, A/B testing, traffic splits, quotas, etc.&lt;/li&gt;
&lt;li&gt;It also provides automatic metrics, logs, and traces for all traffic within the cluster from Ingress to Egress and between pods.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is Istio Service Mesh?
&lt;/h3&gt;

&lt;p&gt;Let's take a quick look at Istio internals. The Istio architecture can be classified into two distinct planes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qhlxbs4n5145a97m231.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qhlxbs4n5145a97m231.png" alt="Istio Service Mesh Architecture" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control plane&lt;/strong&gt;: It consists of the istiod demon, and it manages and configures the envoy proxies to route traffic. The control plane also enforces policies and collects telemetry, and includes components like Pilot for traffic management, Citadel to manage security, and Galley to manage configurations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data plane&lt;/strong&gt;: It's made of &lt;a href="https://www.envoyproxy.io/" rel="noopener noreferrer"&gt;Envoy&lt;/a&gt; proxies deployed as sidecars to our application containers. Envoy is a high-performance, lightweight distributed proxy. It controls all the incoming and outgoing traffic to the container it is attached to.&lt;/p&gt;

&lt;p&gt;We can use tools like &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;, &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;, &lt;a href="https://www.kiali.io/" rel="noopener noreferrer"&gt;Kiali&lt;/a&gt; and &lt;a href="https://zipkin.io/" rel="noopener noreferrer"&gt;Zipkin&lt;/a&gt; for monitoring and observability as they work well with the telemetry provided by Istio. You can use these or use your existing monitoring stack as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Java Microservices Stack using JHipster
&lt;/h2&gt;

&lt;p&gt;Before you proceed, ensure you have installed JHipster. If not, install it using the command &lt;code&gt;npm -g install generator-jhipster&lt;/code&gt;. At the moment of writing, I'm using JHipster version &lt;strong&gt;7.8.1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will be using the &lt;a href="https://www.jhipster.tech/jdl/intro" rel="noopener noreferrer"&gt;JHipster Domain Language (JDL)&lt;/a&gt; to define our microservices, entities, and deployment options. But first, let's take a look at the architecture we will be building today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nidl5ydet0tam4b61n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nidl5ydet0tam4b61n6.png" alt="Istio Microservice Architecture" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have the Istio control plane taking care of policy, load balancing, etc. We also have the Istio Ingress gateway to route all external traffic to our applications. We have four microservices. First is a gateway application created by JHipster that acts as our React GUI and authentication layer. The remaining are services that provide APIs. Each of our containers will have an envoy proxy as an auto-injected sidecar. We hook up Grafana, Prometheus, Zipkin, and Kiali to the telemetry provided by Istio so that we have monitoring and observability for our cluster. Each microservice also has its own database.&lt;/p&gt;

&lt;p&gt;If you would prefer not to build the application yourself, clone the example from &lt;a href="https://github.com/oktadev/okta-java-spring-k8s-istio-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/oktadev/okta-java-spring-k8s-istio-microservices-example.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not an overly complex architecture, but it's also not that simple. First, let us define our microservice using JDL. Create a file called &lt;code&gt;app.jdl&lt;/code&gt; and paste the following content into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;
    &lt;span class="n"&gt;applicationType&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;
    &lt;span class="n"&gt;packageName&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;
    &lt;span class="n"&gt;serviceDiscoveryType&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;authenticationType&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
    &lt;span class="n"&gt;prodDatabaseType&lt;/span&gt; &lt;span class="n"&gt;postgresql&lt;/span&gt;
    &lt;span class="n"&gt;cacheProvider&lt;/span&gt; &lt;span class="n"&gt;hazelcast&lt;/span&gt;
    &lt;span class="n"&gt;buildTool&lt;/span&gt; &lt;span class="n"&gt;gradle&lt;/span&gt;
    &lt;span class="n"&gt;clientFramework&lt;/span&gt; &lt;span class="n"&gt;react&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;
    &lt;span class="n"&gt;applicationType&lt;/span&gt; &lt;span class="n"&gt;microservice&lt;/span&gt;
    &lt;span class="n"&gt;packageName&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
    &lt;span class="n"&gt;serviceDiscoveryType&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;authenticationType&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
    &lt;span class="n"&gt;prodDatabaseType&lt;/span&gt; &lt;span class="n"&gt;postgresql&lt;/span&gt;
    &lt;span class="n"&gt;cacheProvider&lt;/span&gt; &lt;span class="n"&gt;hazelcast&lt;/span&gt;
    &lt;span class="n"&gt;buildTool&lt;/span&gt; &lt;span class="n"&gt;gradle&lt;/span&gt;
    &lt;span class="n"&gt;serverPort&lt;/span&gt; &lt;span class="mi"&gt;8081&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;
    &lt;span class="n"&gt;applicationType&lt;/span&gt; &lt;span class="n"&gt;microservice&lt;/span&gt;
    &lt;span class="n"&gt;packageName&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;
    &lt;span class="n"&gt;serviceDiscoveryType&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;authenticationType&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
    &lt;span class="n"&gt;prodDatabaseType&lt;/span&gt; &lt;span class="n"&gt;postgresql&lt;/span&gt;
    &lt;span class="n"&gt;buildTool&lt;/span&gt; &lt;span class="n"&gt;gradle&lt;/span&gt;
    &lt;span class="n"&gt;serverPort&lt;/span&gt; &lt;span class="mi"&gt;8082&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;
    &lt;span class="n"&gt;applicationType&lt;/span&gt; &lt;span class="n"&gt;microservice&lt;/span&gt;
    &lt;span class="n"&gt;packageName&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;developer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;
    &lt;span class="n"&gt;serviceDiscoveryType&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;authenticationType&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
    &lt;span class="n"&gt;databaseType&lt;/span&gt; &lt;span class="n"&gt;mongodb&lt;/span&gt;
    &lt;span class="n"&gt;cacheProvider&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;enableHibernateCache&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;buildTool&lt;/span&gt; &lt;span class="n"&gt;gradle&lt;/span&gt;
    &lt;span class="n"&gt;serverPort&lt;/span&gt; &lt;span class="mi"&gt;8083&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each application defines its name, package name, authentication type, database, etc. For all supported options and configurations, please refer to the &lt;a href="https://www.jhipster.tech/jdl/applications" rel="noopener noreferrer"&gt;JDL applications documentation&lt;/a&gt;. Each application also defines the &lt;code&gt;applicationType&lt;/code&gt; and the entities it serves. Next, add the entity definitions to the &lt;code&gt;app.jdl&lt;/code&gt; you just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Entities for Store Gateway
 */&lt;/span&gt;
&lt;span class="c1"&gt;// Customer for the store&lt;/span&gt;
&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;firstName&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;lastName&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;gender&lt;/span&gt; &lt;span class="nc"&gt;Gender&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="nf"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;(/^[^&lt;/span&gt;&lt;span class="err"&gt;@\&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]+&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;[^&lt;/span&gt;&lt;span class="err"&gt;@\&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.[^&lt;/span&gt;&lt;span class="err"&gt;@\&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]+&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;/)&lt;/span&gt;
  &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;addressLine1&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;addressLine2&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Gender&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;MALE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FEMALE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OTHER&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="nc"&gt;OneToOne&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;serviceClass&lt;/span&gt;
&lt;span class="n"&gt;paginate&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pagination&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Entities for product microservice
 */&lt;/span&gt;
&lt;span class="c1"&gt;// Product sold by the Online store&lt;/span&gt;
&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;itemSize&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="nc"&gt;ImageBlob&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;XL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;XXL&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;ProductCategory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;ProductOrder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;placedDate&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;invoiceId&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;
  &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;COMPLETED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CANCELLED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;totalPrice&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nc"&gt;OrderItemStatus&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;OrderItemStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OUT_OF_STOCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BACK_ORDER&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="nc"&gt;ManyToOne&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="nc"&gt;OneToMany&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;ProductOrder&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orderItem&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;ProductCategory&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;productCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;serviceClass&lt;/span&gt;
&lt;span class="n"&gt;paginate&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pagination&lt;/span&gt;
&lt;span class="n"&gt;microservice&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProductCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Entities for Invoice microservice
 */&lt;/span&gt;
&lt;span class="c1"&gt;// Invoice for sales&lt;/span&gt;
&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nc"&gt;InvoiceStatus&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;paymentMethod&lt;/span&gt; &lt;span class="nc"&gt;PaymentMethod&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;paymentDate&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;paymentAmount&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;InvoiceStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;PAID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ISSUED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CANCELLED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;trackingCode&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;PaymentMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;CREDIT_CARD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CASH_ON_DELIVERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PAYPAL&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="nc"&gt;OneToMany&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shipment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;serviceClass&lt;/span&gt;
&lt;span class="n"&gt;paginate&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pagination&lt;/span&gt;
&lt;span class="n"&gt;microservice&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Shipment&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Entities for notification microservice
 */&lt;/span&gt;
&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="n"&gt;sentDate&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="nc"&gt;NotificationType&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
  &lt;span class="n"&gt;productId&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;NotificationType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SMS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PARCEL&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;microservice&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define entities for each service and mark the entities as microservice entities. We also define relationships between entities, enums, and other options like pagination, service layer, etc. Please refer to JDL &lt;a href="https://www.jhipster.tech/jdl/entities-fields" rel="noopener noreferrer"&gt;Entities&lt;/a&gt; and &lt;a href="https://www.jhipster.tech/jdl/relationships" rel="noopener noreferrer"&gt;relationships&lt;/a&gt; documentation for more possibilities.&lt;/p&gt;

&lt;p&gt;Now, we are ready to run JHipster. Open a terminal window on the folder where you saved the JDL and run the following command.&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;mkdir &lt;/span&gt;jhipster-istio
&lt;span class="nb"&gt;cd &lt;/span&gt;jhipster-istio

jhipster jdl app.jdl &lt;span class="nt"&gt;--fork&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the applications with all their entities and specified configurations. You should be able to see the gateway application in action by running the following command on the &lt;strong&gt;store&lt;/strong&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./gradlew &lt;span class="c"&gt;# starts the Spring Boot application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see everything that's generated in the &lt;a href="https://github.com/oktadev/okta-java-spring-k8s-istio-microservices-example" rel="noopener noreferrer"&gt;example application on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a GKE cluster and install Istio
&lt;/h2&gt;

&lt;p&gt;To deploy the stack to Google Kubernetes Engine, we need to create a cluster and install Istio. So let's begin by creating a cluster using Google Cloud SDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a cluster
&lt;/h3&gt;

&lt;p&gt;Ensure you are logged into the &lt;a href="https://cloud.google.com/sdk/gcloud" rel="noopener noreferrer"&gt;gcloud CLI&lt;/a&gt; from the command-line and run the following command to create a GKE cluster.&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;# set region and zone&lt;/span&gt;
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;compute/region europe-west1
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;compute/zone europe-west1-b
&lt;span class="c"&gt;# Create a project and enable container APIs&lt;/span&gt;
gcloud projects create jhipster-demo-okta &lt;span class="c"&gt;# You need to also enable billing via GUI&lt;/span&gt;
gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project jhipster-demo-okta
gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;container.googleapis.com

&lt;span class="c"&gt;# Create GKE Cluster&lt;/span&gt;
gcloud container clusters create hello-hipster &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--num-nodes&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--machine-type&lt;/span&gt; n1-standard-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could take anywhere from 5 to 15 minutes. &lt;code&gt;--machine-type&lt;/code&gt; is important as we need more CPU than available in the default setup. Once the cluster is created, it should be set automatically as the current Kubernetes context. You can verify that by running &lt;code&gt;kubectl config current-context&lt;/code&gt;. If the new cluster is not set as the current context, you can set it by running &lt;code&gt;gcloud container clusters get-credentials hello-hipster&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrpwoguep2b4twclgq2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrpwoguep2b4twclgq2y.png" alt="GKE Cluster nodes" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I'm using &lt;a href="https://kdash.cli.rs/" rel="noopener noreferrer"&gt;KDash&lt;/a&gt; to monitor the cluster; you can try it or use kubectl, &lt;a href="https://github.com/derailed/k9s" rel="noopener noreferrer"&gt;k9s&lt;/a&gt;, and so on as you prefer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Install Istio to cluster
&lt;/h3&gt;

&lt;p&gt;As of writing this, I'm using Istio version 1.13.4. You can install &lt;strong&gt;istioctl&lt;/strong&gt; by running the following command, preferably from your home directory.&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;ISTIO_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.13.4
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://istio.io/downloadIstio | sh -
&lt;span class="nb"&gt;cd &lt;/span&gt;istio-&lt;span class="nv"&gt;$ISTIO_VERSION&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to run &lt;strong&gt;istioctl&lt;/strong&gt; from the command line. Now, we can use the CLI to Install Istio to the GKE cluster. Istio provides a few &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; profiles out of the box. We will use the demo profile for demo purposes. You can choose the production or dev profile as well. The command should install Istio and set up everything required on our cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;istioctl &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you run into any trouble with firewall or user privilege issues, please refer to &lt;a href="https://istio.io/latest/docs/setup/platform-setup/gke/" rel="noopener noreferrer"&gt;GKE setup guide from Istio&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the installation is complete, we need to fetch the External IP of the Istio Ingress Gateway. If you are using KDash, you can see it on the services tab, or you can run the following command to get it using kubectl.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc istio-ingressgateway &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install observability tools
&lt;/h3&gt;

&lt;p&gt;Istio also provides addons for most of the popular monitoring and observability tools. Let's install Grafana, Prometheus, Kiali and Zipkin on our cluster. These are preconfigured to work with the telemetry data provided by Istio. Ensure you are in the folder where you installed Istio, like &lt;strong&gt;istio-1.13.4&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;&lt;span class="nb"&gt;cd &lt;/span&gt;istio-&lt;span class="nv"&gt;$ISTIO_VERSION&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/addons/grafana.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/addons/prometheus.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/addons/kiali.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; samples/addons/extras/zipkin.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4x0lyz0wlq5xnqduajm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4x0lyz0wlq5xnqduajm.png" alt="GKE Cluster with Istio pods" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we look at the istio-system namespace, we can see all the Istio components along with Grafana, Prometheus, Kiali, and Zipkin running. You can also see this by running the following command .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy the microservice stack to GKE
&lt;/h2&gt;

&lt;p&gt;Our cluster is ready, and we have Istio installed. Now, we can deploy our microservice stack to the cluster. First, we need to create Kubernetes manifests for our deployments and services and configurations for Istio. And once again, JHipster comes to the rescue. We can use the &lt;a href="https://www.jhipster.tech/jdl/deployments" rel="noopener noreferrer"&gt;JDL deployment&lt;/a&gt; configurations to generate Kubernetes setup for our stack with one command easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Kubernetes manifests
&lt;/h3&gt;

&lt;p&gt;Create a new JDL file, say &lt;code&gt;deployment.jdl&lt;/code&gt;, and add the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// will be created under 'kubernetes' folder&lt;/span&gt;
&lt;span class="nf"&gt;deployment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;deploymentType&lt;/span&gt; &lt;span class="n"&gt;kubernetes&lt;/span&gt;
  &lt;span class="n"&gt;appsFolders&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;dockerRepositoryName&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your-docker-repository-name&amp;gt;"&lt;/span&gt;
  &lt;span class="n"&gt;serviceDiscoveryType&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt;
  &lt;span class="n"&gt;istio&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;kubernetesServiceType&lt;/span&gt; &lt;span class="nc"&gt;Ingress&lt;/span&gt;
  &lt;span class="n"&gt;kubernetesNamespace&lt;/span&gt; &lt;span class="n"&gt;jhipster&lt;/span&gt;
  &lt;span class="n"&gt;ingressDomain&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;istio-ingress-gateway-external-ip&amp;gt;.nip.io"&lt;/span&gt;
  &lt;span class="n"&gt;ingressType&lt;/span&gt; &lt;span class="n"&gt;gke&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope it's self-explanatory. You can refer to the JDL deployment documentation for all the available options. We have enabled Istio and set the ingress domain to the Istio Ingress Gateway's external IP that we noted earlier. Now we need a DNS for our IP. For real use-cases, you should map a DNS for the IP using a service provided by your cloud vendor like Google Cloud DNS, but for testing and demo purposes, we can use a wildcard DNS service like &lt;a href="http://nip.io" rel="noopener noreferrer"&gt;&lt;strong&gt;nip.io&lt;/strong&gt;&lt;/a&gt; to resolve our IP. Just append &lt;code&gt;nip.io&lt;/code&gt; to our IP and use that as the &lt;code&gt;ingressDomain&lt;/code&gt;. Make sure to use a docker repo where you have push rights.&lt;/p&gt;

&lt;p&gt;Now run the following command from the root folder where you ran the previous &lt;code&gt;jhipster jdl&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jhipster jdl deployment.jdl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new folder, &lt;strong&gt;kubernetes&lt;/strong&gt;, with all the required Kubernetes manifests like deployments, services, Istio virtual services, gateways, and so on, for all the applications, databases, and monitoring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k0y303lyerwr2orp008.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k0y303lyerwr2orp008.png" alt="JHipster JDL deployment" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of the services will also have an Istio &lt;a href="https://istio.io/latest/docs/reference/config/networking/virtual-service/" rel="noopener noreferrer"&gt;virtual service&lt;/a&gt; and &lt;a href="https://istio.io/latest/docs/reference/config/networking/destination-rule/" rel="noopener noreferrer"&gt;destination rule&lt;/a&gt;. For example, the invoice service will have the following destination rule defining traffic policies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.istio.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DestinationRule&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice-destinationrule&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jhipster&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice&lt;/span&gt;
  &lt;span class="na"&gt;trafficPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;simple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RANDOM&lt;/span&gt;
    &lt;span class="na"&gt;connectionPool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;maxConnections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100ms&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;http1MaxPendingRequests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;http2MaxRequests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
        &lt;span class="na"&gt;maxRequestsPerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;outlierDetection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;consecutive5xxErrors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;baseEjectionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also includes the following virtual service that defines the route. You could also use virtual services to do traffic split between two versions of the same app, among other things.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.istio.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VirtualService&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice-virtualservice&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jhipster&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;invoice&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice&lt;/span&gt;
            &lt;span class="na"&gt;subset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1"&lt;/span&gt;
          &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="na"&gt;perTryTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway is defined for the store application as it is our GUI as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.istio.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-gateway&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jhipster&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-gateway&lt;/span&gt;
    &lt;span class="na"&gt;istio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingressgateway&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;istio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingressgateway&lt;/span&gt;
  &lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&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;http&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTP&lt;/span&gt;
      &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store.jhipster.34.76.233.160.nip.io&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&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;http2&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTP2&lt;/span&gt;
      &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store.jhipster.34.76.233.160.nip.io&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.istio.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VirtualService&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-gw-virtualservice&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jhipster&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-gw-virtualservice&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store.jhipster.34.76.233.160.nip.io&lt;/span&gt;
  &lt;span class="na"&gt;gateways&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store-gateway&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/invoice/&lt;/span&gt;
      &lt;span class="na"&gt;rewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/notification/&lt;/span&gt;
      &lt;span class="na"&gt;rewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notification&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/product/&lt;/span&gt;
      &lt;span class="na"&gt;rewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;product&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there are also many useful commands printed on the console that you can use to do the deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to GKE
&lt;/h3&gt;

&lt;p&gt;We are ready to deploy now. First, we need to build and push the images to the registry. We can use the handy &lt;a href="https://github.com/GoogleContainerTools/jib" rel="noopener noreferrer"&gt;Jib&lt;/a&gt; commands provided by JHipster. Navigate to each of the microservice folders and run the commands below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;store &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;yourDockerRepository/store
&lt;span class="nb"&gt;cd &lt;/span&gt;invoice &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;yourDockerRepository/invoice
&lt;span class="nb"&gt;cd &lt;/span&gt;notification &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;yourDockerRepository/notification
&lt;span class="nb"&gt;cd &lt;/span&gt;product &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew bootJar &lt;span class="nt"&gt;-Pprod&lt;/span&gt; jib &lt;span class="nt"&gt;-Djib&lt;/span&gt;.to.image&lt;span class="o"&gt;=&lt;/span&gt;yourDockerRepository/product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the images are pushed to the Docker registry, we can deploy the stack using the handy script provided by JHipster. Navigate to the &lt;code&gt;kubernetes&lt;/code&gt; folder created by JHipster and run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;kubernetes
./kubectl-apply.sh &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the deployments are done, we must wait for the pods to be in &lt;strong&gt;RUNNING&lt;/strong&gt; status. Useful links will be printed on the terminal; make a note of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkj4qoisgz0u5r1w1x3p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkj4qoisgz0u5r1w1x3p4.png" alt="GKE cluster with application pods" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now access the application at the given &lt;code&gt;http://store.jhipster.&amp;lt;istio-ingress-gateway-external-ip&amp;gt;.nip.io&lt;/code&gt; URI and log in with the default credentials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhhbdlkd5kubdfdr7vhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhhbdlkd5kubdfdr7vhp.png" alt="Store gateway application" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Currently, the JHipster OIDC setup does not work with Istio and there is an &lt;a href="https://github.com/jhipster/generator-jhipster/issues/17384" rel="noopener noreferrer"&gt;open issue&lt;/a&gt; in JHipster issue tracker for this. Alternative solutions would be to use an &lt;a href="https://istio.io/latest/blog/2021/better-external-authz/" rel="noopener noreferrer"&gt;external authorization server&lt;/a&gt; with something like &lt;a href="https://www.openpolicyagent.org/" rel="noopener noreferrer"&gt;Open Policy Agent&lt;/a&gt;. We will cover this in a later blog post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Monitoring and observability
&lt;/h3&gt;

&lt;p&gt;Since we deployed tools for observability, let's see what we have.&lt;/p&gt;

&lt;h4&gt;
  
  
  Grafana
&lt;/h4&gt;

&lt;p&gt;First up are Grafana and Prometheus for metrics and dashboards. Click the URI for Grafana from the previous deployment step. Click &lt;strong&gt;General&lt;/strong&gt; at the top left corner and click the &lt;strong&gt;istio&lt;/strong&gt; folder. You should see multiple preconfigured dashboards. You can monitor the performance of the workloads and the istio system itself here. You can also create your own dashboards if you like. Prometheus provides the data visualized on Grafana.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf9aod84wpoinggq9rwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf9aod84wpoinggq9rwx.png" alt="Grafana dashboard" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Kiali
&lt;/h4&gt;

&lt;p&gt;Kiali is a management console for Istio service mesh, and it provides a web interface for visualizing the network topology of your service mesh. You can use it to explore the network topology of your cluster and see the network traffic flowing through it. Click &lt;strong&gt;Graph&lt;/strong&gt; on the left side menu to see the network topology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsfjkx9zr1t2sef5xnrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsfjkx9zr1t2sef5xnrv.png" alt="Kiali dashboard" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Zipkin
&lt;/h4&gt;

&lt;p&gt;Zipkin is a distributed tracing solution for distributed systems. It is a tool for capturing distributed traces and providing a centralized view of the traces. This is essential for a microservice setup where a request could span multiple services, and debugging would require tracing them. Click &lt;strong&gt;RUN QUERY&lt;/strong&gt; on the home screen to fetch recent traces, and click &lt;strong&gt;SHOW&lt;/strong&gt; on one of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjwjiamrxz9xdotr3ps5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjwjiamrxz9xdotr3ps5.png" alt="Zipkin dashboard" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup the GCP cluster
&lt;/h3&gt;

&lt;p&gt;Once you are done with experiments, make sure to delete the cluster you created so that you don't end up with a big bill from Google. You can delete the cluster from the Google Cloud Console GUI or via the command line using the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters delete hello-hipster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learn more about Java Microservices, Istio, Kubernetes, and JHipster
&lt;/h2&gt;

&lt;p&gt;If you want to learn more about Kubernetes, OIDC, or using OIDC with Kubernetes, and security in general, check out these additional resources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2022/06/22/terraform-eks-microservices" rel="noopener noreferrer"&gt;How to Deploy Java Microservices on Amazon EKS Using Terraform and Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/11/08/k8s-api-server-oidc" rel="noopener noreferrer"&gt;How to Secure Your Kubernetes Cluster with OpenID Connect and RBAC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2022/05/05/kubernetes-microservices-azure" rel="noopener noreferrer"&gt;Kubernetes Microservices on Azure with Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/12/02/k8s-security-best-practices" rel="noopener noreferrer"&gt;How to Secure Your Kubernetes Clusters With Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/docs/concepts/oauth-openid/" rel="noopener noreferrer"&gt;OAuth 2.0 and OpenID Connect Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2021/10/08/secure-access-to-aws-eks" rel="noopener noreferrer"&gt;Secure Access to AWS EKS Clusters for Admins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find all the code from this example on &lt;a href="https://github.com/oktadev/okta-java-spring-k8s-istio-microservices-example" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you liked this tutorial, chances are you'll enjoy the others we publish. Please follow &lt;a href="https://twitter.com/oktadev" rel="noopener noreferrer"&gt;@oktadev on Twitter&lt;/a&gt; and &lt;a href="https://youtube.com/oktadev" rel="noopener noreferrer"&gt;subscribe to our YouTube channel&lt;/a&gt; to get notified when we publish new developer tutorials.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>jhipster</category>
      <category>java</category>
      <category>istio</category>
    </item>
  </channel>
</rss>
