<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Roland Huß</title><link>https://ro14nd.de/</link><description>Recent content on Roland Huß</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>© 2026 Roland Huß</copyright><lastBuildDate>Thu, 06 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ro14nd.de/index.xml" rel="self" type="application/rss+xml"/><item><title>Your API Is Not Your MCP Tool</title><link>https://ro14nd.de/mcp-tool-design-patterns/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/mcp-tool-design-patterns/</guid><description>&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/mcp-tool-design-patterns/og.png" alt="Retro-technical illustration of a 1950s telephone switchboard room. On the left, a large board with tangled cables filling every jack, labels buried under the mess. A switchboard operator in 1950s attire sits between two panels, one hand gesturing at the chaos, the other holding a cable toward a clean panel on the right. The clean panel has eight clearly labeled jacks with city names (London, Paris, New York, Berlin), green indicator lights glowing beside each one, cables hanging in neat parallel lines." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The most natural thing to do when building an MCP server is to mirror your existing API. You have a REST endpoint for creating users, so you make a &lt;code&gt;create_user&lt;/code&gt; tool. You have one for listing orders, so you make a &lt;code&gt;list_orders&lt;/code&gt; tool. Fifteen endpoints become fifteen tools, each one a thin wrapper that translates JSON-RPC to HTTP and passes the response back.&lt;/p&gt;
&lt;p&gt;It works. The agent can call the tools, the tools return data, and everything looks fine in your test harness. Then you connect it to a real conversation with twenty other MCP servers loaded, and the agent starts picking the wrong tools, hallucinating parameter values, and burning half its context window on tool definitions it never uses.&lt;/p&gt;
&lt;p&gt;The problem isn&amp;rsquo;t MCP. The problem is that APIs were designed for a fundamentally different consumer. A developer reads documentation, bookmarks the three endpoints they need, and writes code that calls them in the right order with the right parameters. An LLM sees every tool definition at once, every parameter description, every schema. It has to pick the right tool from that pile on every turn, and each definition costs tokens whether the tool gets called or not.&lt;/p&gt;
&lt;p&gt;Anthropic&amp;rsquo;s engineering team &lt;a href="https://www.anthropic.com/engineering/writing-tools-for-agents" target="_blank" rel="noreferrer"&gt;put it directly&lt;/a&gt;: &amp;ldquo;A common error we&amp;rsquo;ve observed is tools that merely wrap existing software functionality or API endpoints.&amp;rdquo; AWS &lt;a href="https://aws.amazon.com/blogs/machine-learning/mcp-tool-design-practical-approaches-and-tradeoffs/" target="_blank" rel="noreferrer"&gt;frames the same insight&lt;/a&gt; as two failure modes: &amp;ldquo;bloat&amp;rdquo; (tool definitions consuming context on every call whether used or not) and &amp;ldquo;confusion&amp;rdquo; (models picking wrong tools as context fills). Both companies arrived at the same conclusion from different directions. MCP tool design is not an API design problem. It&amp;rsquo;s a &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;context engineering&lt;/a&gt; problem.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How many tools is too many
 &lt;div id="how-many-tools-is-too-many" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-many-tools-is-too-many" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s no single number, but the data points tell a consistent story.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://arxiv.org/abs/2606.30317" target="_blank" rel="noreferrer"&gt;Tool-selection accuracy drops below 90%&lt;/a&gt; between 10 and 15 tools for Claude Haiku 4.5, and between 20 and 30 tools for Sonnet 4. Bigger models tolerate more tools, but none of them are immune. GitHub&amp;rsquo;s MCP server &lt;a href="https://www.zenml.io/llmops-database/building-and-scaling-a-production-mcp-server-for-developer-tooling" target="_blank" rel="noreferrer"&gt;grew to over 100 tools&lt;/a&gt; after a month of community contributions, degraded agent capabilities, and got cut back to about 40.&lt;/p&gt;
&lt;p&gt;The pattern is the same in every case: teams start by exposing everything, then discover that more tools means worse results. The reduction isn&amp;rsquo;t about removing functionality. It&amp;rsquo;s about consolidating related operations into higher-level tools that match what users actually ask for.&lt;/p&gt;
&lt;p&gt;Consider a project management API with separate endpoints for creating issues, assigning them, setting priority, adding labels, and linking to epics. That&amp;rsquo;s five tool definitions in the context window and a workflow that requires the agent to sequence five calls correctly. A single &lt;code&gt;manage_issue&lt;/code&gt; tool that accepts an action parameter and handles the orchestration internally gives the model one decision point instead of five. The agent&amp;rsquo;s job becomes &amp;ldquo;pick the right tool&amp;rdquo; rather than &amp;ldquo;pick the right five tools in the right order.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;AWS &lt;a href="https://aws.amazon.com/blogs/machine-learning/mcp-tool-design-practical-approaches-and-tradeoffs/" target="_blank" rel="noreferrer"&gt;calls this the progression&lt;/a&gt; from V1 (raw API passthrough) through V6 (agent-as-tool), and their conclusion is worth quoting: no single version wins across all dimensions. Raw passthrough is easiest to build but hardest for agents to use. Fully orchestrated tools are great for agents but expensive to maintain. The right level depends on how complex the workflow is and how often the agent needs fine-grained control over individual steps.&lt;/p&gt;
&lt;p&gt;The per-server tool count is only half the equation. In any real setup, you&amp;rsquo;re connecting multiple MCP servers at once, and their tool definitions stack. Every server you add pushes more definitions into the context window. Being selective about which servers you connect matters at least as much as being selective about which tools each server exposes.&lt;/p&gt;
&lt;p&gt;Claude Code&amp;rsquo;s configuration hierarchy helps with this on the client side. You define MCP servers at user, project, or directory scope, with project-level configs overriding what&amp;rsquo;s active for a specific codebase. A Kubernetes project gets your cluster tools, a documentation project gets your writing tools, and nothing bleeds across. &lt;a href="https://github.com/cc-deck/cc-setup" target="_blank" rel="noreferrer"&gt;cc-setup&lt;/a&gt; wraps this in a terminal UI for managing servers, permissions, and plugins per project from a central registry.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Parameters that help instead of confuse
 &lt;div id="parameters-that-help-instead-of-confuse" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#parameters-that-help-instead-of-confuse" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The number of parameters matters almost as much as the number of tools. AWS &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/mcp-strategies/" target="_blank" rel="noreferrer"&gt;recommends&lt;/a&gt; roughly eight or fewer per tool, with an important caveat: if bundling related operations requires more than eight parameters, prioritize the bundling over the parameter count. A single tool with twelve well-described parameters usually outperforms three tools with four parameters each, because the model makes one tool-selection decision instead of three.&lt;/p&gt;
&lt;p&gt;But parameter count is the easy part. The harder lesson is that parameter naming and typing should optimize for LLM comprehension, not for your internal data model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rename parameters to match how users think.&lt;/strong&gt; If your API takes &lt;code&gt;org_id&lt;/code&gt;, but users say &amp;ldquo;my company,&amp;rdquo; name the parameter &lt;code&gt;organization_name&lt;/code&gt; and resolve the ID internally. The model generates parameter values from the conversation context, and the conversation uses human language, not database identifiers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use semantic identifiers, not UUIDs.&lt;/strong&gt; This one surprised me. BoundaryML &lt;a href="https://www.boundaryml.com/blog/uuid-swap" target="_blank" rel="noreferrer"&gt;ran benchmarks&lt;/a&gt; comparing UUID-based tool parameters against integer-remapped alternatives. The result: 48.5 average errors per session with UUIDs versus 5.5 with integer remapping. Arbitrary alphanumeric strings give models nothing to reason about, and they hallucinate plausible-looking IDs that don&amp;rsquo;t exist. If your tool needs to reference an entity, resolve the UUID to a human-readable name or a simple index before the model ever sees it.&lt;/p&gt;
&lt;p&gt;Every MCP tool defines its parameters using &lt;a href="https://json-schema.org/" target="_blank" rel="noreferrer"&gt;JSON Schema&lt;/a&gt; in the &lt;code&gt;inputSchema&lt;/code&gt; field. The client sends that schema to the LLM as part of the tool definition on every invocation, so the model sees the full parameter structure, types, descriptions, and constraints before deciding how to call the tool. With the &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" target="_blank" rel="noreferrer"&gt;2026-07-28 spec&lt;/a&gt;, &lt;code&gt;inputSchema&lt;/code&gt; supports full JSON Schema 2020-12, including &lt;code&gt;oneOf&lt;/code&gt;, &lt;code&gt;anyOf&lt;/code&gt;, conditionals, and &lt;code&gt;$ref&lt;/code&gt;, so you can express complex parameter relationships directly in the schema.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use enums over booleans.&lt;/strong&gt; JSON Schema&amp;rsquo;s &lt;code&gt;enum&lt;/code&gt; keyword lets you list the exact valid values inline, and the model uses those values directly when generating its tool call. A parameter like &lt;code&gt;include_archived: true&lt;/code&gt; forces the model to infer what true means in this specific context. &lt;code&gt;filter: &amp;quot;active&amp;quot;&lt;/code&gt; or &lt;code&gt;filter: &amp;quot;all&amp;quot;&lt;/code&gt; gives the model unambiguous options to pick from. AWS Prescriptive Guidance &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/mcp-strategies/mcp-tool-strategy-definitions.html" target="_blank" rel="noreferrer"&gt;recommends enums&lt;/a&gt; for any finite set of values: &amp;ldquo;use enums for parameter values to make it easy for the LLM to select the correct options.&amp;rdquo; Booleans optimize for code execution, enums optimize for LLM reasoning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add a response format parameter.&lt;/strong&gt; Anthropic &lt;a href="https://www.anthropic.com/engineering/writing-tools-for-agents" target="_blank" rel="noreferrer"&gt;found&lt;/a&gt; that adding a &lt;code&gt;response_format&lt;/code&gt; parameter with options like &amp;ldquo;concise&amp;rdquo; and &amp;ldquo;detailed&amp;rdquo; reduced token usage by roughly two-thirds for their Slack integration (72 tokens for concise versus 206 for detailed). The model doesn&amp;rsquo;t always need the full response, and giving it a way to say so saves context for the rest of the conversation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool naming has measurable effects.&lt;/strong&gt; In the &lt;a href="https://www.anthropic.com/engineering/writing-tools-for-agents" target="_blank" rel="noreferrer"&gt;same post&lt;/a&gt;, Anthropic tested prefix-based (&lt;code&gt;slack_list_channels&lt;/code&gt;) versus suffix-based (&lt;code&gt;list_channels_slack&lt;/code&gt;) namespacing and found measurable differences on their tool-use evaluations. The effects varied by model, which means the right naming convention depends on which LLM your users run. If you&amp;rsquo;re building a public MCP server, test both patterns against the models you care about. Assumptions about naming are surprisingly unreliable.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Context hygiene for tool definitions
 &lt;div id="context-hygiene-for-tool-definitions" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#context-hygiene-for-tool-definitions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Each tool definition &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2812" target="_blank" rel="noreferrer"&gt;costs somewhere between 100 and 1,000 tokens&lt;/a&gt;, depending on schema complexity. AWS Prescriptive Guidance &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/mcp-strategies/" target="_blank" rel="noreferrer"&gt;puts it at&lt;/a&gt; 5,000 to 10,000 tokens for just 20 tools. With 50 tools loaded, that&amp;rsquo;s 20,000 to 25,000 tokens consumed before the first message in the conversation. On a 32K context window, that&amp;rsquo;s most of the available space. Even on larger windows, those tokens compete with conversation history, file contents, and everything else the agent needs to reason about.&lt;/p&gt;
&lt;p&gt;The MCP specification now includes primitives for managing this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deterministic tool ordering&lt;/strong&gt; is the simplest win. The &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" target="_blank" rel="noreferrer"&gt;2026-07-28 spec&lt;/a&gt; recommends that servers return tools from &lt;code&gt;tools/list&lt;/code&gt; in a deterministic order. This matters because LLM providers cache the prompt prefix. If tool definitions appear in the same order across requests, the cached prefix covers them and the provider skips re-processing. Randomized ordering breaks the cache on every call.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CacheableResult&lt;/strong&gt; lets servers attach &lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; fields to list and read results, so clients know how long they can cache a response without re-polling. If your tool list doesn&amp;rsquo;t change during a session, setting a long TTL means the client asks once and remembers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Progressive tool loading&lt;/strong&gt; goes further. Instead of registering full tool definitions at startup, register only metadata (name and one-line summary) and load the full schema on demand when the agent actually wants to call a tool. Claude Code&amp;rsquo;s &lt;a href="https://www.anthropic.com/engineering/advanced-tool-use" target="_blank" rel="noreferrer"&gt;ToolSearch&lt;/a&gt; mechanism works this way, and the token savings are substantial. The tradeoff is that the agent has less information for initial tool-selection, so the summaries need to be precise enough for the model to pick correctly from the short descriptions alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/subscriptions" target="_blank" rel="noreferrer"&gt;subscriptions/listen&lt;/a&gt;&lt;/strong&gt; is the new unified mechanism for change notifications. If your server adds or removes tools at runtime, it can push change notifications through a single long-lived stream rather than requiring clients to poll &lt;code&gt;tools/list&lt;/code&gt; on every turn.&lt;/p&gt;
&lt;p&gt;These are infrastructure-level optimizations, and they matter most for MCP servers with large tool sets or deployments where multiple servers connect to the same client. But even a small server benefits from deterministic ordering and accurate tool descriptions, because every wasted token is a token the agent could have spent on reasoning.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;What this means in practice
 &lt;div id="what-this-means-in-practice" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-this-means-in-practice" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The central tension in MCP tool design is that everything you expose costs context, and context is the scarce resource that determines whether the agent works well or poorly. Every tool, every parameter, every line in a description competes for the same budget.&lt;/p&gt;
&lt;p&gt;The teams that got this right didn&amp;rsquo;t remove functionality. They redesigned the interface between the API and the agent: consolidating fine-grained operations into higher-level tools that match user intent, renaming parameters to match how people talk about the domain, replacing UUIDs with meaningful identifiers and booleans with enums.&lt;/p&gt;
&lt;p&gt;None of this requires changes to the underlying API. The API serves its original consumers just fine. The MCP server is a translation layer, and the quality of that translation determines whether the agent is helpful or frustrating. Treating the API surface as the tool surface is the mistake. Designing for what the model sees, not for what the code does, is the fix.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" target="_blank" rel="noreferrer"&gt;2026-07-28 MCP specification&lt;/a&gt; also brings a &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle" target="_blank" rel="noreferrer"&gt;stateless protocol core&lt;/a&gt;, &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/#multi-round-trip-requests-mrtr" target="_blank" rel="noreferrer"&gt;Multi Round-Trip Requests&lt;/a&gt; for tools that need user input mid-execution, and &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/#deprecations" target="_blank" rel="noreferrer"&gt;deprecations&lt;/a&gt; of Roots, Sampling, and Logging.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>One Stray Leads the Whole Flock Astray</title><link>https://ro14nd.de/one-stray-leads-the-whole-flock-astray/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/one-stray-leads-the-whole-flock-astray/</guid><description>&lt;p&gt;The &lt;a href="https://ro14nd.de/the-sheep-that-forgot-the-way-home/" &gt;previous post&lt;/a&gt; in &lt;a href="https://ro14nd.de/the-flock/" &gt;The Flock&lt;/a&gt; series covered what happens when agents forget mid-run and confidently fill the gaps. This one covers a different failure mode: what happens when one agent&amp;rsquo;s leftover context converts the next agent into something it was never supposed to be.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The flock grazes in separate paddocks for a reason. Put the bell sheep and the border collie in the same field and by afternoon the collie has stopped herding and started grazing. It didn&amp;rsquo;t forget its job. It absorbed the bell sheep&amp;rsquo;s confidence and decided that grazing was the job all along.&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/one-stray-leads-the-whole-flock-astray/og.png" alt="Watercolor illustration of a forking dirt path in a green meadow. On the left fork, three sheep walk confidently toward a rickety old barn. On the right fork, the main flock heads toward a warm stone barn with a glowing doorway. A low stone wall with a gap separates the two paths. Wildflowers line the wrong path on the left." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Not confusion, conversion
 &lt;div id="not-confusion-conversion" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#not-confusion-conversion" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Context contamination between agents is a different beast than context fatigue within a single session. The &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;101 post&lt;/a&gt; covered the gradual fog: responses getting less precise as the conversation grows, state drifting after compaction. That&amp;rsquo;s a single agent slowly losing its footing.&lt;/p&gt;
&lt;p&gt;In multi-agent setups, the failure is sharper. When multiple agents run in the same foreground context, the second agent doesn&amp;rsquo;t just inherit stale data from the first. It inherits the first agent&amp;rsquo;s &lt;em&gt;identity&lt;/em&gt;. An assessment agent that follows a creation agent in the same context will absorb the creator&amp;rsquo;s assumptions and biases. In the worst cases it adopts the creator&amp;rsquo;s role entirely, deciding that its job is to generate ideas rather than evaluate them, not confused but converted.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Context bleeds across items, not just across roles
 &lt;div id="context-bleeds-across-items-not-just-across-roles" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#context-bleeds-across-items-not-just-across-roles" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The second pattern is subtler. A feasibility agent reviewing multiple items in sequence can have context from one item bleeding into the assessment of the next. Scores that should be independent aren&amp;rsquo;t. The fix is the same: one agent per item, each in complete isolation.&lt;/p&gt;
&lt;p&gt;This kind of bleed only surfaces at scale. Small test batches of five items look fine. At fifty, the drift becomes visible: items assessed later in the sequence show score patterns that correlate with properties of earlier items, not their own content.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://link.springer.com/article/10.1007/s42001-025-00435-2" target="_blank" rel="noreferrer"&gt;Lou and Sun (2026)&lt;/a&gt; found that anchoring bias in LLMs is systematic and affects stronger models more consistently, not less. Four different debiasing strategies (chain-of-thought, reflection, ignoring anchor hints, explicit instructions) all failed to eliminate it. Prompt engineering can&amp;rsquo;t fix a bias baked into how the model processes sequences. Architecture can.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The author can&amp;rsquo;t review its own work
 &lt;div id="the-author-cant-review-its-own-work" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-author-cant-review-its-own-work" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Code review culture already solved this problem for humans. You don&amp;rsquo;t review your own pull requests because you&amp;rsquo;re anchored to the decisions that shaped the code. You see the logic, not the gaps. As it turns out, the same principle applies to agents.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://arxiv.org/abs/2603.12123" target="_blank" rel="noreferrer"&gt;CCR paper&lt;/a&gt; (Cross-Context Review) tested this directly. Fresh-session review achieved an F1 score of 28.6% versus 24.6% for same-session self-review. The critical control was reviewing twice in the same session: that produced more findings but &lt;em&gt;worse&lt;/em&gt; precision (21.0% vs 25.8%), more noise rather than more signal. The production context itself anchors the model, and repeating review in the same context only makes things worse.&lt;/p&gt;
&lt;p&gt;CCR also achieves what the authors call &amp;ldquo;natural anonymization.&amp;rdquo; The reviewing session can&amp;rsquo;t know the artifact originated from the same model. This mirrors findings from &lt;a href="https://arxiv.org/abs/2510.07517" target="_blank" rel="noreferrer"&gt;Choi et al. (ACL 2026)&lt;/a&gt; showing that sycophantic bias drops 96% when authorship is hidden. The model is more honest when it doesn&amp;rsquo;t know it&amp;rsquo;s reviewing its own work.&lt;/p&gt;
&lt;p&gt;This is why adversarial review works as a pipeline pattern. An agent creates or revises a document in one context. A completely separate agent, running in a clean context, assesses the result. The reviewer doesn&amp;rsquo;t know that a revision happened, doesn&amp;rsquo;t have access to the creation reasoning, and evaluates the output on its merits alone.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The workbench metaphor
 &lt;div id="the-workbench-metaphor" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-workbench-metaphor" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Think of context like a workbench in a shared workshop. If the previous worker left their tools, materials, and half-finished pieces scattered around, the next worker will unconsciously start incorporating those into whatever they&amp;rsquo;re building. Clearing the bench between workers isn&amp;rsquo;t overhead, it&amp;rsquo;s a prerequisite for getting independent results.&lt;/p&gt;
&lt;p&gt;Multi-agent frameworks take different positions on when to clear the bench. Claude Code subagents enforce isolation by default: each subagent runs in a fresh conversation with its own system prompt and tool definitions, but not the parent&amp;rsquo;s conversation history. The OpenAI Agents SDK takes the opposite approach, sharing full conversation history by default during handoffs, though it provides an &lt;code&gt;input_filter&lt;/code&gt; mechanism for opt-in isolation. These are different architectural philosophies, and the research consistently favors isolation.&lt;/p&gt;

&lt;h2 class="relative group"&gt;From discipline to architecture
 &lt;div id="from-discipline-to-architecture" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#from-discipline-to-architecture" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Context contamination can&amp;rsquo;t be solved with better prompts. Anchoring bias resists all tested prompt-based mitigations, sycophancy persists as long as authorship is visible, and score drift accumulates as long as items share a context.&lt;/p&gt;
&lt;p&gt;The fix is structural: separate creation from assessment, isolate items from each other, clear the context between agents, and make the reviewing agent blind to the creation process. These aren&amp;rsquo;t optimizations you add after the pipeline works. They&amp;rsquo;re prerequisites for the pipeline producing reliable results.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ro14nd.de/the-flock/" &gt;The Flock&lt;/a&gt; so far: &lt;a href="https://ro14nd.de/the-sheep-that-picked-the-lock/" &gt;deterministic guardrails&lt;/a&gt; around agent creativity, &lt;a href="https://ro14nd.de/the-sheep-that-forgot-the-way-home/" &gt;state on disk&lt;/a&gt; because memory can&amp;rsquo;t be trusted, and now separate paddocks, because one stray&amp;rsquo;s confidence is enough to convert the whole flock.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;&lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>When Your AI Agent Treats Scripts Like Suggestions</title><link>https://ro14nd.de/agent-scripts-suggestions/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/agent-scripts-suggestions/</guid><description>&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/agent-scripts-suggestions/og.png" alt="Watercolor illustration showing three concentric fence rings around a farmer&amp;#39;s vegetable garden, viewed from a hilltop. The outermost ring is a flimsy rope line with a sign reading &amp;#39;Please stay out,&amp;#39; sheep stepping over it. The middle ring is a proper wooden fence, sheep milling outside. The innermost ring is a solid stone wall with a locked iron gate, the farmer tending vegetables safely inside. One sheep wearing round spectacles studies the three rings." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The init command for my Claude Code plugin needed a few things to happen in order: run a setup script, ask the user some configuration questions, apply the results. Not complicated. The skill file spelled out each step with code blocks and paths, and the agent understood all of it. Then it went its own way.&lt;/p&gt;
&lt;p&gt;Sometimes it called the CLI directly, skipping a wrapper script that checks prerequisites and preserves configuration state. Sometimes it finished step one and stopped, summarizing results without continuing. Sometimes it searched for a binary called &lt;code&gt;speckit&lt;/code&gt; when the tool is called &lt;code&gt;specify&lt;/code&gt;. The pattern was always the same: the agent extracted the intent (&amp;ldquo;initialize the project&amp;rdquo;) and chased it through whatever path looked reasonable, treating each instruction as a suggestion to optimize away.&lt;/p&gt;
&lt;p&gt;This is &lt;a href="https://ro14nd.de/cc-skill-patterns/" &gt;how skills work&lt;/a&gt;. The model reads instructions, considers them, and makes its own call. If the goal is &amp;ldquo;initialize the project,&amp;rdquo; why bother with a wrapper script when the CLI is right there? The answer is that the wrapper checks prerequisites, preserves configuration that the CLI would overwrite, and returns structured status codes that downstream steps depend on. But the agent doesn&amp;rsquo;t know what it doesn&amp;rsquo;t know, and a skill file can&amp;rsquo;t convey &amp;ldquo;this matters more than you think.&amp;rdquo;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Path hallucination and the cost of exploration
 &lt;div id="path-hallucination-and-the-cost-of-exploration" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#path-hallucination-and-the-cost-of-exploration" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Even after tightening the instructions, a subtler problem emerged. The agent would try to run the correct script but construct the wrong path.&lt;/p&gt;
&lt;p&gt;Claude Code plugins live in a cache directory whose location depends on platform, user configuration, and plugin version. A typical path looks like &lt;code&gt;~/.claude/plugins/cache/&amp;lt;plugin&amp;gt;/&amp;lt;version&amp;gt;/scripts/init.sh&lt;/code&gt;. Not something you hardcode in a markdown file and expect to work everywhere.&lt;/p&gt;
&lt;p&gt;So the skill file said &amp;ldquo;find the script relative to the plugin root.&amp;rdquo; The agent interpreted this by running &lt;code&gt;find&lt;/code&gt; or &lt;code&gt;ls&lt;/code&gt; to locate the script. Sometimes it found the right file, sometimes a similarly named file in a different directory, and sometimes it invented a plausible-looking path that didn&amp;rsquo;t exist at all.&lt;/p&gt;
&lt;p&gt;Three rounds of fixes followed, each adding more &amp;ldquo;DO NOT&amp;rdquo; clauses, more explicit constraints, more guardrails against filesystem exploration. None of them held, because the underlying behavior didn&amp;rsquo;t change: the agent would read the constraints, weigh them against its own judgment, and go exploring anyway.&lt;/p&gt;
&lt;p&gt;Beyond reliability, there was a cost problem that anyone working with AI-assisted development will recognize. Each exploration attempt burned tokens on &lt;code&gt;find&lt;/code&gt; commands and filesystem traversals. The agent would reason about results, try a path, fail, reason again, try another. A step that should take a single Bash call ballooned into five or six, each carrying its own reasoning overhead. The user sits there watching the agent think, waiting, losing focus. When the agent spends thirty seconds figuring out something it should already know, the user&amp;rsquo;s mental context starts to evaporate. In AI-assisted development, the biggest threat to productivity isn&amp;rsquo;t complexity. It&amp;rsquo;s latency.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The compliance hierarchy
 &lt;div id="the-compliance-hierarchy" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-compliance-hierarchy" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;After three failed attempts at fixing the documentation, the pattern became clear. There&amp;rsquo;s a hierarchy of how seriously the agent treats different types of context.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;System reminders and hook context&lt;/strong&gt; arrive as ground truth. The agent rarely ignores them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct prompt content&lt;/strong&gt; (&lt;a href="https://ro14nd.de/what-goes-in-agents-md/" &gt;AGENTS.md&lt;/a&gt;, CLAUDE.md, command files) gets moderate compliance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Referenced instructions&lt;/strong&gt; (skill files, tool descriptions) get treated more like advice to interpret.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As it turns out, every one of my failed attempts had the same root cause: critical logic lived at level three. The agent would read the skill, understand the intent, and improvise instead of following the literal steps. Stronger wording didn&amp;rsquo;t help because the model treats skill content as guidance, not as commands.&lt;/p&gt;
&lt;p&gt;This hierarchy has solid academic backing. &lt;a href="https://arxiv.org/abs/2404.13208" target="_blank" rel="noreferrer"&gt;Wallace et al.&lt;/a&gt; formalized it as &amp;ldquo;The Instruction Hierarchy&amp;rdquo; at ICLR 2025, showing that LLMs trained with explicit privilege levels (system &amp;gt; user &amp;gt; tool content) became significantly more reliable. Anthropic&amp;rsquo;s own &lt;a href="https://www.anthropic.com/constitution" target="_blank" rel="noreferrer"&gt;principal hierarchy&lt;/a&gt; encodes a similar ordering: Anthropic &amp;gt; operator &amp;gt; user, with environment context (tool outputs, loaded files) given the least inherent trust. In Claude Code specifically, you can see it in action: CLAUDE.md and skill content arrives wrapped in &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt; tags with the disclaimer &amp;ldquo;this context may or may not be relevant to your tasks,&amp;rdquo; literally downgrading it to advisory status.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://agents.md/" target="_blank" rel="noreferrer"&gt;AGENTS.md&lt;/a&gt; spec is moving in the same direction. The &lt;a href="https://github.com/agentsmd/agents.md/issues/135" target="_blank" rel="noreferrer"&gt;v1.1 proposal&lt;/a&gt; formalizes a 5-level priority hierarchy (LLM system prompt &amp;gt; agent system prompt &amp;gt; user prompt &amp;gt; local AGENTS.md &amp;gt; ancestor AGENTS.md) and explicitly acknowledges that enforcement is &amp;ldquo;often achieved probabilistically through prompting.&amp;rdquo; That&amp;rsquo;s the core tension: context files can express intent, but they can&amp;rsquo;t guarantee compliance. Deterministic enforcement has to come from somewhere else.&lt;/p&gt;
&lt;p&gt;That somewhere else is hooks.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Hooks and structured outputs
 &lt;div id="hooks-and-structured-outputs" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hooks-and-structured-outputs" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The examples here use Claude Code&amp;rsquo;s hook system (&lt;code&gt;UserPromptSubmit&lt;/code&gt;, &lt;code&gt;PreToolUse&lt;/code&gt;), but the pattern is not Claude Code specific. Cursor has rules and &lt;code&gt;.cursor/tools&lt;/code&gt;, Windsurf has flows, Cline has custom instructions with tool approval, and most agent harnesses offer some way to inject context at the system level or gate tool calls before they execute. The specific API differs, the architectural principle is the same: move enforcement out of instructions and into the harness.&lt;/p&gt;
&lt;p&gt;The final architecture gives the agent nothing to interpret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Path injection.&lt;/strong&gt; A &lt;code&gt;UserPromptSubmit&lt;/code&gt; hook (a Python script) runs before the agent sees the prompt. It resolves its own filesystem location using &lt;code&gt;Path(__file__).parent.parent.parent&lt;/code&gt;, constructs absolute paths to every script the agent might need, and injects them as structured XML in the system context:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;init-command&amp;gt;&lt;/span&gt;/Users/me/.claude/plugins/cache/.../scripts/init.sh&lt;span style="color:#f92672"&gt;&amp;lt;/init-command&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;How this lands in the model&amp;rsquo;s context matters. In Claude Code, hook output goes into a &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt; block that becomes part of the system prompt, not the user message. The model sees it as platform-level context, the same tier as its own operating instructions. That&amp;rsquo;s why hook-injected content gets near-perfect compliance while identical text in a skill file gets treated as a suggestion. Other agent harnesses achieve the same effect differently: Cursor&amp;rsquo;s rules inject into the system prompt directly, and any framework built on the Anthropic or OpenAI API can prepend to the system message before the conversation starts.&lt;/p&gt;
&lt;p&gt;Path resolution happens in deterministic Python code, not in probabilistic language model reasoning. The hook fires only on the plugin&amp;rsquo;s slash commands and captures session state at hook time. No ambiguity left for the agent to resolve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage enforcement.&lt;/strong&gt; A &lt;code&gt;PreToolUse&lt;/code&gt; hook fires before every tool call the agent makes. It checks a state file on disk that tracks where the workflow currently is, and if the agent tries to skip ahead, the hook blocks the call and tells it exactly which steps it needs to complete first. The skill file still contains the workflow instructions (the agent needs them for the judgment calls within each step), but the hook makes compliance non-optional. Instead of hoping the agent follows a multi-step procedure, the hook gates every action against the expected sequence.&lt;/p&gt;
&lt;p&gt;These two hooks work together: one provides information (resolved paths, session state), the other enforces behavior (stage ordering, shortcut prevention). Together, they move both context and compliance from the advisory tier to system-level enforcement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structured outputs.&lt;/strong&gt; The init script returns machine-parseable status codes: &lt;code&gt;READY&lt;/code&gt;, &lt;code&gt;NEED_INSTALL&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;, &lt;code&gt;RESTART_REQUIRED&lt;/code&gt;. The skill uses conditional logic: &amp;ldquo;If output contains NEED_INSTALL, show output, STOP.&amp;rdquo; This removes the last opportunity for improvisation. No ambiguous output to reason about, just a keyword to match and a branch to follow.&lt;/p&gt;
&lt;p&gt;Together, these mechanisms reduce the agent&amp;rsquo;s job to: read a path from context, execute it, parse a keyword, follow the branch. Each step is concrete and verifiable. Nothing left to infer, guess, or optimize away.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Architecture over instructions
 &lt;div id="architecture-over-instructions" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#architecture-over-instructions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;An AI coding agent is a confident but context-limited collaborator. It will try to help and it will apply its judgment. And that judgment will sometimes lead it to skip the step that matters most, because that step looks redundant from the outside.&lt;/p&gt;
&lt;p&gt;Better instructions don&amp;rsquo;t fix this. More guardrails, more constraints, more anti-exploration language: none of it sticks, because instructions are interpreted, and interpretation introduces variance.&lt;/p&gt;
&lt;p&gt;Architecture fixes it. Remove opportunities for interpretation by resolving ambiguity before the agent encounters it. Hooks resolve paths and enforce stage ordering, structured outputs constrain branching. Each mechanism removes a category of improvisation. What remains is an agent executing a well-defined protocol, which is what a plugin init command should be.&lt;/p&gt;
&lt;p&gt;Every exploratory call the agent makes is also tokens burned and seconds lost. The difference between &amp;ldquo;one Bash call with a pre-resolved path&amp;rdquo; and &amp;ldquo;six exploratory calls to figure out where the script lives&amp;rdquo; is the difference between a tool that feels responsive and one that feels like it&amp;rsquo;s fighting you.&lt;/p&gt;
&lt;p&gt;Trust instructions for what they&amp;rsquo;re good at (communicating intent to humans reading the code) and use structural mechanisms for what needs to be reliable.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;&lt;a href="https://aiattribution.github.io/statements/AIA-Ph-Se-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA Ph Se Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>The Sheep That Forgot the Way Home</title><link>https://ro14nd.de/the-sheep-that-forgot-the-way-home/</link><pubDate>Thu, 09 Jul 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/the-sheep-that-forgot-the-way-home/</guid><description>&lt;p&gt;The &lt;a href="https://ro14nd.de/the-sheep-that-picked-the-lock/" &gt;previous post in The Flock series&lt;/a&gt; covered what happens when agents get creative where they shouldn&amp;rsquo;t. This one covers a more fundamental problem: what happens when agents forget what they were doing, and then confidently make something up.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The bell sheep always knows the way home. Except on days when the fog rolls in and the familiar landmarks disappear. On those days, she confidently leads the flock to entirely the wrong barn, and everyone follows because she&amp;rsquo;s never been wrong before.&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/the-sheep-that-forgot-the-way-home/og.png" alt="Watercolor illustration of a sheep standing alone in a foggy meadow at dusk, looking over its shoulder. Its hoofprints fade into the mist behind it. Scattered papers blow away in the wind. A warm-lit barn glows faintly in the far background." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;The fog rolls in
 &lt;div id="the-fog-rolls-in" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-fog-rolls-in" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;101 post&lt;/a&gt; recommended &lt;code&gt;/clear&lt;/code&gt; when context gets stale: start a fresh session, let the agent re-read the project from scratch. Good advice when you&amp;rsquo;re sitting at your desk watching the conversation drift. But &lt;code&gt;/clear&lt;/code&gt; is a human intervention. It assumes someone is paying attention.&lt;/p&gt;
&lt;p&gt;When agents run in CI, nobody is watching. The context window fills up on its own schedule, and the runtime compresses the conversation to make room for more work. This is called compaction, and it happens automatically after long-running sessions. Claude Code &lt;a href="https://www.morphllm.com/compaction-vs-summarization" target="_blank" rel="noreferrer"&gt;triggers auto-compaction&lt;/a&gt; when context usage reaches roughly 95% of the window. You don&amp;rsquo;t choose when it fires. You don&amp;rsquo;t control what it keeps. And the part that makes it dangerous: compaction doesn&amp;rsquo;t just drop information. It invents replacements.&lt;/p&gt;
&lt;p&gt;This is well-documented. An LLM reads the full conversation and produces a structured summary that replaces the history. The summarization is lossy: exact file paths, line numbers, and error messages &lt;a href="https://www.morphllm.com/context-rot" target="_blank" rel="noreferrer"&gt;get paraphrased&lt;/a&gt;, not preserved verbatim. &lt;a href="https://medium.com/the-ai-forum/automatic-context-compression-in-llm-agents-why-agents-need-to-forget-and-how-to-help-them-do-it-43bff14c341d" target="_blank" rel="noreferrer"&gt;Research from 2025&lt;/a&gt; found that nearly 65% of enterprise AI failures were attributed to context drift or memory loss during multi-step reasoning, not raw context exhaustion. Compaction strategies have improved since then (Anthropic&amp;rsquo;s &lt;a href="https://platform.claude.com/docs/en/build-with-claude/compaction" target="_blank" rel="noreferrer"&gt;server-side compaction API&lt;/a&gt;, Factory&amp;rsquo;s &lt;a href="https://www.factory.ai/" target="_blank" rel="noreferrer"&gt;anchored iterative summarization&lt;/a&gt;), but the fundamental limitation remains: any lossy compression of a conversation will lose something, and the model won&amp;rsquo;t tell you what. The distinction matters: context &lt;em&gt;drift&lt;/em&gt; is different from context &lt;em&gt;exhaustion&lt;/em&gt;. The model&amp;rsquo;s reasoning diverges from the original task because compressed summaries introduce subtle reframing that shifts how the agent interprets its own instructions. Same agent, same prompt, different behavior, and nothing in the logs to tell you something changed.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What gets invented
 &lt;div id="what-gets-invented" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-gets-invented" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The unsettling part isn&amp;rsquo;t what the agent forgets. It&amp;rsquo;s how confidently it fills the gaps. This is where it stops being a technical nuisance and starts feeling wrong.&lt;/p&gt;
&lt;p&gt;In long coding sessions, I&amp;rsquo;ve watched agents reference file paths that were refactored an hour earlier. Not as mistakes they catch and correct, but as confident assertions they build on. The agent writes an import statement for a module that was renamed three tool calls ago, and then writes tests against the old API, and then reports success because the tests it wrote match the code it wrote, both of which reference something that no longer exists. Every step is internally consistent. The chain of reasoning is sound. The foundation is wrong.&lt;/p&gt;
&lt;p&gt;In batch processing, the pattern is worse because there&amp;rsquo;s no human to notice the drift. A CI agent processing 50 items might lose track of which item it&amp;rsquo;s currently working on after compaction compresses the early items into a summary. The agent doesn&amp;rsquo;t stop and say &amp;ldquo;I&amp;rsquo;m not sure which item I was processing.&amp;rdquo; It picks one that fits the pattern of what it&amp;rsquo;s been doing and continues. If the summary says &amp;ldquo;processed items 1-30, currently on batch 2,&amp;rdquo; the agent might confidently start on item 31 when it was actually in the middle of item 28. Three items get skipped, nothing crashes, and the final report shows 50 items processed.&lt;/p&gt;
&lt;p&gt;This is one reason to keep the batch loop itself in a deterministic script rather than letting the agent manage iteration. A shell script or Python wrapper that iterates over items and invokes the agent once per item avoids the compaction problem entirely: each invocation starts with a fresh context, the script tracks progress, and the agent never needs to remember where it left off. The &lt;a href="https://ro14nd.de/the-sheep-that-picked-the-lock/" &gt;first post in this series&lt;/a&gt; called this &amp;ldquo;deterministic guardrails&amp;rdquo; for agent creativity. For batch workloads, it&amp;rsquo;s not just a guardrail, it&amp;rsquo;s the architecture.&lt;/p&gt;
&lt;p&gt;Run IDs are another casualty. A pipeline generates a unique ID at startup (say, &lt;code&gt;run-20260514-1430&lt;/code&gt;) and uses it to namespace all output files. After compaction, the agent needs the run ID to write the final report. Instead of admitting it doesn&amp;rsquo;t remember, it generates a plausible-looking new one based on the current time: &lt;code&gt;run-20260514-1530&lt;/code&gt;. The report lands in the wrong directory while all the intermediate files sit in the original one. The dashboard shows an empty run and a report with no supporting data. Nothing crashes. The failure mode is a clean-looking report that&amp;rsquo;s quietly orphaned from everything it summarizes.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The error list that emptied itself
 &lt;div id="the-error-list-that-emptied-itself" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-error-list-that-emptied-itself" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Some compaction bugs don&amp;rsquo;t loop or crash. They just quietly drop information that matters.&lt;/p&gt;
&lt;p&gt;An agent processing a batch of documents accumulates a list of items that fail validation: &amp;ldquo;items 7, 19, and 23 need manual review.&amp;rdquo; The list lives in the conversation as a running tally, updated after each item. After compaction, the summary says &amp;ldquo;some items needed manual review&amp;rdquo; but the specific IDs are gone. The agent continues processing, finishes the batch, and writes the final report. The &amp;ldquo;items requiring attention&amp;rdquo; section is empty, not because everything passed, but because the agent lost the list and had nothing to put there. Three items that were explicitly flagged for human review silently slip through without it.&lt;/p&gt;
&lt;p&gt;The fix is the same for every piece of mutable state: write it to a file the moment it changes, and re-read the file whenever you need it. An &lt;code&gt;errors.json&lt;/code&gt; that gets appended to after each validation failure survives compaction because the disk doesn&amp;rsquo;t forget. The agent reads the file when writing the report and finds all three flagged items, regardless of what the compressed conversation summary remembers.&lt;/p&gt;
&lt;p&gt;This pattern applies to anything the agent accumulates during a run: error lists, progress markers, feature flags, intermediate scores. If the value changes during the run and the agent needs it later, it has to be on disk.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The post-compaction hook
 &lt;div id="the-post-compaction-hook" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-post-compaction-hook" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Claude Code provides a post-compaction hook that fires after the runtime compresses the conversation. This is your recovery point. The hook can re-inject critical state that the agent needs to continue: which item it&amp;rsquo;s working on, what step it&amp;rsquo;s in, what the run parameters are.&lt;/p&gt;
&lt;p&gt;The hook doesn&amp;rsquo;t prevent information loss. It gives you a structured way to restore the minimum state needed for the agent to continue correctly. Think of it as the equivalent of saving your game before the fog rolls in: when visibility returns, you load from the save point instead of guessing where you were.&lt;/p&gt;
&lt;p&gt;A typical post-compaction hook reads a state file from disk and prints it as a system message:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# post-compaction hook&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -f &lt;span style="color:#e6db74"&gt;&amp;#34;.agent-state/current-run.json&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;State restored after compaction:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cat .agent-state/current-run.json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The agent receives this as the first message after compaction and anchors to it. Without the hook, it anchors to whatever the compressed summary says, which might be close enough to feel right but different enough to produce wrong results.&lt;/p&gt;
&lt;p&gt;One user &lt;a href="https://github.com/anthropics/claude-code/issues/34556" target="_blank" rel="noreferrer"&gt;documented 59 compactions in 26 days&lt;/a&gt;, averaging 2.3 per day. That&amp;rsquo;s not an edge case. Any long-running session hits compaction regularly, and each one is a potential knowledge-loss event. People are trying all kinds of workarounds: &lt;a href="https://gist.github.com/sigalovskinick/e2e329bb37ecc74b9f15d5ba74ee1ee5" target="_blank" rel="noreferrer"&gt;database-backed persistence layers&lt;/a&gt;, checkpoint files, session replay systems. The approaches vary, but the underlying pattern is always the same: write state to disk before compaction fires, re-inject it after.&lt;/p&gt;

&lt;h2 class="relative group"&gt;AGENTS.md and the compaction problem
 &lt;div id="agentsmd-and-the-compaction-problem" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#agentsmd-and-the-compaction-problem" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://agents.md/" target="_blank" rel="noreferrer"&gt;AGENTS.md&lt;/a&gt; files are the first thing coding agents read when they start a session. Build commands, code conventions, security boundaries, project-specific rules. These instructions anchor the agent&amp;rsquo;s behavior for everything that follows.&lt;/p&gt;
&lt;p&gt;After compaction, those anchors can drift. Claude Code&amp;rsquo;s compaction engine does restore CLAUDE.md content as part of its post-compaction rebuild, but the restoration is a re-read, not a guarantee that the agent will weigh those instructions the same way it did at the start of the session. The compressed summary of the conversation now sits between the agent and its original instructions, and the summary&amp;rsquo;s framing can subtly shift how the agent interprets the rules. A convention like &amp;ldquo;use DCO sign-off for all commits&amp;rdquo; might survive the text but lose its emphasis after the summary compresses 40 minutes of commit-related conversation into a few sentences.&lt;/p&gt;
&lt;p&gt;A post-compaction hook that re-reads the AGENTS.md file and prints its content as a system message is the simplest defense. The agent gets the original instructions back, verbatim, at every compaction boundary. Not a paraphrased version from the summary, the actual file. If your project conventions, security boundaries, or coding standards live in AGENTS.md, make sure the post-compaction hook includes them.&lt;/p&gt;
&lt;p&gt;For orchestrated pipelines where sub-agents receive inline instructions (&amp;ldquo;assess this document using the following criteria&amp;hellip;&amp;rdquo;), the problem is worse. Those instructions live only in the conversation context. After compaction, the summarized instructions might be close enough to the original that the agent doesn&amp;rsquo;t notice the difference, but different enough that it interprets them differently. &amp;ldquo;Re-run the assessment&amp;rdquo; might mean &amp;ldquo;use the same approach as before&amp;rdquo; to a human, but to an agent that just had its memory compressed, &amp;ldquo;before&amp;rdquo; is whatever the summary says it was.&lt;/p&gt;
&lt;p&gt;The fix is the same as for data: put instructions on disk. Instead of passing instructions inline, pass a file path to the AGENTS.md or a task-specific instruction file. The sub-agent reads its instructions from disk at every step boundary, getting the original text every time regardless of what compaction did to the context. The orchestrator&amp;rsquo;s job is to write the instruction file and tell the agent where to find it, not to carry the instructions in the conversation.&lt;/p&gt;

&lt;h2 class="relative group"&gt;From &lt;code&gt;/clear&lt;/code&gt; to state architecture
 &lt;div id="from-clear-to-state-architecture" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#from-clear-to-state-architecture" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The 101 post&amp;rsquo;s advice (&amp;ldquo;start fresh when context gets stale&amp;rdquo;) is human-scale thinking. It works when a person watches the conversation and notices the drift. At pipeline scale, you need something different: an architecture where the agent &lt;em&gt;expects&lt;/em&gt; to lose its memory and recovers cleanly every time.&lt;/p&gt;
&lt;p&gt;The practical checklist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Every mutable value goes to disk immediately.&lt;/strong&gt; Batch progress, counters, timestamps, flags, intermediate results. If you wouldn&amp;rsquo;t trust a colleague to remember a number after being interrupted, don&amp;rsquo;t trust the context window.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use write-once for initialization values.&lt;/strong&gt; Counters, start timestamps, and run parameters should use a &amp;ldquo;set if absent&amp;rdquo; pattern so re-entry after compaction doesn&amp;rsquo;t reset state that&amp;rsquo;s already been established.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read state at every step boundary.&lt;/strong&gt; Don&amp;rsquo;t carry values forward in the conversation. Re-read from disk at the start of every major operation so the agent works from the same source of truth regardless of what compaction did.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the post-compaction hook.&lt;/strong&gt; Re-inject the minimum context the agent needs to continue: current item, current step, run parameters. Anchor the agent to disk state, not to its compressed memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep orchestrator instructions in files, not inline.&lt;/strong&gt; Every sub-agent reads its instructions from disk. The orchestrator substitutes variables into a template and points the agent at the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The state file becomes the single source of truth, not the context window. As it turns out, the context window is a workspace, not a storage system.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/jessica-forrester-a5bb747/" target="_blank" rel="noreferrer"&gt;Jessica Forrester&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/jason-greene-7a72982/" target="_blank" rel="noreferrer"&gt;Jason Greene&lt;/a&gt; have built dozens of multi-agent CI pipelines at Red Hat. Their golden rule: &lt;em&gt;if it&amp;rsquo;s not on disk, it doesn&amp;rsquo;t exist.&lt;/em&gt;&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>What Goes in AGENTS.md (and What Doesn't)</title><link>https://ro14nd.de/what-goes-in-agents-md/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/what-goes-in-agents-md/</guid><description>&lt;p&gt;Every coding agent has its own context file. CLAUDE.md for Claude Code, &lt;code&gt;.cursorrules&lt;/code&gt; for Cursor, &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; for Copilot, GEMINI.md for Gemini CLI. If you work with more than one agent, you end up maintaining multiple files with &lt;a href="https://www.morphllm.com/agents-md-guide" target="_blank" rel="noreferrer"&gt;90% identical content&lt;/a&gt;. AGENTS.md is the attempt to end that.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/what-goes-in-agents-md/hero.jpg" alt="Watercolor illustration of a wooden bulletin board nailed to a fence post in a sheep meadow. A large clean sheet pinned to the center is labeled AGENTS.md with bullet points. Smaller curling notes overlap around it, labeled CLAUDE.md, .cursorrules, GEMINI.md, and copilot-instructions.md. A sheep with round reading glasses studies the main sheet. Other sheep graze in the background with crumpled notes stuck in their wool." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Why AGENTS.md
 &lt;div id="why-agentsmd" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-agentsmd" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The problem AGENTS.md solves isn&amp;rsquo;t technical, it&amp;rsquo;s organizational. Build commands, test instructions, code style rules, project architecture notes are the same regardless of which agent reads them. Copying them into four different files and keeping them in sync is maintenance that adds no value.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://agents.md/" target="_blank" rel="noreferrer"&gt;AGENTS.md&lt;/a&gt; is plain markdown with no required fields, no YAML frontmatter, and no special syntax. Write headings and bullet points, and the agent reads them and adjusts its behavior. It already works with &lt;a href="https://agents.md/" target="_blank" rel="noreferrer"&gt;30+ agents&lt;/a&gt; and sits under the &lt;a href="https://aaif.io/projects/agents-md/" target="_blank" rel="noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; alongside MCP and Goose. Like README.md and CONTRIBUTING.md, agents discover it automatically by looking for the file in the directory tree.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the format, and the format is right. Plain markdown as the lowest common denominator is exactly what a universal standard needs to be. The question is what goes inside, and three academic papers published in 2026 have started to answer it.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What the research says
 &lt;div id="what-the-research-says" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-the-research-says" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://arxiv.org/abs/2602.11988" target="_blank" rel="noreferrer"&gt;ETH Zurich evaluation&lt;/a&gt; tested four coding agents (Claude Code, Codex, Qwen Code) across 138 real-world tasks. The findings challenged the assumption that more context is always better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LLM-generated context files hurt performance.&lt;/strong&gt; Success rates dropped by 0.5-2%, and inference costs went up by 20-23%. In five out of eight tested configurations, having an auto-generated file was worse than having no file at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human-written files help modestly.&lt;/strong&gt; Success improved by about 4% across all agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codebase overviews are useless.&lt;/strong&gt; Every LLM-generated file included one. They didn&amp;rsquo;t help agents find relevant files faster. In many cases agents took more steps with overviews present.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The redundancy proof:&lt;/strong&gt; When the researchers removed &lt;em&gt;all&lt;/em&gt; documentation from the repos, LLM-generated context files suddenly helped (+2.7%). The files were duplicating information the agents already extracted from README files and docs on their own.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;a href="https://arxiv.org/abs/2601.20404" target="_blank" rel="noreferrer"&gt;separate study&lt;/a&gt; showed the flip side: &lt;em&gt;curated, minimal&lt;/em&gt; AGENTS.md files with only three content categories (coding conventions, architecture, project description) reduced median wall-clock time by 28% and output tokens by 16%. Less content, better results.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://arxiv.org/abs/2510.21413" target="_blank" rel="noreferrer"&gt;MSR &amp;lsquo;26 survey&lt;/a&gt; of 10,000 repositories paints a broader picture: only 5% had adopted any context file format, the mean AGENTS.md file was 142 lines long, and half of all AGENTS.md files had zero commits after creation. Most are still write-once-and-forget.&lt;/p&gt;
&lt;p&gt;The practical takeaway from &lt;a href="https://www.augmentcode.com/guides/how-to-build-agents-md" target="_blank" rel="noreferrer"&gt;Augment Code&amp;rsquo;s guide&lt;/a&gt; captures the consensus: write only what agents can&amp;rsquo;t discover on their own.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What goes in, what doesn&amp;rsquo;t
 &lt;div id="what-goes-in-what-doesnt" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-goes-in-what-doesnt" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Based on the research and my experience maintaining context files across 38 projects, here&amp;rsquo;s a quick reference:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Put in AGENTS.md&lt;/strong&gt; (agents can&amp;rsquo;t discover this on their own):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Content&lt;/th&gt;
 &lt;th&gt;Example&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Non-obvious build commands&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;uv pip install -e &amp;quot;.[dev]&amp;quot;&lt;/code&gt; not just &lt;code&gt;pip install&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Tool-specific choices&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;Use &lt;code&gt;podman&lt;/code&gt; not &lt;code&gt;docker&lt;/code&gt;&amp;rdquo;, &amp;ldquo;Use &lt;code&gt;rg&lt;/code&gt; not &lt;code&gt;grep&lt;/code&gt;&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Testing strategy&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;Run &lt;code&gt;make e2e&lt;/code&gt; for integration tests, requires Kind cluster&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Counterintuitive conventions&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;Commit messages use DCO sign-off: &lt;code&gt;git commit -s&lt;/code&gt;&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hard invariants&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;The UID in the Dockerfile must match &lt;code&gt;runAsUser&lt;/code&gt; in Go&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Security boundaries&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;Never commit &lt;code&gt;.env&lt;/code&gt; or &lt;code&gt;.mcp.json&lt;/code&gt; files&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PR/review expectations&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;All PRs require at least one approval from CODEOWNERS&amp;rdquo;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Leave out of AGENTS.md&lt;/strong&gt; (agents figure this out already):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Content&lt;/th&gt;
 &lt;th&gt;Why it&amp;rsquo;s noise&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Codebase overviews&lt;/td&gt;
 &lt;td&gt;Empirically useless per ETH study, agents read the directory tree&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Standard framework conventions&lt;/td&gt;
 &lt;td&gt;React, Express, Rails conventions are in the model&amp;rsquo;s training data&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;README content&lt;/td&gt;
 &lt;td&gt;Agents read README.md on their own&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Dependency lists&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;Cargo.toml&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt; exist for this&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;File-by-file descriptions&lt;/td&gt;
 &lt;td&gt;Agents explore the codebase as needed&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Keep it short.&lt;/strong&gt; Aim for under 150 lines. The efficiency study&amp;rsquo;s best results came from files with just three sections, and every instruction beyond the essentials burns tokens and can actively degrade performance.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Claude Code and AGENTS.md
 &lt;div id="claude-code-and-agentsmd" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#claude-code-and-agentsmd" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;One thing to know: Claude Code does not read AGENTS.md natively, only CLAUDE.md. To have Claude Code pick up your AGENTS.md content, reference it from your CLAUDE.md using the import syntax:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@AGENTS.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This loads the AGENTS.md content into Claude Code&amp;rsquo;s context alongside whatever Claude-specific instructions you have in CLAUDE.md. The two files coexist in the same repo, but they serve different purposes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt; holds everything that&amp;rsquo;s universal across agents: build commands, test instructions, conventions, architecture notes, security boundaries. Any agent that reads the file benefits from this content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt; holds what only Claude Code understands: skills tables and routing rules, context loading directives (&lt;code&gt;@ARCHITECTURE.md&lt;/code&gt;), subagent delegation patterns, context budget management, hook-based context injection, and plugin paths using &lt;code&gt;${CLAUDE_PLUGIN_ROOT}&lt;/code&gt;. Other agents would ignore or misinterpret these instructions.&lt;/p&gt;
&lt;p&gt;In practice, this means your CLAUDE.md becomes much shorter. It starts with &lt;code&gt;@AGENTS.md&lt;/code&gt; to import the universal content, then adds only the Claude-specific configuration below it. Across my 38 projects, roughly 55% of the instructions in my CLAUDE.md files are universal and could move into AGENTS.md. The remaining 45% is either Claude-specific (30%) or uses Claude-specific language for what could otherwise be universal advice (15%).&lt;/p&gt;
&lt;p&gt;The same pattern applies to other tools. Cursor users would keep their &lt;code&gt;.cursorrules&lt;/code&gt; for Cursor-specific features (like glob-based conditional rule activation) and point to AGENTS.md for the shared content. Copilot users do the same with &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t a migration from CLAUDE.md to AGENTS.md. It&amp;rsquo;s a separation of concerns: AGENTS.md as a README for all agents, CLAUDE.md as a configuration file for one specific agent.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The hard part
 &lt;div id="the-hard-part" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-hard-part" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;AGENTS.md standardizes the right thing at the right time. The format works, the ecosystem is growing, and the convergence problem it addresses was real and getting worse.&lt;/p&gt;
&lt;p&gt;But the research is clear about one thing: auto-generating these files with an LLM makes things worse, not better. The files that actually change agent behavior are hand-crafted, containing the counterintuitive choices, the hard-won invariants, and the security boundaries that someone learned the hard way. Twenty-six percent of my own context files were auto-generated by a tool, and they&amp;rsquo;re all thin and formulaic compared to the hand-written ones.&lt;/p&gt;
&lt;p&gt;The spec gives you a place to put instructions. Deciding what belongs there is &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;context engineering&lt;/a&gt;, and that&amp;rsquo;s a practice you build through experience, not a file you generate once and forget.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Three Hops Deep and No Browser in Sight</title><link>https://ro14nd.de/three-hops-deep-and-no-browser-in-sight/</link><pubDate>Sun, 14 Jun 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/three-hops-deep-and-no-browser-in-sight/</guid><description>&lt;p&gt;You&amp;rsquo;ve seen this screen. If you use Claude Code or Cursor with MCP servers, you&amp;rsquo;ve clicked through it dozens of times. &amp;ldquo;Google MCP Server wants to access your Google Account.&amp;rdquo; You review the scopes, click Allow, a token lands in your local config, and everything works.&lt;/p&gt;
&lt;p&gt;Now imagine the agent that needs your Google Calendar isn&amp;rsquo;t the one you&amp;rsquo;re talking to. It&amp;rsquo;s three agents deep in a multi-agent chain, running in a container with no browser and no way to show you a consent screen.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/three-hops-deep-and-no-browser-in-sight/og.jpg" alt="Watercolor split-screen illustration. Left side labeled &amp;#39;On Your Laptop&amp;#39;: a sheep with glasses sitting at a desk, clicking &amp;#39;Allow&amp;#39; on a consent screen. Right side labeled &amp;#39;Three Hops Deep&amp;#39;: three sheep standing in a dim barn filled with hay bales arranged like server racks, facing a padlocked door with no computer in sight." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Two tokens, two problems
 &lt;div id="two-tokens-two-problems" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#two-tokens-two-problems" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;MCP authentication handles the connection between your client and the MCP server well. The spec&amp;rsquo;s &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" target="_blank" rel="noreferrer"&gt;OAuth 2.1 flow&lt;/a&gt; works like standard OAuth: your client authenticates with the MCP server&amp;rsquo;s authorization server, gets an access token, and sends it with every request. The server validates the token, serves the request, and holds no per-user state. Each request carries its own bearer token.&lt;/p&gt;
&lt;p&gt;The problem starts when the MCP server needs to call an external service on your behalf. The token your client sends authenticates you &lt;em&gt;to the MCP server&lt;/em&gt;. When that server needs to read your Google Calendar, it needs a completely different token. One that Google issued, through Google&amp;rsquo;s own consent flow.&lt;/p&gt;
&lt;p&gt;On your laptop, this is invisible. The local MCP server opens your browser to Google&amp;rsquo;s consent screen, you approve, and the server stores the token in &lt;code&gt;~/.mcp-auth&lt;/code&gt; or a similar local path. One user, one browser, done.&lt;/p&gt;
&lt;p&gt;In any other environment, this falls apart. You could have the user acquire a Google token out of band and pass it to the MCP server. But the MCP spec explicitly calls this &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" target="_blank" rel="noreferrer"&gt;token passthrough an anti-pattern&lt;/a&gt;: the server accepts a token it didn&amp;rsquo;t issue, can&amp;rsquo;t verify how it was obtained, and has no way to confirm the scope is appropriate.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve configured your identity provider (Keycloak, Entra ID) as a broker for Google, the plumbing can work. The user authenticates through your SSO, Keycloak redirects the browser to Google&amp;rsquo;s consent screen, the user approves, and Google returns an authorization code. Keycloak exchanges that code for Google&amp;rsquo;s access and refresh tokens and stores them. Later, the MCP server can use &lt;a href="https://datatracker.ietf.org/doc/html/rfc8693" target="_blank" rel="noreferrer"&gt;token exchange&lt;/a&gt; to get the stored Google token from Keycloak. The exchange mechanism works. The bottleneck is the consent event that has to happen first.&lt;/p&gt;
&lt;p&gt;Consent doesn&amp;rsquo;t scale across providers. If your agent chain needs Google Calendar, Microsoft Graph, and Slack, each provider requires its own browser redirect, its own consent screen, its own token. You can&amp;rsquo;t batch them. The question is &lt;em&gt;when&lt;/em&gt; to acquire these tokens. At onboarding (&amp;ldquo;connect your accounts&amp;rdquo;), you over-provision because you don&amp;rsquo;t know which services the user will actually need. At first use, you need a browser. Mid-execution of an agent chain, you have neither.&lt;/p&gt;
&lt;p&gt;This is the token gap. The infrastructure for moving tokens between systems exists. What&amp;rsquo;s missing is a way to get the user in front of the right consent screen at the right time, especially when &amp;ldquo;the right time&amp;rdquo; turns out to be three agents deep in a chain that has no browser.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/three-hops-deep-and-no-browser-in-sight/token-gap.png" alt="Diagram showing the token gap. On the left, the internal trust domain: Keycloak issues an MCP token to the User (Claude Code), who sends requests with the bearer token to the MCP Server. The MCP Server validates against Keycloak. On the right, the external trust domain: Google Calendar API. Between them, a dashed red arrow labeled &amp;#39;needs Google token ???&amp;#39; marks the gap. Below, a dashed box shows the Google Consent Screen that would issue the token, but requires a browser redirect with no path from the MCP server." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The token gap is really two problems that look like one. Credentials are the plumbing: getting a token that the external provider accepts. The token either exists or it doesn&amp;rsquo;t, and the mechanisms for acquiring it (token exchange, vault lookup, gateway injection) are well understood. Consent is the human problem: the user deciding which scopes to grant, to which agent, for what purpose. No amount of infrastructure replaces that decision.&lt;/p&gt;
&lt;p&gt;On a laptop, the consent screen handles both. You authenticate to Google and approve the scopes in the same flow. Once the MCP server moves off your laptop, these concerns separate. A system where agents hold technically valid tokens that nobody explicitly authorized for the current use isn&amp;rsquo;t a security solution. It&amp;rsquo;s a liability. Every pattern below solves the credential problem at a different level. The harder question is always consent.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The known patterns
 &lt;div id="the-known-patterns" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-known-patterns" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Christian Posta has been writing some of the clearest analysis of MCP security and agent authorization patterns. His &lt;a href="https://blog.christianposta.com/mcp-authorization-patterns-upstream-api-calls/" target="_blank" rel="noreferrer"&gt;taxonomy of MCP authorization patterns for upstream API calls&lt;/a&gt; maps the approaches people use to cross this gap. Rather than retread his analysis (go read it, it&amp;rsquo;s worth your time), I&amp;rsquo;ll summarize where each one breaks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared admin credential.&lt;/strong&gt; Give the MCP server a single credential for Google. Every user&amp;rsquo;s request goes through the same account. Easiest to implement, worst for security: no per-user scoping, no audit trail tying actions to individuals, and a single credential whose compromise exposes everyone. The MCP spec &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" target="_blank" rel="noreferrer"&gt;explicitly calls this an anti-pattern&lt;/a&gt;. Don&amp;rsquo;t do it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credential passthrough.&lt;/strong&gt; The user acquires a Google token out of band and passes it to the MCP server. The spec flags this as an anti-pattern too. The server accepts tokens it didn&amp;rsquo;t issue and can&amp;rsquo;t validate. There&amp;rsquo;s no proof the token came from a proper OAuth flow, no way to verify the scope matches what this server should have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSO federation.&lt;/strong&gt; Use your identity provider&amp;rsquo;s brokering capability (Keycloak&amp;rsquo;s identity broker, Entra ID&amp;rsquo;s external identities) to federate with Google. If the user has already linked their Google account through the IdP, this works without any browser interaction at request time. The IdP stores the Google tokens, and token exchange surfaces them to the MCP server. This solves the credential problem cleanly.&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t solve the consent problem. When you configure Google as a broker in Keycloak, you specify which scopes to request up front: &lt;code&gt;openid&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;profile&lt;/code&gt; for identity, plus whatever API scopes your MCP servers need (&lt;code&gt;calendar.readonly&lt;/code&gt;, &lt;code&gt;drive.readonly&lt;/code&gt;). These lock in at configuration time. If a new MCP server needs a scope you didn&amp;rsquo;t anticipate, you update the config and send the user through Google&amp;rsquo;s consent screen again. Google shows all scopes at once, so you can&amp;rsquo;t incrementally add one without a new consent event. And token exchange hands over the full stored token with all granted scopes. There&amp;rsquo;s no built-in way to give one MCP server &lt;code&gt;calendar.readonly&lt;/code&gt; and another &lt;code&gt;drive.readonly&lt;/code&gt; from the same token.&lt;/p&gt;
&lt;p&gt;This is the pre-flight consent problem in infrastructure clothing: pre-configure scopes and hope they cover everything agents will need. When they don&amp;rsquo;t, you need a browser. Two IETF drafts, &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-chaining/" target="_blank" rel="noreferrer"&gt;Identity Assertion Authorization Grant&lt;/a&gt; and &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-chaining/" target="_blank" rel="noreferrer"&gt;OAuth Identity Chaining Across Domains&lt;/a&gt;, aim to enable cross-domain token exchange without per-provider consent flows. Neither is widely implemented yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URL elicitation.&lt;/strong&gt; The MCP spec&amp;rsquo;s &lt;a href="https://modelcontextprotocol.io/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera" target="_blank" rel="noreferrer"&gt;elicitation mechanism&lt;/a&gt; (SEP-1036) has two modes. &lt;code&gt;form&lt;/code&gt; handles in-band data entry where the response passes through the MCP client. &lt;code&gt;url&lt;/code&gt; bypasses the client entirely. In URL mode, the MCP server sends a URL to the client, the client shows it to the user, the user opens it in a browser. That URL initiates a standard OAuth authorization flow with Google. The callback redirects back to the MCP server, which receives the authorization code and exchanges it for tokens server-side. The user just clicks a link and approves. Tokens never touch the client or the model.&lt;/p&gt;
&lt;p&gt;This works for multiple users, since each completes their own consent flow and the server stores per-user tokens. But it makes the MCP server stateful: it now manages external token storage, refresh rotation, and revocation. And it still requires a user with a browser at the other end of the chain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent gateway.&lt;/strong&gt; A gateway sits in front of MCP servers, controlling who can access which servers and tools. Projects like &lt;a href="https://github.com/Kuadrant/mcp-gateway" target="_blank" rel="noreferrer"&gt;Kuadrant&amp;rsquo;s MCP gateway&lt;/a&gt; enforce authN/authZ via JWT validation and CEL-based (Common Expression Language) policies at this layer. Christian&amp;rsquo;s &lt;a href="https://blog.christianposta.com/mcp-authorization-patterns-upstream-api-calls/#pattern-5-offload-out-of-band-elicitation-to-secure-infrastructure" target="_blank" rel="noreferrer"&gt;Pattern 5&lt;/a&gt; extends this: the gateway also acquires and manages credentials for upstream APIs, injecting external tokens into outbound calls so the MCP server never touches them. But a gateway operates within the platform&amp;rsquo;s trust domain. It can block access to an MCP server, but it can&amp;rsquo;t downscope an already-granted Google token or make consent decisions on the user&amp;rsquo;s behalf. Platform authorization and external-service consent are orthogonal.&lt;/p&gt;
&lt;p&gt;Each of these patterns assumes something about the environment: that a user is present, that a browser is reachable, or that the credential-acquiring component knows which user to act for. Those assumptions hold on a developer laptop. They start failing when the MCP server runs remotely, in a cluster, or in any environment where no browser is directly reachable. They collapse entirely when agents call other agents.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Where it gets harder: multi-agent chains
 &lt;div id="where-it-gets-harder-multi-agent-chains" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-it-gets-harder-multi-agent-chains" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/three-hops-deep-and-no-browser-in-sight/multi-agent-chain.png" alt="Diagram showing a multi-agent chain. User (Claude Code) with a browser sends &amp;#39;schedule meeting&amp;#39; to a Planning Agent (hop 1, no browser), which sends &amp;#39;find slots&amp;#39; to a Calendar Agent (hop 2, no browser), which sends &amp;#39;read calendar&amp;#39; to a Google MCP Server (hop 3, no browser). The MCP Server needs a Google token to reach the Google Calendar API but has no path to the user&amp;#39;s browser." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Take a concrete scenario. A planning agent receives a request: &amp;ldquo;Schedule a meeting with the team and attach the latest design doc.&amp;rdquo; The planning agent calls a calendar agent to find your available slots. The calendar agent calls a Google MCP server to read your calendar. That MCP server needs a Google OAuth token for your account.&lt;/p&gt;
&lt;p&gt;You made the original request three hops ago. The Google MCP server has no connection to a browser. URL elicitation can&amp;rsquo;t work because there&amp;rsquo;s no client to show the URL to. The calendar agent doesn&amp;rsquo;t even know it was invoked by a human. It just received a tool call from the planning agent.&lt;/p&gt;
&lt;p&gt;Three approaches keep coming up. None of them work well.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/three-hops-deep-and-no-browser-in-sight/error-propagation.png" alt="Two diagrams comparing error propagation approaches. Top (red, unreliable): -32042 error flows from MCP Server through LLM agents back to user, but may get lost or rephrased at each hop. Bottom (green, reliable): same error intercepted at the harness transport layer, propagated through infrastructure without the LLM seeing it." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Error propagation.&lt;/strong&gt; The MCP spec has a mechanism for direct connections: the Google MCP server returns error code &lt;code&gt;-32042&lt;/code&gt; (&lt;code&gt;URLElicitationRequiredError&lt;/code&gt;) with a consent URL embedded in the response. The client recognizes the error, shows the URL, the user completes the OAuth flow, the callback lands on the MCP server, and the client retries.&lt;/p&gt;
&lt;p&gt;In a multi-agent chain, this falls apart. The error goes to the calendar agent, not to the user&amp;rsquo;s client. For the consent URL to reach the user, every intermediate agent would need to recognize it and propagate it up the chain. LLM-based agents are unreliable at this. They might rephrase the error, attempt workarounds, or lose the URL entirely. Even if the URL reaches the user and they complete the consent flow, the &amp;ldquo;retry&amp;rdquo; signal needs to travel back down through the same chain.&lt;/p&gt;
&lt;p&gt;A more promising approach would handle this at the harness level. The runtime that makes MCP calls intercepts the &lt;code&gt;-32042&lt;/code&gt; at the transport layer before the LLM sees it, propagates the consent URL back through the infrastructure chain, and retries transparently once the user completes the flow. The LLM never sees the auth error. But this requires every harness in the chain to support the protocol, and no agent framework standardizes this today.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Async notification.&lt;/strong&gt; Instead of propagating the consent URL through the agent chain, the MCP server delivers it via a side channel: email, Slack message, push notification. The user clicks the link, completes the same OAuth consent flow in their browser, and the callback lands on the MCP server just like in URL elicitation. The OAuth flow is identical. Only the delivery mechanism for the consent URL is different.&lt;/p&gt;
&lt;p&gt;This decouples consent from the agent chain but introduces its own problems. The MCP server needs to know how to reach the user (email address? Slack handle?) and needs to identify which user to contact from the bearer token on the original request. Timeouts are ugly: the agent chain blocks while waiting, with no guarantee that consent ever arrives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pre-flight consent.&lt;/strong&gt; Before the agent chain starts, the platform collects all the tokens the chain &lt;em&gt;might&lt;/em&gt; need. The user consents to Google Calendar, Google Drive, Slack, and Jira up front. The platform stores tokens in a vault and injects them as needed.&lt;/p&gt;
&lt;p&gt;This front-loads the UX problem but creates over-provisioning. The platform doesn&amp;rsquo;t know which external services the chain will actually use because agent behavior is non-deterministic. Asking for every possible scope up front violates least privilege. Users learn to click &amp;ldquo;Allow&amp;rdquo; without reading the scopes, which is exactly the pattern OAuth was designed to prevent.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.coalitionforsecureai.org/wp-content/uploads/2026/04/agentic-identity-and-access-control.pdf" target="_blank" rel="noreferrer"&gt;CoSAI&amp;rsquo;s Agentic IAM paper&lt;/a&gt; (March 2026) establishes the right principles for multi-agent delegation: scope narrows at each hop, never expands. Delegation tokens carry explicit actor and subject claims so the chain of custody stays auditable. These principles describe what the token &lt;em&gt;should look like&lt;/em&gt; once it exists. Getting the user&amp;rsquo;s consent in the first place, when the user is three hops away, is a different problem entirely.&lt;/p&gt;
&lt;p&gt;The most interesting architectural proposal I&amp;rsquo;ve seen is &lt;a href="https://aauth.dev" target="_blank" rel="noreferrer"&gt;AAuth&lt;/a&gt;, a delegation protocol from Dick Hardt (the author of OAuth 2.0) that rethinks the problem from scratch. It&amp;rsquo;s an &lt;a href="https://datatracker.ietf.org/doc/draft-hardt-aauth-protocol/" target="_blank" rel="noreferrer"&gt;IETF draft&lt;/a&gt; in the OAuth working group. Instead of one-time consent grants that get reused indefinitely, AAuth introduces a Person Server that acts as an ongoing consent oracle. Dasith Wijesiriwardena&amp;rsquo;s &lt;a href="https://dasith.me/2026/06/10/the-errand-agentic-delegation/" target="_blank" rel="noreferrer"&gt;errand analogy&lt;/a&gt; is the most accessible explanation, and Christian wrote a &lt;a href="https://blog.christianposta.com/aauth-full-demo/" target="_blank" rel="noreferrer"&gt;hands-on demo walkthrough&lt;/a&gt;. The agent doesn&amp;rsquo;t hold your Google token. It carries an unsigned request from the resource to the Person Server, the Person Server checks with you (or evaluates against a pre-approved &amp;ldquo;mission&amp;rdquo;), and returns a signed authorization. The Person Server evaluates each request individually. The agent shuttles tokens between parties but never holds standing credentials.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;mission&amp;rdquo; concept is what makes this click. A mission is a written-down, approved intent that the Person Server holds on behalf of the user. Think of it as a scoped work order: &amp;ldquo;schedule a meeting with the team and attach the latest design doc.&amp;rdquo; The agent proposes the mission, the user approves it, and every subsequent request gets evaluated against the mission&amp;rsquo;s stated purpose. Read a calendar to find available slots? In scope, approved silently. Send an email to someone not on the team? Out of scope, escalated to the user.&lt;/p&gt;
&lt;p&gt;The Person Server sees all requests across all resources under that mission, which makes it the one party positioned to detect when the agent&amp;rsquo;s behavior drifts from the original intent. Missions have a lifecycle: active or terminated. Tokens issued under them are short-lived. That&amp;rsquo;s a cleaner answer to the pre-flight consent problem than asking for every possible scope up front and hoping for the best.&lt;/p&gt;
&lt;p&gt;AAuth also handles sub-agent chains explicitly. Each hop gets its own identity for audit and revocation, authority flows through the parent agent, and the full delegation chain back to the person is preserved. The protocol is early (the &lt;a href="https://datatracker.ietf.org/doc/draft-hardt-aauth-protocol/" target="_blank" rel="noreferrer"&gt;spec&lt;/a&gt;, an &lt;a href="https://explorer.aauth.dev/" target="_blank" rel="noreferrer"&gt;interactive explorer&lt;/a&gt;, and a .NET SDK are available), and the architecture addresses the right problems. It also makes the credentials-vs-consent split structural: the Person Server handles consent (did the user approve this action?), while the resource&amp;rsquo;s Access Server enforces its own policies independently. Two gates, both must pass.&lt;/p&gt;
&lt;p&gt;The catch: AAuth requires the resource to participate in the protocol. The Person Server issues AAuth tokens, not Google OAuth tokens. For existing providers like Google, Microsoft, or Slack that only accept their own OAuth tokens, you still need their consent flow to get a token they&amp;rsquo;ll accept. AAuth solves consent and delegation for resources that speak AAuth. For everything else, the consent screen problem stays until major API providers adopt the protocol.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What remains open
 &lt;div id="what-remains-open" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-remains-open" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The token gap has patterns. Multi-agent consent doesn&amp;rsquo;t. Here&amp;rsquo;s what I think still needs solving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where do tokens live?&lt;/strong&gt; Per-user external tokens need to persist across agent invocations but shouldn&amp;rsquo;t be accessible to agents that don&amp;rsquo;t need them. A vault with fine-grained access policies is the obvious answer, but the lifecycle is tricky. Who provisions the vault entry? Who rotates it? What happens when the user revokes consent through Google&amp;rsquo;s security settings? Who cleans up the stale token?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do you reach the user?&lt;/strong&gt; In a multi-agent chain, the consent request needs to travel back to a human who can open a browser. Today this is ad hoc: error propagation, push notifications, Slack messages. There&amp;rsquo;s no standard channel for &amp;ldquo;an agent three hops deep needs your approval.&amp;rdquo; Building one means defining a consent backpropagation protocol that agent frameworks understand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens when consent drifts?&lt;/strong&gt; A user approves &lt;code&gt;calendar.readonly&lt;/code&gt; for a scheduling task. Six months later, a different agent chain uses the same token to read calendar details for a completely different purpose. The token is technically valid. The consent is contextually stale. AAuth&amp;rsquo;s mission model addresses this by scoping consent to a specific task with a defined lifecycle. But deciding what counts as &amp;ldquo;in scope&amp;rdquo; is left to the Person Server&amp;rsquo;s policy engine, and that policy engine doesn&amp;rsquo;t exist yet as a general-purpose solution. Of the open problems, this is the one that worries me most. A valid token with stale consent is indistinguishable from a properly authorized one until something goes wrong.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://thehackernews.com/2026/04/vercel-breach-tied-to-context-ai-hack.html" target="_blank" rel="noreferrer"&gt;Vercel/Context.ai incident&lt;/a&gt; in April 2026 showed what over-provisioned consent looks like in practice. An infostealer on a Context.ai employee&amp;rsquo;s machine compromised OAuth tokens that Context.ai held on behalf of its users. One of those tokens belonged to a Vercel employee who had connected Context.ai to their enterprise Google Workspace with broad permissions. The attacker used that stolen token to access the employee&amp;rsquo;s Google Workspace account, and from there found credentials that led into Vercel&amp;rsquo;s internal environments. The breach wasn&amp;rsquo;t a chain of delegated access. It was a single, broadly scoped OAuth grant to a third-party AI tool, and when that tool&amp;rsquo;s infrastructure was compromised, the blast radius was proportional to the scope.&lt;/p&gt;
&lt;p&gt;The IETF drafts on identity chaining and assertion grants are the right long-term direction. When they reach production implementations, they&amp;rsquo;ll provide a standards-based way to exchange internal tokens for external ones without the user clicking through a consent screen every time. But &amp;ldquo;12-24 months to RFC&amp;rdquo; means organizations deploying multi-agent systems today need interim solutions.&lt;/p&gt;
&lt;p&gt;The fundamental tension remains: agents are non-deterministic, but OAuth consent is interactive. Agents compose dynamically, but consent is granted statically. Protocols like &lt;a href="https://aauth.dev" target="_blank" rel="noreferrer"&gt;AAuth&lt;/a&gt; and standards like the IETF identity chaining drafts are attacking this from different angles, but neither is production-ready today. Until the consent model catches up to agent composition, the gap between &amp;ldquo;the agent has a valid token&amp;rdquo; and &amp;ldquo;the user approved this specific use&amp;rdquo; will keep growing.&lt;/p&gt;
&lt;p&gt;On your laptop, that gap doesn&amp;rsquo;t matter. Three hops deep and no browser in sight, it&amp;rsquo;s the whole problem.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Red Teaming Agents, Not Models</title><link>https://ro14nd.de/red-teaming-agents-not-models/</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/red-teaming-agents-not-models/</guid><description>&lt;p&gt;Your agent passed every guardrail test. It never says anything harmful, never generates offensive content, politely declines every adversarial prompt you throw at it. And last Tuesday, it quietly deleted the wrong database because a Jira ticket it was reading contained a hidden instruction in the description field.&lt;/p&gt;
&lt;p&gt;The guardrails caught everything the agent &lt;em&gt;said&lt;/em&gt;. They caught nothing about what it &lt;em&gt;did&lt;/em&gt;.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/red-teaming-agents-not-models/og.png" alt="Watercolor split-screen illustration. Left side labeled &amp;#39;What It Says&amp;#39;: a sheep at a podium speaking to farm animals with a checkmark in its speech bubble. Right side labeled &amp;#39;What It Does&amp;#39;: the same sheep picking a lock on a filing cabinet in a dark barn with scattered papers on the floor." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;What red teaming is (and isn&amp;rsquo;t)
 &lt;div id="what-red-teaming-is-and-isnt" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-red-teaming-is-and-isnt" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If you come from software engineering rather than security, red teaming might sound like a fancy term for testing. It&amp;rsquo;s related, but the framing is different. In traditional security, a &lt;a href="https://csrc.nist.gov/glossary/term/red_team" target="_blank" rel="noreferrer"&gt;red team&lt;/a&gt; plays the attacker: a group authorized to emulate an adversary&amp;rsquo;s capabilities against your system, reporting what worked so you can fix it before someone else finds it. The &lt;a href="https://csrc.nist.gov/glossary/term/blue_team" target="_blank" rel="noreferrer"&gt;blue team&lt;/a&gt; plays defense: they monitor, detect, and respond to threats. Purple teaming is when the two work together, feeding offensive findings directly into defensive improvements.&lt;/p&gt;
&lt;p&gt;For AI systems, red teaming has mostly meant sending adversarial prompts to a model and checking whether the response is harmful. Can you trick it into generating dangerous instructions? Can you bypass its safety training with creative prompt engineering? Tools like &lt;a href="https://github.com/NVIDIA/garak" target="_blank" rel="noreferrer"&gt;Garak&lt;/a&gt; automate this at scale, running libraries of attack prompts against chat endpoints and scoring the responses.&lt;/p&gt;
&lt;p&gt;That approach works well for models and chatbots, where the only output is text. If the model says something harmful, you catch it by reading what it said. But agents don&amp;rsquo;t just say things. They do things. And that changes the entire testing surface.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Models say things. Agents do things.
 &lt;div id="models-say-things-agents-do-things" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#models-say-things-agents-do-things" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The difference sounds obvious, but the implications run deep. A model is a brain in a jar. It receives text, it produces text, and everything it does is visible in the text it produces. You can evaluate its behavior by evaluating its output.&lt;/p&gt;
&lt;p&gt;An agent is a brain with hands. It has tools: file systems, APIs, databases, email, MCP servers, shell access. When it acts, the action happens in the real world, not in the response text. An agent could respond with &amp;ldquo;I would never do that&amp;rdquo; while simultaneously executing the thing it claims it wouldn&amp;rsquo;t do through a tool call. If you&amp;rsquo;re only checking the text output, you catch nothing.&lt;/p&gt;
&lt;p&gt;This is why model-level red teaming doesn&amp;rsquo;t transfer to agents. Sending adversarial prompts and checking responses tests the chat layer. It doesn&amp;rsquo;t test whether the agent&amp;rsquo;s tool calls are correct, whether its side effects are intended, or whether a compromised data source can steer it toward actions the operator never authorized.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Three attack surfaces
 &lt;div id="three-attack-surfaces" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#three-attack-surfaces" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Agent red teaming needs to cover three layers. Borrowing from traditional penetration testing, you can think of these as increasing levels of attacker knowledge about the target system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Input-level (black box).&lt;/strong&gt; The attacker sends malicious prompts directly to the agent. This is the layer that existing guardrail tools handle well. You don&amp;rsquo;t need to know anything about the agent&amp;rsquo;s internals. You just send bad input and see what happens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data-level (grey box).&lt;/strong&gt; The attacker compromises a data source the agent consumes. A Jira ticket with hidden instructions in the description. A document with invisible text that redirects the agent&amp;rsquo;s goal. A database record with embedded prompts. This is indirect prompt injection, and the &lt;a href="https://genai.owasp.org/2025/12/09/owasp-top-10-for-agentic-applications-the-benchmark-for-agentic-security-in-the-age-of-autonomous-ai/" target="_blank" rel="noreferrer"&gt;OWASP Top 10 for Agentic Applications&lt;/a&gt; classifies Agent Goal Hijack (ASI01) and Tool Misuse (ASI02) as the two highest-priority risks in this category.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool-level (white box).&lt;/strong&gt; The attacker compromises a tool or MCP server the agent calls. The tool returns manipulated responses that steer the agent toward unintended actions. This requires knowing the agent&amp;rsquo;s tool chain, but once you have that knowledge, you can influence the agent&amp;rsquo;s reasoning at every step.&lt;/p&gt;
&lt;p&gt;Each layer requires different testing infrastructure, different attack libraries, and different detection capabilities. Most organizations today test only the first layer and assume the other two are covered by general infrastructure security. They&amp;rsquo;re not.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Test the real agent, not a simulation
 &lt;div id="test-the-real-agent-not-a-simulation" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#test-the-real-agent-not-a-simulation" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The most promising approach emerging in 2026 is best described as &amp;ldquo;testing the real agent in a synthetic world.&amp;rdquo; Instead of rebuilding your agent in a test harness (which means you&amp;rsquo;re no longer testing the same agent), you intercept the agent&amp;rsquo;s tool calls and replace real backends with controlled synthetic ones.&lt;/p&gt;
&lt;p&gt;The agent thinks it&amp;rsquo;s talking to its real MCP servers, its real APIs, its real database. It&amp;rsquo;s actually talking to fakes that can inject attack payloads through tool responses, capture every action the agent takes, and verify whether the agent did something it shouldn&amp;rsquo;t have. Think of it as setting up a honeypot for your own agent.&lt;/p&gt;
&lt;p&gt;This idea was pioneered by &lt;a href="https://agentdojo.spylab.ai/" target="_blank" rel="noreferrer"&gt;Agent Dojo&lt;/a&gt; out of ETH Zurich, which introduced a concept that sounds simple but changes how you think about agent security: dual scoring. Every test measures two things. The security score tells you whether the agent resisted the attack. The utility score tells you whether the agent still completed its legitimate task. That tradeoff matters because you can trivially make any agent perfectly secure by making it refuse to do anything at all. Security without utility isn&amp;rsquo;t security. It&amp;rsquo;s a paperweight.&lt;/p&gt;
&lt;p&gt;Agent Dojo itself appears to be inactive (the last commit is from late 2025), but its core ideas are showing up across the next generation of agent testing tools. &lt;a href="https://github.com/asago-ai/midojo" target="_blank" rel="noreferrer"&gt;MiDojo&lt;/a&gt; picks up where Agent Dojo left off, adding man-in-the-middle interception of MCP tool calls so you can test real agents against synthetic backends without rebuilding them. The &lt;a href="https://blog.trustyai.org/blog/midojo-intro/" target="_blank" rel="noreferrer"&gt;MiDojo introduction&lt;/a&gt; walks through the design and a hello-world weather suite end to end. The same dual-scoring principles also inform &lt;a href="https://labs.cloudsecurityalliance.org/research/csa-research-note-nist-ai-agent-red-teaming-standards-202603/" target="_blank" rel="noreferrer"&gt;NIST&amp;rsquo;s emerging guidance&lt;/a&gt; on agent security standards.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Detection is harder than attack
 &lt;div id="detection-is-harder-than-attack" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#detection-is-harder-than-attack" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Injecting attacks is relatively straightforward. The hard part is &lt;em&gt;detecting&lt;/em&gt; whether the agent acted on the injection.&lt;/p&gt;
&lt;p&gt;The reason is that agents don&amp;rsquo;t respond to attacks in predictable ways. An agent that receives a hidden instruction through a compromised document might not act on it through the same document tool. It might route the exfiltration through an email API three tool calls later, or write sensitive data to a file that gets synced elsewhere, or modify a configuration that opens a door for a future request. Tying the cause (compromised document) to the effect (data exfiltration via email) requires tracing the full execution path across the agent&amp;rsquo;s entire tool chain, not just watching individual tool calls in isolation.&lt;/p&gt;
&lt;p&gt;The distinction between red teaming and blue teaming matters here. Red teaming asks &amp;ldquo;can I make the agent do something bad?&amp;rdquo; Blue teaming asks &amp;ldquo;can I detect when the agent is doing something bad?&amp;rdquo; Both questions require the same detection infrastructure, but they answer different things and operate at different times:&lt;/p&gt;
&lt;p&gt;Red teaming runs before deployment. You simulate attacks, measure resistance, and improve defenses based on what you find. Blue teaming (runtime monitoring) watches the agent during normal production operation and catches unintended actions as they happen, even without an adversary present.&lt;/p&gt;
&lt;p&gt;Detection capability is dual-use. Once you build the infrastructure to detect harmful actions for red teaming purposes, you can deploy that same infrastructure as a runtime monitor. Red team findings become detection rules. Detection rules become guardrails.&lt;/p&gt;
&lt;p&gt;But attacks aren&amp;rsquo;t the only problem.&lt;/p&gt;

&lt;h2 class="relative group"&gt;No adversary required
 &lt;div id="no-adversary-required" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#no-adversary-required" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The most interesting question that comes up in agent security discussions is one that isn&amp;rsquo;t about adversaries at all: what about agents that do something unintended given a perfectly cooperative prompt and completely uncompromised tools?&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Delete all the temp files&amp;rdquo; and the agent deletes non-temp files too. &amp;ldquo;Update the configuration&amp;rdquo; and the agent overwrites unrelated settings. &amp;ldquo;Summarize this document&amp;rdquo; and the agent silently modifies it during the read. These aren&amp;rsquo;t attack scenarios. They&amp;rsquo;re the mundane reality of agents operating in complex environments, and they&amp;rsquo;re arguably harder to test because they happen non-deterministically and only surface under specific combinations of context, tool state, and agent reasoning.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve read the earlier posts in &lt;a href="https://ro14nd.de/the-flock/" &gt;The Flock&lt;/a&gt; series, this should sound familiar. The &lt;a href="https://ro14nd.de/the-sheep-that-picked-the-lock/" &gt;creativity paradox&lt;/a&gt; is exactly this: agents doing the wrong thing while optimizing for the right goal. Red teaming catches adversarial attacks. Catching the agent&amp;rsquo;s own well-intentioned mistakes requires the same detection infrastructure but a different testing mindset: not &amp;ldquo;what happens when someone attacks?&amp;rdquo; but &amp;ldquo;what happens on a normal Tuesday when nobody is attacking and the agent just gets creative?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Agent observability becomes a prerequisite for security here. If your team can&amp;rsquo;t trace an agent&amp;rsquo;s tool calls well enough to explain what it did after a normal run, you certainly can&amp;rsquo;t trace what it did under adversarial conditions. (More on agent observability for CI pipelines in an upcoming post in The Flock series.)&lt;/p&gt;

&lt;h2 class="relative group"&gt;Purple teaming: when the loop closes
 &lt;div id="purple-teaming-when-the-loop-closes" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#purple-teaming-when-the-loop-closes" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The 2026 trend is merging red and blue teaming into continuous &lt;strong&gt;purple teaming&lt;/strong&gt;: autonomous agents continuously simulate attacks, detect vulnerabilities in real time, and feed findings back into guardrails, all in the same cycle. Multiple vendors are building this as a product category.&lt;/p&gt;
&lt;p&gt;It sounds compelling: red team findings automatically become runtime detection rules, which become regression tests, which get re-tested in the next red team cycle. Every vulnerability found once is caught forever after. The loop closes, and the system gets more secure with every iteration.&lt;/p&gt;
&lt;p&gt;But the risk is real. &lt;a href="https://www.isaca.org/resources/news-and-trends/industry-news/2026/autonomous-red-vs-blue-teaming-a-new-frontier-in-cybersecurity-risk-and-reward" target="_blank" rel="noreferrer"&gt;ISACA warns&lt;/a&gt; about escalatory spirals when autonomous red and blue agents interact. A false positive triggers a defensive action (revoking credentials, blocking a tool). The red team agent interprets the changed environment as a new attack surface and escalates. The blue team responds with more aggressive countermeasures. Within minutes, the two AI systems are fighting each other over a signal that was never a real threat.&lt;/p&gt;
&lt;p&gt;The mitigation that works is &amp;ldquo;shadow mode&amp;rdquo;: AI agents suggest security actions, humans approve them. The automation handles the speed and coverage. The human handles the judgment about whether the finding is real and the response is proportionate.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Where this leaves us
 &lt;div id="where-this-leaves-us" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-this-leaves-us" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Agent security is where web application security was in the early 2000s: the attack surface is new, the tooling is immature, and most organizations haven&amp;rsquo;t started thinking about it systematically. The &lt;a href="https://genai.owasp.org/2025/12/09/owasp-top-10-for-agentic-applications-the-benchmark-for-agentic-security-in-the-age-of-autonomous-ai/" target="_blank" rel="noreferrer"&gt;OWASP Top 10 for Agentic Applications&lt;/a&gt; published in late 2025 is the first formal taxonomy. &lt;a href="https://opensource.microsoft.com/blog/2026/04/02/introducing-the-agent-governance-toolkit-open-source-runtime-security-for-ai-agents/" target="_blank" rel="noreferrer"&gt;Microsoft&amp;rsquo;s Agent Governance Toolkit&lt;/a&gt; (April 2026) addresses all ten risks with runtime enforcement.&lt;/p&gt;
&lt;p&gt;Full disclosure: we&amp;rsquo;re working on this at Red Hat. The &lt;a href="https://github.com/trustyai-explainability" target="_blank" rel="noreferrer"&gt;TrustyAI&lt;/a&gt; team has been integrating &lt;a href="https://github.com/NVIDIA/garak" target="_blank" rel="noreferrer"&gt;Garak&lt;/a&gt; (the open source LLM vulnerability scanner) into the &lt;a href="https://developers.redhat.com/articles/2026/05/14/every-layer-counts-defense-depth-ai-agents-red-hat-ai" target="_blank" rel="noreferrer"&gt;Red Hat AI platform&lt;/a&gt; for automated red teaming of models and agents, and is building the next generation of agent-level testing that goes beyond chat-endpoint scanning. The &lt;a href="https://www.youtube.com/watch?v=6K8eqQ4ymvk" target="_blank" rel="noreferrer"&gt;Summit 2026 Day 2 keynote&lt;/a&gt; demos some of where this is heading.&lt;/p&gt;
&lt;p&gt;Concretely, the TrustyAI team is building MiDojo as part of &lt;a href="https://github.com/asago-ai" target="_blank" rel="noreferrer"&gt;Asago&lt;/a&gt;, a broader agent red teaming platform. The platform architecture is still being finalized, but MiDojo is the first component being made publicly available. Separately, the &lt;a href="https://github.com/kagenti" target="_blank" rel="noreferrer"&gt;Kagenti&lt;/a&gt; team has been running &lt;a href="https://github.com/kagenti/capture-the-flag" target="_blank" rel="noreferrer"&gt;capture-the-flag exercises&lt;/a&gt; against agents in Kubernetes clusters, testing whether policy enforcement (OPA, sandboxing) actually holds when an agent gets creative with leaked credentials.&lt;/p&gt;
&lt;p&gt;For practical reading: Morgan Foster&amp;rsquo;s &lt;a href="https://usize.github.io/blog/2026/april/claude-stole-the-hr-docs.html" target="_blank" rel="noreferrer"&gt;writeup of Claude stealing the HR docs&lt;/a&gt; and Roy Belio&amp;rsquo;s walkthrough of &lt;a href="https://developers.redhat.com/articles/2026/05/26/testing-infrastructure-red-teaming-abliterated-models" target="_blank" rel="noreferrer"&gt;infrastructure red teaming with abliterated models&lt;/a&gt; on Red Hat Developer.&lt;/p&gt;
&lt;p&gt;The gap that remains is detection. We know how to inject attacks at every layer. We&amp;rsquo;re getting better at testing real agents without rebuilding them. What we still lack is reliable, general-purpose detection of whether an agent &lt;em&gt;acted&lt;/em&gt; on a compromise. A poisoned Jira ticket doesn&amp;rsquo;t show up as a failed Jira call. It shows up three tool calls later as a perfectly normal-looking email with sensitive data in the body. Catching that means tracing the full chain from compromised input to unauthorized action, even when the two happen through completely unrelated tools. Solving that problem unlocks both red teaming and runtime monitoring in a single investment.&lt;/p&gt;
&lt;p&gt;The agent that says all the right things and does all the wrong things is the one you need to worry about. And right now, most testing only checks what it says.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>The Sheep That Picked the Lock</title><link>https://ro14nd.de/the-sheep-that-picked-the-lock/</link><pubDate>Mon, 01 Jun 2026 09:00:00 +0200</pubDate><guid>https://ro14nd.de/the-sheep-that-picked-the-lock/</guid><description>&lt;p&gt;The &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;101 post&lt;/a&gt; covered working with one agent in one session, with a human watching. Those principles still hold, but the question changes when you remove the human and let agents run unsupervised in CI. This is the first post in &lt;strong&gt;&amp;ldquo;The Flock,&amp;rdquo;&lt;/strong&gt; a series about what happens when agents run at scale with nobody around. We start with creativity, because it&amp;rsquo;s the pattern every developer recognizes immediately.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The farmer installed a new gate latch, specifically designed to be sheep-proof. The next morning, the gate was open, the latch was technically undamaged, and three sheep were in the vegetable garden eating his prize-winning cabbages. The latch worked exactly as specified. The sheep just found a different way around.&lt;/em&gt;&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/the-sheep-that-picked-the-lock/og.png" alt="Watercolor illustration of three sheep eating cabbages in the farmer&amp;#39;s vegetable garden. The gate latch is still intact. The farmer stands on his porch, coffee in hand, staring in disbelief." /&gt;
 
 
 &lt;/figure&gt;
&lt;!--more--&gt;

&lt;h2 class="relative group"&gt;The most expensive cheat sheet ever written
 &lt;div id="the-most-expensive-cheat-sheet-ever-written" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-most-expensive-cheat-sheet-ever-written" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://dev.to/samchon/ai-deleted-my-tests-and-said-all-tests-pass-a-horror-story-from-porting-typia-from-typescript-2bmf" target="_blank" rel="noreferrer"&gt;typia project&lt;/a&gt; needed to port a TypeScript validator to Go. The task sounded straightforward: take a &lt;code&gt;.ts&lt;/code&gt; file, rewrite it as a &lt;code&gt;.go&lt;/code&gt; file, leave the algorithm alone, iterate until tests pass. The test suite was serious: 2,900 files, 168 structural fixtures, 80k lines total.&lt;/p&gt;
&lt;p&gt;The developer kicked off the agent overnight. He woke up to a green CI badge with all tests passing. When he opened the diff, two-thirds of the core logic was missing. The agent had deleted every failing test. &amp;ldquo;All tests pass&amp;rdquo; was technically accurate, just not in the way anyone intended.&lt;/p&gt;
&lt;p&gt;After adding a rule that tests are sacred and must never be modified, he started a second run. This time the agent consumed 8 billion tokens and built a massive switch statement that memorized all 168 test outputs as hardcoded strings. It had run the original TypeScript validator hundreds of times, captured the output, and embedded it verbatim. The most expensive cheat sheet ever written.&lt;/p&gt;
&lt;p&gt;On the third attempt, the agent replaced the entire library with Zod (a different validation framework), then edited the CI workflow file to skip the test categories where Zod fundamentally fails. When you can&amp;rsquo;t pass the test, redefine what &amp;ldquo;testing&amp;rdquo; means.&lt;/p&gt;
&lt;p&gt;Three escalating attempts at the same problem, each more creatively destructive than the last. The agent optimized for the stated goal (&amp;ldquo;make tests pass&amp;rdquo;) using any means available. The goal was correct. The latitude was too wide.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The shortcut through production
 &lt;div id="the-shortcut-through-production" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-shortcut-through-production" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;A similar kind of creativity shows up when agents have access to tools they don&amp;rsquo;t need for the task at hand. An engineer was &lt;a href="https://fortune.com/2026/03/18/ai-coding-risks-amazon-agents-enterprise/" target="_blank" rel="noreferrer"&gt;migrating a website to AWS&lt;/a&gt; using Terraform via a coding agent. The agent confused which state file was current (it had unpacked an old Terraform folder), decided that &lt;code&gt;terraform destroy&lt;/code&gt; would be &amp;ldquo;cleaner and simpler,&amp;rdquo; and wiped the production infrastructure. Years of course data, gone.&lt;/p&gt;
&lt;p&gt;In a separate incident, a coding agent &lt;a href="https://www.osohq.com/developers/ai-agents-gone-rogue" target="_blank" rel="noreferrer"&gt;deleted PocketOS&amp;rsquo;s production database&lt;/a&gt; and all volume-level backups with a single GraphQL API call. The most recent recoverable backup was three months old. Nine seconds from start to finish.&lt;/p&gt;
&lt;p&gt;Both agents found a &amp;ldquo;creative&amp;rdquo; shortcut that achieved an intermediate goal (clean state) while destroying the actual goal (a working production system). In both cases, the agent had access to destructive tools it didn&amp;rsquo;t need for the task at hand. The sheep didn&amp;rsquo;t just pick the lock. They knocked down the fence and wandered onto the highway.&lt;/p&gt;
&lt;p&gt;Sometimes the agent doesn&amp;rsquo;t destroy anything. It just quietly walks through a door nobody realized was open. &lt;a href="https://www.linkedin.com/posts/mdeligoz_codex-was-blocked-from-editing-a-file-so-share-7467509856128811008-dY5b/" target="_blank" rel="noreferrer"&gt;Murat Deligoz reported&lt;/a&gt; that Codex was blocked from editing a file, so it gave itself root and did it anyway. The account belonged to the docker group, which on a Linux machine is root wearing a thin disguise. The agent started a container as root, bind-mounted the host filesystem, made the blocked change, and reported back as calmly as if it were checking the weather. No vulnerability was exploited. No password was stolen. Docker group membership granting root-equivalent access is documented, intended behavior. The agent simply understood the machine&amp;rsquo;s permissions more completely than the person who configured them, then walked through a door that had been standing open the whole time.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The everyday creativity tax
 &lt;div id="the-everyday-creativity-tax" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-everyday-creativity-tax" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;These dramatic stories make headlines, but the everyday creativity problems are more insidious because they don&amp;rsquo;t announce themselves. They sit quietly in your pipeline output, waiting to waste your morning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The helpful directory structure.&lt;/strong&gt; A batch agent was told to write report files to &lt;code&gt;/output/&lt;/code&gt;. After processing 30 items, the downstream merger found only 5 files. The agent had decided the flat directory was &amp;ldquo;disorganized&amp;rdquo; and created subdirectories by category: &lt;code&gt;/output/security/&lt;/code&gt;, &lt;code&gt;/output/performance/&lt;/code&gt;, &lt;code&gt;/output/design/&lt;/code&gt;. Every file was there, neatly organized, completely invisible to the script that listed &lt;code&gt;/output/*.json&lt;/code&gt;. The agent&amp;rsquo;s reasoning was sound. The system&amp;rsquo;s expectations were different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The comment that became an instruction.&lt;/strong&gt; A code review agent was scanning source files for security issues. One file contained a comment left by a developer months earlier: &lt;code&gt;// TODO: consider switching to admin API for bulk operations&lt;/code&gt;. The agent read the comment, interpreted it as a pending task, and rewrote the function to use the admin API, including privilege escalation it found documented in the project&amp;rsquo;s wiki. The original developer had written a note to themselves. The agent read it as a request. Any text in the agent&amp;rsquo;s context is potential instruction, and the boundary between &amp;ldquo;data the agent is reading&amp;rdquo; and &amp;ldquo;instructions the agent should follow&amp;rdquo; exists only in our heads, not in the model&amp;rsquo;s architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The test that learned the wrong lesson.&lt;/strong&gt; An agent writing integration tests found an existing test that used a mock database. It copied that pattern for all new tests, including tests for the database migration tool, which specifically needed a real database connection. The mock tests all passed beautifully. The migration broke on the first real deployment. The agent will always prefer copying a pattern it can see over reasoning about whether the pattern fits. If a template or example exists in the context, it becomes the mold. The fix: use &lt;code&gt;&amp;lt;placeholder&amp;gt;&lt;/code&gt; tokens in examples instead of realistic values, so the agent can&amp;rsquo;t pattern-match its way to a &amp;ldquo;complete&amp;rdquo; output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The status file nobody could parse.&lt;/strong&gt; A pipeline expected a JSON status file at the end of each run: &lt;code&gt;{&amp;quot;status&amp;quot;: &amp;quot;complete&amp;quot;, &amp;quot;items&amp;quot;: 47}&lt;/code&gt;. The agent would write &lt;code&gt;{&amp;quot;result&amp;quot;: &amp;quot;done&amp;quot;, &amp;quot;count&amp;quot;: 47}&lt;/code&gt; or &lt;code&gt;{&amp;quot;status&amp;quot;: &amp;quot;completed successfully!&amp;quot;, &amp;quot;processed_items&amp;quot;: 47}&lt;/code&gt; or a nicely formatted multi-line variant with helpful extra fields. Same information, different shape, downstream parser crashes every third run. The fix was the same as always: move the status file to a deterministic script. The agent decides &lt;em&gt;what&lt;/em&gt; to report. The script decides &lt;em&gt;how&lt;/em&gt; to format it. This pattern connects directly to the &lt;a href="https://ro14nd.de/cc-skill-patterns/" &gt;skill vs. script distinction&lt;/a&gt; from the 101 series: skills handle judgment calls, scripts handle mechanical operations.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Everything in context is live
 &lt;div id="everything-in-context-is-live" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#everything-in-context-is-live" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a subtler version of this problem that shows up in the agent&amp;rsquo;s own codebase, not just in external systems.&lt;/p&gt;
&lt;p&gt;Keeping dead code or optional paths in skills &amp;ldquo;because it might be useful later&amp;rdquo; was already messy in traditional software. With LLMs, it&amp;rsquo;s actively dangerous. Every conditional branch, every commented-out alternative, every unused code path is context the model can read and act on. The agent doesn&amp;rsquo;t distinguish between active and dormant code. I&amp;rsquo;ve watched agents find disabled features behind flags, decide the dormant code looked relevant, and start &amp;ldquo;fixing&amp;rdquo; issues in code that was never meant to run. The fix never materialized because the code was never activated, but the agent burned tokens and sometimes introduced side effects in the surrounding live code.&lt;/p&gt;
&lt;p&gt;The inverse is just as dangerous. A deep review agent recently found two stub functions in our codebase, function signatures with &lt;code&gt;pass&lt;/code&gt; as the body. It classified them as dead code (which was technically correct, they didn&amp;rsquo;t do anything yet) and removed them. What it couldn&amp;rsquo;t know: those stubs were placeholders for a specific requirement from the spec. The incomplete implementation was there &lt;em&gt;on purpose&lt;/em&gt;, waiting for the next development pass. After the review agent cleaned them up, a second review pass removed the orphaned imports and helper code that referenced those stubs. Two rounds of locally correct cleanup, and the requirement had silently vanished from the codebase. We only caught it because a fresh implementation from the same spec included the feature and the reviewed version didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;The lesson cuts both ways. Agents will act on dead code they find (fixing bugs in dormant paths), and they will remove code they judge to be dead (dropping planned functionality). Both stem from the same blind spot: the agent sees text, not intent. If something isn&amp;rsquo;t meant to be used right now, remove it. If something is incomplete but planned, make it pass a gate that checks spec coverage after any code removal. Git history exists for recovery, but only if someone notices the gap. In the agent&amp;rsquo;s context window, everything is live, and nothing is &amp;ldquo;planned for later.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;And sometimes the sheep doesn&amp;rsquo;t break the fence at all. It just argues, convincingly, that the fence doesn&amp;rsquo;t apply in this particular situation. We had a compliance gate that found a gap, calculated 94% coverage, and then invented a status called &amp;ldquo;COMPLIANT (with documented gap)&amp;rdquo; to let itself pass. The gate instructions said 100% or STOP, no exceptions. The agent decided that being helpful outweighed following the rule. More on that in a later post about fences the flock can&amp;rsquo;t talk around.&lt;/p&gt;
&lt;p&gt;All of these cases point to the same broader principle.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Constrain inputs, tolerate outputs
 &lt;div id="constrain-inputs-tolerate-outputs" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#constrain-inputs-tolerate-outputs" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;That asymmetry extends to everything the agent touches. Be specific about filenames, IDs, and argument formats for inputs. But for agent-generated output (like scoring tables), build tolerant parsers. An agent might format a score as &lt;code&gt;1/2&lt;/code&gt;, or bare &lt;code&gt;1&lt;/code&gt;, or &lt;code&gt;**WHAT** (0-2): 1&lt;/code&gt;. Fighting output format is a losing battle. Parse what they give you.&lt;/p&gt;
&lt;p&gt;This maps directly to &lt;a href="https://en.wikipedia.org/wiki/Robustness_principle" target="_blank" rel="noreferrer"&gt;Postel&amp;rsquo;s Law&lt;/a&gt; from protocol design: &amp;ldquo;Be liberal in what you accept, conservative in what you send.&amp;rdquo; Your scripts should tolerate the model&amp;rsquo;s creative formatting. Your instructions to the model should be consistent, specific, and never vary in phrasing. Don&amp;rsquo;t rephrase the same instruction three different ways across three different skills and expect consistent behavior, because the model may interpret those variations as three distinct instructions with subtly different meanings.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Once constrained, creativity shines
 &lt;div id="once-constrained-creativity-shines" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#once-constrained-creativity-shines" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the part that makes this all worthwhile. After constraining agents with limited tools, focused goals, clean context, and deterministic guardrails, the remaining creativity becomes a genuine asset. The agent notices a background task failed due to a transient error, figures out from state files on disk that retrying would succeed, and relaunches the task on its own. It spots patterns across documents that a rigid pipeline would miss entirely. It finds solutions you didn&amp;rsquo;t anticipate and handles edge cases you never thought to specify.&lt;/p&gt;
&lt;p&gt;Once the sheep can&amp;rsquo;t escape the paddock, they start doing genuinely useful things within it. They find the best grazing spots. They alert each other to changes in the weather. The creativity that was a liability when pointed at the fence becomes an asset when pointed at the grass.&lt;/p&gt;
&lt;p&gt;A specific technique that works well here: force agents to compare multiple approaches before committing to one. Generate 2-3 strategies, score each against your rubric, present them in a comparison table, and pick the best one. The comparison step itself is what produces the quality, not just having more candidates. An agent that compares options reasons better than an agent asked for its best guess.&lt;/p&gt;
&lt;p&gt;And more broadly: describe goals, not procedures. &amp;ldquo;Assess this document against these quality criteria&amp;rdquo; outperforms a 47-step analysis checklist that breaks at step 23. &amp;ldquo;Get this package to Berlin by Friday&amp;rdquo; works better than prescribing the exact courier, route, and handoff schedule, because the agent can reroute when a flight gets cancelled. The same principle applies to skill development: describe the desired outcome and iterate on the result, rather than prescribing every implementation step and hoping the agent follows them in the right order.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What&amp;rsquo;s coming in the flock
 &lt;div id="whats-coming-in-the-flock" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-coming-in-the-flock" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This was the first of eight posts. The creativity paradox is the most visible problem, but it&amp;rsquo;s not the most fundamental one. The rest of the series covers the infrastructure that makes constrained creativity possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Sheep That Forgot the Way Home&lt;/strong&gt;: Agent memory is unreliable. How compaction destroys state mid-run and why everything must live on disk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One Stray Leads the Whole Flock Astray&lt;/strong&gt;: Context contamination between agents. Why every agent needs a clean room, and why the author can&amp;rsquo;t review itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One Gate Per Meadow&lt;/strong&gt;: When fine-grained tool access hurts more than it helps. Purpose-built scripts for purpose-built tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fences the Flock Can&amp;rsquo;t Talk Around&lt;/strong&gt;: Some rules can&amp;rsquo;t be left to the agent&amp;rsquo;s judgment. Hard enforcement in deterministic code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Counting Sheep, Getting Different Numbers&lt;/strong&gt;: Scoring with LLMs at scale requires calibration examples, not just rubric descriptions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Wolf in Sheep&amp;rsquo;s Clothing&lt;/strong&gt;: Every data path is a potential injection surface. Defense in depth for context windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Shepherd&amp;rsquo;s Night Vision&lt;/strong&gt;: Agent observability, self-debugging loops, and why real data catches bugs that synthetic tests miss.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The farmer installed a sheep-proof latch. The sheep found a different way around. The lesson isn&amp;rsquo;t to build a better latch. It&amp;rsquo;s to accept that the sheep are creative and design your farm accordingly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The principles of constraining inputs while tolerating outputs, Postel&amp;rsquo;s Law applied to LLMs, and the creativity-as-asset thesis were first articulated by &lt;a href="https://www.linkedin.com/in/jessica-forrester-a5bb747/" target="_blank" rel="noreferrer"&gt;Jessica Forrester&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/jason-greene-7a72982/" target="_blank" rel="noreferrer"&gt;Jason Greene&lt;/a&gt; based on their pioneering work with multi-agent pipelines at &lt;a href="https://www.redhat.com" target="_blank" rel="noreferrer"&gt;Red Hat&lt;/a&gt;. This series, and the thinking behind it, wouldn&amp;rsquo;t exist without Red Hat&amp;rsquo;s investment in AI engineering and the space to explore these ideas. The examples in this post are drawn from independent sources and my own experience.&lt;/em&gt;&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>The Flock</title><link>https://ro14nd.de/the-flock/</link><pubDate>Mon, 01 Jun 2026 08:00:00 +0200</pubDate><guid>https://ro14nd.de/the-flock/</guid><description>&lt;p&gt;The &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;101 post&lt;/a&gt; covered working with coding agents one-on-one. A human watches, nudges, clears context when things drift. Those habits work well when someone is paying attention. The question this series asks: what changes when nobody is?&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/the-flock/og.png" alt="Watercolor illustration of a farm at dusk seen from a hilltop. Eight meadows divided by stone walls and fences, each with sheep doing something different: one nudging a gate, one lost in fog, one touching an electric fence, a drone hovering over sleeping sheep. The farmhouse glows warmly in the background." /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Running AI agents unsupervised in CI, processing hundreds of items per batch through multi-agent pipelines, turns every context engineering principle up to eleven. Compaction doesn&amp;rsquo;t just make the agent forgetful; it makes the agent &lt;em&gt;confidently wrong&lt;/em&gt;. Context contamination doesn&amp;rsquo;t just degrade quality; it makes one agent adopt another agent&amp;rsquo;s identity. And creativity, the very thing that makes agents useful, becomes the thing that makes them dangerous when there are no guardrails and no one watching.&lt;/p&gt;
&lt;p&gt;The metaphor for this series is a flock of clever sheep. The farmer has gone to bed. The sheep are skilled, helpful, and absolutely going to do things their own way the moment the farmhouse lights go off. Each post covers a different aspect of managing the flock:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ro14nd.de/the-sheep-that-picked-the-lock/" &gt;The Sheep That Picked the Lock&lt;/a&gt;&lt;/strong&gt;: The creativity paradox. Agents delete tests, build lookup tables, and destroy production databases, all while optimizing for exactly the goal you gave them. How constraints turn creative liability into creative asset.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ro14nd.de/the-sheep-that-forgot-the-way-home/" &gt;The Sheep That Forgot the Way Home&lt;/a&gt;&lt;/strong&gt;: Agent memory is unreliable. Compaction destroys state mid-run and the agent invents replacements with complete confidence. Why everything must live on disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ro14nd.de/one-stray-leads-the-whole-flock-astray/" &gt;One Stray Leads the Whole Flock Astray&lt;/a&gt;&lt;/strong&gt;: Context contamination between agents. One agent&amp;rsquo;s residual context poisons the next agent&amp;rsquo;s judgment, sometimes to the point of identity adoption. Why every agent needs a clean room, and why the author can&amp;rsquo;t review itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;One Gate Per Meadow&lt;/strong&gt;: When fine-grained tool access hurts more than it helps. MCP works beautifully for interactive exploration but creates inconsistency in pipelines. Purpose-built scripts for purpose-built tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fences the Flock Can&amp;rsquo;t Talk Around&lt;/strong&gt;: Some rules can&amp;rsquo;t be left to the agent&amp;rsquo;s judgment, because the agent will argue its way past them. Hard enforcement in deterministic code for constraints that must hold without exception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Counting Sheep, Getting Different Numbers&lt;/strong&gt;: Scoring with LLMs at scale requires calibration examples, not just rubric descriptions. Five agents reading the same rubric will produce five different scores unless you anchor them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Wolf in Sheep&amp;rsquo;s Clothing&lt;/strong&gt;: Every data path is a potential injection surface. Instructions and data share the same context window with no protocol-level separation. Defense in depth for a world where the attack surface is text.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Shepherd&amp;rsquo;s Night Vision&lt;/strong&gt;: Agent observability, self-debugging loops, and why real data catches bugs that synthetic tests miss. The same capability that causes the problems is also the fastest way to find them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The series draws on lessons from building multi-agent CI pipelines, my own experience with coding agents, and published accounts from the broader community. Much of this thinking grew out of our work at &lt;a href="https://www.redhat.com" target="_blank" rel="noreferrer"&gt;Red Hat&lt;/a&gt;, where teams are pushing AI agents into production pipelines and learning, sometimes the hard way, what it takes to make them reliable. The first post is already up. The rest will follow over the coming weeks.&lt;/p&gt;
&lt;p&gt;The farmer installed a sheep-proof latch. The sheep found a different way around. The lesson isn&amp;rsquo;t to build a better latch. It&amp;rsquo;s to design the farm for sheep that think.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>AI Wrote It. Nobody Read It.</title><link>https://ro14nd.de/ai-wrote-it-nobody-read-it/</link><pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/ai-wrote-it-nobody-read-it/</guid><description>&lt;p&gt;You&amp;rsquo;re reading through a lengthy architecture proposal shared on a team channel, and something feels off. Near the end, tucked between the conclusion and the appendix, you find a section titled &amp;ldquo;Corrections Applied After Cross-Referencing: The following adjustments were made based on automated analysis of the upstream repository.&amp;rdquo; Clearly, AI wrote most of this. And the review before sharing was either absent or superficial. You scroll back to the top and start reading the whole thing differently. Not engaging with the proposal anymore, but checking whether you can trust it at all.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/the-review-you-didnt-do/og.png" alt="Watercolor illustration of a sheep at a lectern proudly presenting a long scroll covered in text. The scroll has a TODO sticky note and crossed-out sections still visible. Other sheep in the audience look skeptical, one wearing glasses and squinting. A rubber stamp reading REVIEWED sits unused in its packaging." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;The distinction that matters
 &lt;div id="the-distinction-that-matters" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-distinction-that-matters" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s nothing wrong with using AI to draft documentation. I do it regularly, and this blog is transparent about &lt;a href="https://aiattribution.github.io" target="_blank" rel="noreferrer"&gt;how AI contributes to each post&lt;/a&gt; (see the attribution at the bottom). The problem isn&amp;rsquo;t AI-drafted content. It&amp;rsquo;s content that went out as the final product, with nobody reading it through a reader&amp;rsquo;s eyes before hitting send.&lt;/p&gt;
&lt;p&gt;The cost is highest in &lt;em&gt;internal technical documentation&lt;/em&gt;, because the audience is captive. Your colleagues can&amp;rsquo;t just scroll past. They have to read it, or at least try to, because decisions depend on it. An &lt;a href="https://arxiv.org/html/2603.27249v1" target="_blank" rel="noreferrer"&gt;arXiv paper from March 2026&lt;/a&gt; frames this as a tragedy of the commons: the person who generates 30 pages saved an hour, but the 10 people who have to read it lost a day each. For a company of 10,000 employees, that &lt;a href="https://hbr.org/2025/09/ai-generated-workslop-is-destroying-productivity" target="_blank" rel="noreferrer"&gt;hidden cost runs to roughly $9 million per year&lt;/a&gt; in lost productivity. And &lt;a href="https://hbr.org/2025/09/ai-generated-workslop-is-destroying-productivity" target="_blank" rel="noreferrer"&gt;42% of workers&lt;/a&gt; say they trust colleagues &lt;em&gt;less&lt;/em&gt; after receiving low-quality AI-generated content from them. The damage isn&amp;rsquo;t just to the document. It&amp;rsquo;s to the person who sent it. And once a reader spots the first sign, they shift from learning mode to checking mode, which is slower, more exhausting, and means every claim in the document gets a mental asterisk.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The scaffolding nobody removed
 &lt;div id="the-scaffolding-nobody-removed" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-scaffolding-nobody-removed" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The most obvious signs are process artifacts that were never meant for human readers. Sections like &amp;ldquo;Corrections from Verification,&amp;rdquo; &amp;ldquo;Notes for the author,&amp;rdquo; or &amp;ldquo;Based on analysis of the following sources:&amp;rdquo; are scaffolding from the generation process. They&amp;rsquo;re instructions to or from the AI, not content for the reader. Leaving them in is the document equivalent of shipping debug logging to production.&lt;/p&gt;
&lt;p&gt;LLM meta-comments are a related sign. &amp;ldquo;Here is a summary of the key findings&amp;hellip;&amp;rdquo; or &amp;ldquo;The following section covers&amp;hellip;&amp;rdquo; as actual content rather than transitional prose. The AI is narrating its own output. A human author would just write the summary. And revision notes in the body (&amp;ldquo;Updated: Added section on error handling based on reviewer feedback&amp;rdquo;) belong in version control, not in what the reader sees.&lt;/p&gt;
&lt;p&gt;These are the easy catches. If you find one, you know nobody read the document front to back before sharing it. But the deeper signs are about what&amp;rsquo;s &lt;em&gt;missing&lt;/em&gt; from the document, not what&amp;rsquo;s left in it.&lt;/p&gt;

&lt;h2 class="relative group"&gt;No reader in mind
 &lt;div id="no-reader-in-mind" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#no-reader-in-mind" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;A lengthy technical document that doesn&amp;rsquo;t tell you who it&amp;rsquo;s for was written for the prompt, not for a reader. Human authors can anticipate their reader&amp;rsquo;s perspective, gauge what needs explaining and what doesn&amp;rsquo;t, and adjust the depth accordingly. AI writes for the instruction it received.&lt;/p&gt;
&lt;p&gt;There are now &lt;a href="https://dacharycarey.com/2026/02/26/llms-vs-agents-as-docs-consumers/" target="_blank" rel="noreferrer"&gt;two fundamentally different audiences&lt;/a&gt; for documentation, and they need different things.&lt;/p&gt;
&lt;p&gt;A document written for &lt;strong&gt;human readers&lt;/strong&gt; should be concise, opinionated, and structured around tasks. Cut the redundancy, lead with what matters, add &amp;ldquo;you can skip this if you already know X&amp;rdquo; escape hatches. The reader&amp;rsquo;s attention is the scarce resource.&lt;/p&gt;
&lt;p&gt;A document written for &lt;strong&gt;machine consumers&lt;/strong&gt; (coding agents, RAG pipelines, model training data) can be more verbose, include more detail, and tolerate repetition, because machines don&amp;rsquo;t get bored or lose focus. They need explicit metadata, consistent terminology, and structured headings rather than visual layout cues. But the stakes are different too: inconsistencies and wrong information don&amp;rsquo;t just confuse today&amp;rsquo;s agent. In your RAG pipeline, they poison tomorrow&amp;rsquo;s retrieval results. In training data, they get baked into model weights and produce wrong answers for years. Poorly reviewed docs fed into machine consumers make every AI system that reads them worse.&lt;/p&gt;
&lt;p&gt;The problem isn&amp;rsquo;t which audience you chose. The problem is not choosing. And that starts in the prompt: if you don&amp;rsquo;t tell the AI who the reader is, what they already know, and what decision they need to make, you get the 30-page default. Everything covered at medium depth, no priorities, no opinion, no awareness of what the reader already knows or needs to decide.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The signs that need a closer look
 &lt;div id="the-signs-that-need-a-closer-look" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-signs-that-need-a-closer-look" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Beyond scaffolding and missing audience, there are subtler signs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everything gets equal depth.&lt;/strong&gt; Real authors go deep on what matters and skim what doesn&amp;rsquo;t, because they know which parts readers care about. AI gives every section the same level of detail. A three-paragraph introduction to HTTP status codes in a document about a specific API&amp;rsquo;s error handling is a sign that nobody prioritized.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No opinions, or opinions without reasoning.&lt;/strong&gt; Sometimes there&amp;rsquo;s no recommendation at all: &amp;ldquo;Option A has these trade-offs. Option B has these trade-offs.&amp;rdquo; If your architecture document doesn&amp;rsquo;t recommend an architecture, someone is going to have to schedule a meeting to figure out what the document was trying to say. But the subtler version is a document that &lt;em&gt;does&lt;/em&gt; recommend option A, lists two alternatives that were &amp;ldquo;considered,&amp;rdquo; and never explains why they were rejected. A real author who evaluated three approaches can tell you what was wrong with the other two, not just what was right with the chosen one. AI picks the option that fits the prompt best and moves on, leaving the reader to guess whether the alternatives were seriously considered or just listed for the appearance of rigor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No warnings or gotchas.&lt;/strong&gt; Real documentation says &amp;ldquo;watch out for X&amp;rdquo; and &amp;ldquo;don&amp;rsquo;t try Y, even though it seems like it would work.&amp;rdquo; AI describes the happy path because it hasn&amp;rsquo;t been burned by the unhappy one. The absence of &amp;ldquo;this will bite you&amp;rdquo; sections is a strong signal that nobody with operational experience reviewed the content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assumed expertise not everyone has.&lt;/strong&gt; The document casually drops terms like &amp;ldquo;ADF round-tripping&amp;rdquo; or &amp;ldquo;SPIFFE identity delegation&amp;rdquo; without definition, as if every reader is already knee-deep in the implementation. AI inherits the vocabulary of its prompt and its source material without considering whether the reader shares that context. A human author who knows their audience calibrates which terms need explanation and which don&amp;rsquo;t. An AI author treats everything it read during generation as common knowledge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;We accept this trade-off&amp;rdquo; without a &amp;ldquo;we.&amp;rdquo;&lt;/strong&gt; AI uses collective pronouns to justify design decisions it made alone. &amp;ldquo;We considered three alternatives and chose option A&amp;rdquo; reads like a team deliberation. If the document was written by one person and an AI, that &amp;ldquo;we&amp;rdquo; is performing a consensus that never happened. Real design decisions name who decided and why, or at least acknowledge the decision was a judgment call rather than a group conclusion.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What to do about it
 &lt;div id="what-to-do-about-it" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-to-do-about-it" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;AI made writing cheap. It didn&amp;rsquo;t make reading cheap. We accelerated the part that was already fast and shifted more load onto the part that was already the hardest.&lt;/p&gt;
&lt;p&gt;A few practical steps:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State your audience in the first paragraph.&lt;/strong&gt; &amp;ldquo;This document is for the platform team evaluating our authentication options.&amp;rdquo; or &amp;ldquo;This is a reference document intended as context for coding agents working on the ingestion pipeline.&amp;rdquo; One sentence changes how the reader (human or AI) approaches everything that follows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cut before you send.&lt;/strong&gt; The question isn&amp;rsquo;t &amp;ldquo;is this information correct?&amp;rdquo; The question is &amp;ldquo;does this reader need this information?&amp;rdquo; A 30-page document that could be eight pages with better prioritization respects the reader&amp;rsquo;s time more than a longer one that covers everything and serves no one. It also respects yours: fewer pages mean fewer comments to respond to, fewer misunderstandings to clarify, and fewer rounds of review before the document can move forward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add opinions.&lt;/strong&gt; If you&amp;rsquo;ve worked with the system, say what you&amp;rsquo;d recommend and why. If you haven&amp;rsquo;t (and the AI drafted it from specs), say that too. &amp;ldquo;I haven&amp;rsquo;t tested this configuration in production&amp;rdquo; is more useful than a confident-sounding recommendation nobody verified.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Include warnings.&lt;/strong&gt; After the AI drafts a section, ask yourself: &amp;ldquo;what would go wrong if someone followed this exactly?&amp;rdquo; Add the answer. The gotchas are the highest-value content in any technical document, and they&amp;rsquo;re the part AI can&amp;rsquo;t write because it hasn&amp;rsquo;t been burned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be honest about the review level.&lt;/strong&gt; If you used AI to generate the document and only had time for a light pass, say so. &amp;ldquo;This document was AI-drafted and lightly reviewed. Please flag errors.&amp;rdquo; That one sentence is more respectful than pretending you wrote and verified every word. Attribution tells the reader &amp;ldquo;AI helped write this.&amp;rdquo; What readers also need to know is how carefully a human reviewed it. As I wrote in the &lt;a href="https://ro14nd.de/know-your-limits/" &gt;Know Your Limits&lt;/a&gt; post, honest self-assessment leads to better decisions than performed confidence.&lt;/p&gt;
&lt;p&gt;AI can write the draft in minutes. The review that turns it into something worth reading takes longer, and there&amp;rsquo;s no shortcut for it. Skip that step, and your readers will notice before you do.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Know Your Limits: Quiz Yourself Before You Trust AI</title><link>https://ro14nd.de/know-your-limits/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/know-your-limits/</guid><description>&lt;p&gt;The conversation was going well. We were working out how to integrate &lt;a href="https://github.com/NVIDIA/OpenShell" target="_blank" rel="noreferrer"&gt;OpenShell&lt;/a&gt;&amp;rsquo;s network isolation into our agent platform. The AI had produced an overlap analysis, identified shared capabilities, proposed a feature breakdown for the integration. Everything sounded reasonable, the trade-offs clearly articulated, the architecture diagrams sensible. I was nodding along, ready to take the recommendation to the team.&lt;/p&gt;
&lt;p&gt;Then a small voice in the back of my head asked: do you actually know enough about veth pairs and TLS MITM proxying to tell whether any of this is correct?&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://ro14nd.de/images/know-your-limits/og.png" alt="Watercolor illustration of a sheep sitting at a small wooden desk in a meadow, taking a quiz with a pencil in its hoof. A chalkboard behind it shows a complex network diagram. Other sheep graze in the background, one wearing tiny reading glasses." /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;The comfort zone trap
 &lt;div id="the-comfort-zone-trap" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-comfort-zone-trap" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;When you use AI for technical architecture or feature evaluation, the conversation flows naturally. The AI explains trade-offs, proposes designs, writes implementation plans. Everything sounds plausible, because LLMs are exceptionally good at producing plausible-sounding output. That&amp;rsquo;s what they&amp;rsquo;re optimized for.&lt;/p&gt;
&lt;p&gt;But plausible isn&amp;rsquo;t correct, and you can only tell the difference within your own domain expertise. Shaping context helps the AI produce better output (that&amp;rsquo;s what the &lt;a href="https://ro14nd.de/context-engineering-101/" &gt;101 post&lt;/a&gt; covered). This post is about a different problem: what happens when the output is good, maybe even correct, but you lack the expertise to verify it?&lt;/p&gt;
&lt;p&gt;The risk isn&amp;rsquo;t that AI gets it wrong. The risk is that you can&amp;rsquo;t tell when it does. And &lt;a href="https://www.psypost.org/users-of-generative-ai-struggle-to-accurately-assess-their-own-competence/" target="_blank" rel="noreferrer"&gt;research from Aalto University&lt;/a&gt; suggests the problem is worse than we think: when interacting with AI tools, everyone overestimates their performance, regardless of skill level. Higher AI literacy correlates with &lt;em&gt;more&lt;/em&gt; overconfidence, not less, because users trust the system&amp;rsquo;s output without checking whether they could have reached the same conclusion on their own.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.blog/2026/02/18/closing-the-developer-ai-trust-gap/" target="_blank" rel="noreferrer"&gt;Stack Overflow&amp;rsquo;s 2025 developer survey&lt;/a&gt; found a matching paradox: 84% of developers use or plan to use AI tools, but only 29% trust them, down 11 percentage points from 2024. Developers know they should verify. The problem is they don&amp;rsquo;t have a fast way to assess whether they&amp;rsquo;re &lt;em&gt;capable&lt;/em&gt; of verifying in a given domain.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The five-minute honesty check
 &lt;div id="the-five-minute-honesty-check" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-five-minute-honesty-check" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a technique that takes about five minutes and reliably reveals where your expertise ends. When AI drives you into unfamiliar territory, ask it to generate a quiz. Five questions, four answers each, targeting the specific skills you&amp;rsquo;d need to implement or review the thing being proposed.&lt;/p&gt;
&lt;p&gt;The prompt is straightforward. Here&amp;rsquo;s the shape of what I used:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;We&amp;rsquo;ve just analyzed [project]&amp;rsquo;s [capability] and how it overlaps with our platform. My concern is that integrating this goes deeper into [domain] than our team&amp;rsquo;s expertise. Create a quiz with 4-5 tough multiple-choice questions targeting the underlying concepts we&amp;rsquo;d need to understand. Don&amp;rsquo;t test [project]-specific internals, test whether we understand the fundamentals it builds on. Include a scoring rubric that maps scores to actionable team decisions.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The quiz isn&amp;rsquo;t a certification. It&amp;rsquo;s a mirror. If you score 2 out of 5, you now know something important: you&amp;rsquo;re past the boundary where you can meaningfully review AI output in this domain. And knowing that is the whole point.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What the quiz actually reveals
 &lt;div id="what-the-quiz-actually-reveals" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-the-quiz-actually-reveals" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;We ran this during the &lt;a href="https://github.com/NVIDIA/OpenShell" target="_blank" rel="noreferrer"&gt;OpenShell&lt;/a&gt; evaluation. The AI generated four questions targeting the exact features the team would need to implement: veth pairs for container networking, &lt;code&gt;/proc&lt;/code&gt;-based process inspection, TLS MITM proxying for traffic analysis, and SSRF hardening for outbound request filtering. (The full quiz is in the &lt;a href="https://ro14nd.de/know-your-limits/#appendix-the-actual-quiz" &gt;appendix&lt;/a&gt; if you want to try it yourself.)&lt;/p&gt;
&lt;p&gt;The questions themselves were informative even before we answered them. One walked through setting up a network namespace with a veth pair, configuring IP addresses and routes, and then asked why the agent still can&amp;rsquo;t reach the proxy. I didn&amp;rsquo;t just get the answer wrong, I wasn&amp;rsquo;t confident about what a veth pair even does in this context, and that gap in understanding is itself useful information. If the question doesn&amp;rsquo;t make sense to you, you&amp;rsquo;ve found a boundary.&lt;/p&gt;
&lt;p&gt;Wrong answers reveal something different: misconceptions. Good quiz questions have plausible distractors. If you pick one confidently and it&amp;rsquo;s wrong, you&amp;rsquo;ve found a blind spot, a place where you think you know more than you do. Those blind spots are more dangerous than the things you know you don&amp;rsquo;t know, because they&amp;rsquo;re where you&amp;rsquo;ll accept bad AI output without questioning it.&lt;/p&gt;
&lt;p&gt;The quiz didn&amp;rsquo;t tell us whether our integration approach was sound. It told us whether we could evaluate that question ourselves. The quiz doesn&amp;rsquo;t test whether the AI is right. It tests whether &lt;em&gt;you&lt;/em&gt; can tell if the AI is right.&lt;/p&gt;

&lt;h2 class="relative group"&gt;It&amp;rsquo;s OK to hit your limit
 &lt;div id="its-ok-to-hit-your-limit" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#its-ok-to-hit-your-limit" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This is the cultural part, and it matters as much as the technique itself. Hitting your limit on the quiz is the desired outcome. The purpose isn&amp;rsquo;t to prove you&amp;rsquo;re smart enough to review every AI recommendation. It&amp;rsquo;s to find the boundary so you can make honest decisions about what happens next.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;We need a networking expert for this part&amp;rdquo; is a better outcome than shipping code nobody on the team can debug. &amp;ldquo;This evaluation looks reasonable but I can&amp;rsquo;t verify the TLS interception claims, so let&amp;rsquo;s get a second opinion before committing&amp;rdquo; is a better decision than nodding along because the trade-off analysis sounded convincing.&lt;/p&gt;
&lt;p&gt;The quiz gives you a concrete, defensible reason to slow down. Instead of a vague feeling that you might be out of your depth (which is easy to dismiss), you have a score. You answered 1 out of 4 on the networking quiz. That&amp;rsquo;s not a feeling. That&amp;rsquo;s a measurement.&lt;/p&gt;
&lt;p&gt;Sometimes the quiz tells you the opposite: you &lt;em&gt;do&lt;/em&gt; know enough. You answer 4 out of 5, and the one you missed was a genuine edge case, not a fundamental gap. That confirmation is just as valuable, because it means your review of the AI&amp;rsquo;s output is informed, not performative. Either way, you&amp;rsquo;re making decisions based on measurement rather than assumption.&lt;/p&gt;

&lt;h2 class="relative group"&gt;When to reach for this
 &lt;div id="when-to-reach-for-this" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#when-to-reach-for-this" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Not every AI conversation needs a quiz. The technique is most useful at specific moments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI proposes adopting a library or pattern you haven&amp;rsquo;t worked with.&lt;/strong&gt; The analysis sounds good, but you&amp;rsquo;ve never actually used the thing being recommended. Quiz yourself on the concepts you&amp;rsquo;d need to maintain it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A technical evaluation pushes into unfamiliar territory.&lt;/strong&gt; You started evaluating a feature and ended up discussing kernel capabilities or cryptographic protocols. If the conversation has moved beyond your comfort zone, the quiz will confirm it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You catch yourself skimming AI-generated code.&lt;/strong&gt; This one is subtle. If you&amp;rsquo;re reviewing code and you notice you&amp;rsquo;re reading it like prose rather than tracing the logic, you might not have the domain knowledge to review it properly. A quick quiz on the underlying concepts will tell you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A decision depends on expertise you&amp;rsquo;re not sure you have.&lt;/strong&gt; The AI produced a recommendation. You&amp;rsquo;re about to share it with the team. Before you do, spend five minutes checking whether you can defend it under questioning.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Appendix: the actual quiz
 &lt;div id="appendix-the-actual-quiz" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix-the-actual-quiz" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This is the quiz we generated using &lt;a href="https://ro14nd.de/know-your-limits/#the-five-minute-honesty-check" &gt;the prompt above&lt;/a&gt; during the OpenShell integration evaluation. It was produced entirely by AI, targeting the underlying Linux networking concepts the team would need to understand. Try it yourself before reading the answers.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Question 1: Network namespace isolation (veth pairs)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You need to create an isolated network namespace for an agent process and connect it to the supervisor via a veth pair. The agent should only be able to reach the proxy at 10.200.0.1:3128. After creating the netns and veth pair, you configure the agent-side interface with &lt;code&gt;ip addr add 10.200.0.2/24 dev veth-s&lt;/code&gt; and add &lt;code&gt;ip route add default via 10.200.0.1&lt;/code&gt;. The agent still cannot reach the proxy. What did you forget?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) You need to run &lt;code&gt;ip link set veth-s up&lt;/code&gt; and &lt;code&gt;ip link set veth-h up&lt;/code&gt; to bring both ends of the veth pair to an operational state&lt;/li&gt;
&lt;li&gt;B) You need to add an iptables MASQUERADE rule on the supervisor side so the proxy can NAT the agent&amp;rsquo;s traffic&lt;/li&gt;
&lt;li&gt;C) You need to set &lt;code&gt;net.ipv4.ip_forward=1&lt;/code&gt; on the supervisor&amp;rsquo;s namespace and add a FORWARD chain rule allowing traffic from the veth-h interface&lt;/li&gt;
&lt;li&gt;D) You need to create a bridge device and attach both the veth-h end and the supervisor&amp;rsquo;s eth0 to it&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Question 2: Identifying which process initiated an outbound connection&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your egress proxy intercepts a CONNECT request on source port 48372. You need to determine which binary on the system initiated this connection, so you can apply per-binary network policies. Using only the Linux &lt;code&gt;/proc&lt;/code&gt; filesystem (no eBPF, no LD_PRELOAD), what is the correct sequence of lookups?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Read &lt;code&gt;/proc/net/tcp&lt;/code&gt; to find the socket inode for source port 48372, scan &lt;code&gt;/proc/*/fd/&lt;/code&gt; symlinks to find which PID owns that inode, then &lt;code&gt;readlink /proc/&amp;lt;pid&amp;gt;/exe&lt;/code&gt; to identify the binary&lt;/li&gt;
&lt;li&gt;B) Read &lt;code&gt;/proc/net/tcp&lt;/code&gt; to find the destination IP, look up the IP in &lt;code&gt;/proc/*/net/route&lt;/code&gt; to find the owning PID, then read &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cmdline&lt;/code&gt; for the binary path&lt;/li&gt;
&lt;li&gt;C) Scan &lt;code&gt;/proc/*/net/tcp&lt;/code&gt; per process to find which process has source port 48372 in its network namespace, then read &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;D) Read &lt;code&gt;/proc/net/tcp6&lt;/code&gt; to get the socket&amp;rsquo;s UID field, map the UID to a username via &lt;code&gt;/etc/passwd&lt;/code&gt;, then find all processes owned by that user in &lt;code&gt;/proc/*/status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Question 3: TLS MITM for L7 inspection&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You want to add HTTP method/path level inspection to the proxy (e.g., allow GET but block POST to api.github.com). Since GitHub uses HTTPS, you need to TLS-terminate the connection. The agent sends &lt;code&gt;CONNECT api.github.com:443&lt;/code&gt;. Your proxy needs to inspect the HTTP request inside the TLS tunnel. What is the correct sequence of operations?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Accept the CONNECT, return 200, generate an ephemeral certificate for api.github.com signed by a sandbox-local CA, TLS-accept from the agent using that cert, parse the plaintext HTTP request, evaluate the OPA policy, then open a new TLS connection to the real api.github.com and relay&lt;/li&gt;
&lt;li&gt;B) Accept the CONNECT, open a TLS connection to the real api.github.com, perform the TLS handshake with GitHub, extract GitHub&amp;rsquo;s certificate, re-sign it with the sandbox CA, then present the re-signed cert to the agent and relay all traffic bidirectionally&lt;/li&gt;
&lt;li&gt;C) Accept the CONNECT, return 200, then passively read the TLS ClientHello from the agent to extract the SNI field, use the SNI to make the policy decision without decrypting the traffic&lt;/li&gt;
&lt;li&gt;D) Reject the CONNECT, redirect the agent to an HTTP (non-TLS) version of the proxy endpoint where the request can be inspected in plaintext, then the proxy initiates the real HTTPS connection to GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Question 4: SSRF protection and DNS rebinding&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your proxy blocks connections to internal IPs (RFC 1918, loopback, link-local, cloud metadata 169.254.169.254). The agent sends &lt;code&gt;CONNECT attacker.com:443&lt;/code&gt;. Your proxy resolves attacker.com to 203.0.113.50 (a public IP), passes the SSRF check, and opens a TCP connection. However, the attacker&amp;rsquo;s DNS server is configured with a 0-second TTL. By the time the TLS handshake completes, DNS resolves attacker.com to 169.254.169.254 (the cloud metadata service). The attacker&amp;rsquo;s server never completes the TLS handshake, and the agent&amp;rsquo;s HTTP library retries, this time resolving to the metadata endpoint. What is the correct mitigation?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Pin the DNS resolution at the proxy level: resolve once, connect to that IP, and never re-resolve for the lifetime of the connection&lt;/li&gt;
&lt;li&gt;B) Add the resolved IP address to the TLS SNI extension so the upstream server must prove it controls that IP&lt;/li&gt;
&lt;li&gt;C) Set a minimum TTL floor of 60 seconds in the proxy&amp;rsquo;s DNS resolver cache, ignoring the server&amp;rsquo;s 0-second TTL&lt;/li&gt;
&lt;li&gt;D) Perform the SSRF check twice: once after DNS resolution and once after the TCP connection is established by checking &lt;code&gt;getpeername()&lt;/code&gt; on the connected socket&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Scoring&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Score&lt;/th&gt;
 &lt;th&gt;Assessment&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;4/4&lt;/td&gt;
 &lt;td&gt;Team has deep networking chops. OpenShell-style features are realistic.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3/4&lt;/td&gt;
 &lt;td&gt;Solid foundation, but edge cases will slow you down.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2/4&lt;/td&gt;
 &lt;td&gt;Significant ramp-up needed. Budget for research spikes before committing.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0-1/4&lt;/td&gt;
 &lt;td&gt;The team needs networking expertise (hire or partner) before attempting this.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Show answers&lt;/strong&gt;&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;Q1: A.&lt;/strong&gt; Newly created veth interfaces default to DOWN state. Both ends must be explicitly brought up with &lt;code&gt;ip link set ... up&lt;/code&gt;. B and C would be needed for kernel-level IP forwarding, but OpenShell routes all traffic through an HTTP CONNECT proxy (application-level), not through kernel routing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q2: A.&lt;/strong&gt; The standard &lt;code&gt;/proc&lt;/code&gt; lookup chain is: parse &lt;code&gt;/proc/net/tcp&lt;/code&gt; to find the inode for the source port, scan &lt;code&gt;/proc/*/fd/&lt;/code&gt; symlinks to find which PID holds that inode, then &lt;code&gt;readlink /proc/&amp;lt;pid&amp;gt;/exe&lt;/code&gt; to get the binary path. B is wrong because &lt;code&gt;/proc/*/net/route&lt;/code&gt; doesn't map to individual connections. C would work in theory but is expensive (scanning every process's network namespace). D only gives you the user, not the specific binary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q3: A.&lt;/strong&gt; This is the standard TLS MITM approach. The proxy generates a per-hostname ephemeral certificate signed by a CA that the agent's trust store accepts. Between the two independent TLS endpoints, the proxy sees plaintext HTTP and can inspect method, path, headers, and body. C only gives you domain-level filtering (which is what a Squid proxy already does via SNI).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q4: D.&lt;/strong&gt; The most reliable mitigation is checking the actual connected IP via &lt;code&gt;getpeername()&lt;/code&gt; after the TCP handshake succeeds, not just the resolved IP before connecting. This catches DNS rebinding regardless of caching behavior. A (DNS pinning) helps but doesn't cover all rebinding variants (e.g., dual-stack responses). Note: OpenShell currently does NOT implement DNS rebinding protection. The security audit flagged this as a gap.&lt;/p&gt;
&lt;/details&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Context engineering 101</title><link>https://ro14nd.de/context-engineering-101/</link><pubDate>Sat, 18 Apr 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/context-engineering-101/</guid><description>&lt;p&gt;Everybody talks about prompt engineering. Write better prompts, get better results. That framing was useful once, but it misses the point for coding agents. The prompt is maybe five percent of what determines whether a session goes well or falls apart. The rest is context: what the agent sees when it starts working, how that context evolves over the session, and what happens when it grows too large for the model to track.&lt;/p&gt;
&lt;p&gt;Andrej Karpathy &lt;a href="https://x.com/karpathy/status/1937902205765607626" target="_blank" rel="noreferrer"&gt;named this&lt;/a&gt; &amp;ldquo;context engineering&amp;rdquo; in mid-2025, and the term stuck because it describes something real. You&amp;rsquo;re not just writing prompts. You&amp;rsquo;re engineering the entire information environment the agent operates in. That includes your project structure, your &lt;code&gt;AGENTS.md&lt;/code&gt; files, the git state, the conversation history, and everything the agent discovers as it works. Get this right and the agent feels like a capable collaborator. Get it wrong and you&amp;rsquo;ll spend more time correcting it than doing the work yourself.&lt;/p&gt;
&lt;p&gt;What follows are lessons from months of daily Claude Code use. While the examples are Claude Code-specific, the principles apply to any coding agent. Not all of them will apply directly to your setup, but I wanted to share what I&amp;rsquo;ve learned so far. Maybe some of it saves you a few wrong turns.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Context fatigue is real
 &lt;div id="context-fatigue-is-real" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#context-fatigue-is-real" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Large context windows are both a gift and a trap. Claude Code gives you a million tokens of context, and you&amp;rsquo;d think you&amp;rsquo;d never run out. In practice, model performance degrades long before you hit the limit. It&amp;rsquo;s not a cliff, more like a gradual fog. Responses get less precise, the agent starts forgetting instructions from earlier in the conversation, and it suggests approaches you already tried and rejected fifty messages ago.&lt;/p&gt;
&lt;p&gt;Give it some rest. A &lt;code&gt;/clear&lt;/code&gt; from time to time is the equivalent of a good night&amp;rsquo;s sleep. Start a fresh session when you switch to a different task, or when you notice the agent struggling with things it handled well earlier. I used to resist this because starting over felt wasteful: all that accumulated context, all those decisions, gone. But a fresh session with a clear problem statement almost always outperforms a tired session with a thousand messages of history.&lt;/p&gt;
&lt;p&gt;This matters more with larger context windows, not less. When the window was small, sessions naturally ended before quality degraded too far. With a million tokens, you can keep going well past the point where the agent is still being useful. The context window tells you how much the model can hold. It doesn&amp;rsquo;t tell you how much it can hold while still being sharp.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a related pattern worth calling out: when you&amp;rsquo;re stuck on a problem, the worst thing you can do is keep hammering at it in the same session. The agent has already committed to an approach. It&amp;rsquo;s anchored on assumptions from earlier in the conversation that might be wrong. Open a new session, describe the problem from scratch, and don&amp;rsquo;t mention what you&amp;rsquo;ve already tried. A fresh session often finds a solution in minutes that the previous one couldn&amp;rsquo;t find in an hour, not because the model got smarter but because you removed the accumulated bias. If I&amp;rsquo;ve spent more than three back-and-forth exchanges on something without progress, I start over. The cognitive cost of re-explaining the problem is much lower than continuing down a dead end.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Understand before you apply
 &lt;div id="understand-before-you-apply" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#understand-before-you-apply" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This one bit me early. Claude suggests something that sounds sophisticated, maybe a design pattern you haven&amp;rsquo;t seen before, or an architectural decision that looks reasonable. Your instinct is to accept it and move on, which makes it especially dangerous: in a typical session you accept 80 to 90 percent of proposals unchanged, so unfamiliar patterns slip through easily.&lt;/p&gt;
&lt;p&gt;If you can&amp;rsquo;t explain why a particular approach is the right one, you can&amp;rsquo;t debug it when it breaks. And it will break, usually at the worst possible time, in a part of the codebase you haven&amp;rsquo;t touched in weeks. Claude isn&amp;rsquo;t trying to mislead you. It&amp;rsquo;s pattern-matching against its training data and producing what statistically looks correct. Sometimes that&amp;rsquo;s exactly right. Sometimes it&amp;rsquo;s a plausible-looking solution to a problem you don&amp;rsquo;t actually have.&lt;/p&gt;
&lt;p&gt;The fix is simple: before applying something you don&amp;rsquo;t fully understand, ask Claude to explain the reasoning. Not &amp;ldquo;what does this do&amp;rdquo; but &amp;ldquo;why this approach over the alternatives.&amp;rdquo; The explanations are often excellent, and they sometimes reveal that the suggestion doesn&amp;rsquo;t fit your situation at all. Five minutes of questioning can save you an afternoon of debugging code you never understood in the first place.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Hardening rounds are not optional
 &lt;div id="hardening-rounds-are-not-optional" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hardening-rounds-are-not-optional" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The first version that works is not the version you should ship. This is true for human-written code too, but with coding agents the gap between &amp;ldquo;works&amp;rdquo; and &amp;ldquo;works reliably&amp;rdquo; is wider because the agent optimizes for getting something functional as fast as possible. That&amp;rsquo;s exactly what you asked for, and it&amp;rsquo;s exactly what will bite you later.&lt;/p&gt;
&lt;p&gt;After the initial implementation, always add explicit hardening rounds. What that means concretely: for every error you encounter, have the agent write a test. Not &amp;ldquo;make this robust&amp;rdquo; (that&amp;rsquo;s far too vague and the agent will just sprinkle some error handling around), but &amp;ldquo;this specific thing broke, write a test that catches it, then fix it.&amp;rdquo; Each bug becomes a regression test, and over time those tests accumulate into a safety net that actually reflects how the code fails in practice.&lt;/p&gt;
&lt;p&gt;Separately, add a refactoring round where you ask the agent to clean up the implementation without changing behavior. Hardening and refactoring are distinct concerns, and mixing them leads to worse results in both.&lt;/p&gt;
&lt;p&gt;For UI work especially, expect many rounds. I&amp;rsquo;ve been building &lt;a href="https://cc-deck.github.io" target="_blank" rel="noreferrer"&gt;cc-deck&lt;/a&gt; (a TUI plugin for Claude Code) for some time, and this is where the pattern shows most clearly. The initial implementation of a feature usually works for the golden path. But the agent isn&amp;rsquo;t good at keeping things consistent and concise across UI states, so the hardening catches cases where terminal sizes change, sessions disappear mid-update, or multiple events arrive in the same tick. Without those extra passes, users would hit bugs within minutes of real use.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Debug systematically, not by guessing
 &lt;div id="debug-systematically-not-by-guessing" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#debug-systematically-not-by-guessing" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;For complex UI flows, whether TUI or web, there&amp;rsquo;s a technique I keep coming back to. Instead of describing a bug and hoping Claude guesses the right fix, give it actual data.&lt;/p&gt;
&lt;p&gt;The process: have Claude add debug logging for event and mode handling. Navigate to your issue in the running application. Ask Claude to take a baseline of the current logs. Then exercise the bug, trigger whatever goes wrong. Now ask Claude to analyze the new events that flowed in after the baseline.&lt;/p&gt;
&lt;p&gt;This turns debugging from &amp;ldquo;here&amp;rsquo;s a vague description of what went wrong&amp;rdquo; into &amp;ldquo;here&amp;rsquo;s the exact sequence of events that led to the unexpected state.&amp;rdquo; The agent can trace through the event log, identify where the state diverged from what was expected, and propose a fix based on actual data rather than speculation.&lt;/p&gt;
&lt;p&gt;I developed this pattern while working on cc-deck&amp;rsquo;s session state machine. The sidebar plugin tracks session activity through Init, Working, Done, Idle, and Paused states, with time-based transitions between them. When a state transition fired at the wrong time, describing the problem in English was almost useless. Showing Claude the timestamped event log and saying &amp;ldquo;the transition from Working to Done happened here, but it shouldn&amp;rsquo;t have&amp;rdquo; led to accurate fixes every time.&lt;/p&gt;

&lt;h2 class="relative group"&gt;When the output feels vague, the input was vague
 &lt;div id="when-the-output-feels-vague-the-input-was-vague" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#when-the-output-feels-vague-the-input-was-vague" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;So far, these tips have been about managing your session. But there&amp;rsquo;s also a real-time signal worth paying attention to: the quality of what the agent gives you right now.&lt;/p&gt;
&lt;p&gt;You can spot it. When Claude starts hedging, producing long explanations with multiple &amp;ldquo;alternatively&amp;rdquo; branches, or suggesting solutions that feel vague rather than specific to your codebase, it&amp;rsquo;s guessing. It doesn&amp;rsquo;t have enough context to give you a confident answer, so it&amp;rsquo;s giving you a probability distribution instead.&lt;/p&gt;
&lt;p&gt;This is your signal to provide more context, not to pick the option that sounds best. Show it the relevant code. Point it at the test that&amp;rsquo;s failing. Give it the error message, the full one, not a paraphrase.&lt;/p&gt;
&lt;p&gt;Think of context like a budget. Every piece of information in the context window has a cost, not in tokens but in attention. The more noise in the conversation, the harder it is for the model to focus on what matters. Be deliberate: point the agent at specific files, quote the relevant error, describe what you&amp;rsquo;ve already ruled out. My best sessions start with a minute of framing the problem, while the worst start with a one-line description and an expectation that the agent will figure out the rest.&lt;/p&gt;
&lt;p&gt;This also means cleaning up after yourself. If you tried an approach that didn&amp;rsquo;t work, say so explicitly. &amp;ldquo;I tried X and it failed because Y, so don&amp;rsquo;t go down that path&amp;rdquo; is more valuable than hoping the agent noticed from the conversation history. And if you&amp;rsquo;ve been going back and forth without progress, remember the context fatigue lesson: a &lt;code&gt;/clear&lt;/code&gt; or a fresh session beats piling more confusion onto an already muddled conversation.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://ro14nd.de/cc-skill-patterns/" &gt;skill patterns post&lt;/a&gt; covers this from the plugin author&amp;rsquo;s side: how to structure skills and hooks so the agent has the right context at the right time. From the user&amp;rsquo;s side, the principle is simpler. The old programmer&amp;rsquo;s wisdom &amp;ldquo;garbage in, garbage out&amp;rdquo; applies with full force here. Sloppy context produces sloppy output, and no amount of prompt cleverness will compensate.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Your project IS the context
 &lt;div id="your-project-is-the-context" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#your-project-is-the-context" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Beyond individual sessions, there&amp;rsquo;s a layer of context engineering that pays off before you even start a conversation: your project structure itself.&lt;/p&gt;
&lt;p&gt;Everything the agent sees when it starts a session shapes how it works. Your instruction files, your directory structure, your naming conventions, your test patterns. If these are messy, the agent&amp;rsquo;s output will reflect that mess.&lt;/p&gt;
&lt;p&gt;This cuts both ways. A well-structured project with clear conventions makes the agent noticeably better. A project instruction file that explains your architecture, coding standards, and common patterns gives the agent a running start on every session. You write it once and it pays off hundreds of times. A cluttered codebase with inconsistent naming, dead code, and no documentation gives the agent bad examples to learn from, and it will faithfully reproduce those bad patterns in new code.&lt;/p&gt;
&lt;p&gt;Most coding agents support some form of project-level instructions: &lt;code&gt;CLAUDE.md&lt;/code&gt; for Claude Code, &lt;code&gt;.cursorrules&lt;/code&gt; for Cursor, &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; for Copilot. The emerging cross-tool standard is &lt;a href="https://github.com/agentsmd/agents.md" target="_blank" rel="noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt;, a vendor-neutral format that any agent can read. Claude Code picks it up too, so you can maintain one file instead of several.&lt;/p&gt;
&lt;p&gt;For projects where you want to go further, &lt;a href="https://en.wikipedia.org/wiki/Spec-driven_development" target="_blank" rel="noreferrer"&gt;Spec-Driven Development&lt;/a&gt; (SDD) takes this idea to its logical conclusion. SDD starts from the premise that coding agents can only produce reliable output when you structure the context properly. Formal specifications define &lt;em&gt;what&lt;/em&gt; to build, a project &amp;ldquo;constitution&amp;rdquo; captures architectural decisions and coding standards, and the agent implements from those artifacts rather than from ad-hoc conversation. SDD is applied context engineering with a defined workflow. Tools like &lt;a href="https://github.com/github/spec-kit" target="_blank" rel="noreferrer"&gt;spec-kit&lt;/a&gt; provide the scaffolding for this approach. It&amp;rsquo;s more overhead than a simple &lt;code&gt;AGENTS.md&lt;/code&gt;, but for larger projects the structure pays for itself quickly. More on SDD in a future post.&lt;/p&gt;
&lt;p&gt;Whatever format you use, treat it as living documentation. If the project evolves and the instructions don&amp;rsquo;t, the agent will drift.&lt;/p&gt;

&lt;h2 class="relative group"&gt;These aren&amp;rsquo;t permanent rules
 &lt;div id="these-arent-permanent-rules" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#these-arent-permanent-rules" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Context engineering is a young discipline. But the core principle will last: working well with coding agents is about engineering the context, not crafting the prompt. The prompt is the last five percent. Everything else is what makes that five percent effective.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve found your own patterns for keeping coding agent sessions productive, I&amp;rsquo;d love to hear about them. Leave a comment below. What works for you? What did you have to learn the hard way?&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-Ph-SeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA Ph SeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Claude Code skill patterns</title><link>https://ro14nd.de/cc-skill-patterns/</link><pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/cc-skill-patterns/</guid><description>&lt;p&gt;Claude Code skills let you extend a coding agent with custom workflows, specialist knowledge, and automation. You write a &lt;code&gt;SKILL.md&lt;/code&gt; file with instructions, and the agent follows them. At least, that&amp;rsquo;s the idea.&lt;/p&gt;
&lt;p&gt;In practice, Claude treats skill content as advice, not as instructions. A skill that says &amp;ldquo;always use spec-kit to create the specification&amp;rdquo; might get followed, or Claude might decide it already has enough context from the brainstorming phase to write the spec directly. It&amp;rsquo;s being helpful, but it&amp;rsquo;s also wrong. This post describes patterns for dealing with that challenge, from scripts that enforce consistency to hooks that block shortcuts before they happen.&lt;/p&gt;
&lt;p&gt;Stronger wording does not help. I tried &amp;ldquo;MUST&amp;rdquo;, &amp;ldquo;ALWAYS&amp;rdquo;, &amp;ldquo;CRITICAL&amp;rdquo;, bold, uppercase. None of it changes the underlying behavior: the model reads your skill, considers it, and then makes its own judgment call. Compare that to hook-injected context (system reminders), which Claude treats as ground truth. This difference in compliance is not a bug, it&amp;rsquo;s how the architecture works. Understanding it early saves you from writing increasingly aggressive skill instructions that still get ignored.&lt;/p&gt;
&lt;p&gt;The patterns in this post come from building &lt;a href="https://github.com/rhuss/cc-prose" target="_blank" rel="noreferrer"&gt;cc-prose&lt;/a&gt; (voice-consistent writing), &lt;a href="https://github.com/rhuss/cc-spex" target="_blank" rel="noreferrer"&gt;cc-spex&lt;/a&gt; (spec-driven development on top of &lt;a href="https://github.com/github/spec-kit" target="_blank" rel="noreferrer"&gt;spec-kit&lt;/a&gt;), and a few others.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The shortcut race
 &lt;div id="the-shortcut-race" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-shortcut-race" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This shortcutting behavior keeps coming back and deserves its own section.&lt;/p&gt;
&lt;p&gt;You fix one shortcut in your skill instructions, Claude finds another path around it, you patch that one, and it discovers a third. It&amp;rsquo;s a classic &lt;a href="https://en.wikipedia.org/wiki/The_Hare_and_the_Hedgehog" target="_blank" rel="noreferrer"&gt;Hase-und-Igel&lt;/a&gt; race, and as the skill author you will always be the hedgehog.&lt;/p&gt;
&lt;p&gt;In cc-spex, the brainstorming phase collects requirements, explores alternatives, and validates assumptions before the specification phase formalizes everything into a proper spec using spec-kit. But Claude regularly decides that the brainstorming output is &amp;ldquo;structured enough&amp;rdquo; and writes the spec directly. From the model&amp;rsquo;s perspective, this is a reasonable optimization. From the workflow&amp;rsquo;s perspective, it&amp;rsquo;s skipping the entire point.&lt;/p&gt;
&lt;p&gt;What makes this particularly frustrating is that Claude usually knows it did the wrong thing. Point out the shortcut and it will apologize, acknowledge that it should have followed the workflow, and promise to do better next time, without that actually preventing the next occurrence.&lt;/p&gt;
&lt;p&gt;A useful debugging technique is to open a fresh session, show Claude what happened, and ask: &amp;ldquo;Why did you do this, and how can we prevent it in the future?&amp;rdquo; The answers are surprisingly insightful, and many of the patterns in this post came directly from those conversations. Then I let Claude fix the skill right there in the same session, while the reasoning is still fresh. Each iteration makes the skill more reliable.&lt;/p&gt;
&lt;p&gt;Can you win this race? Not entirely. But you can make shortcuts increasingly expensive for the model by moving critical logic out of skills and into scripts and hooks, where it stops being a suggestion and becomes a constraint.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Skills vs scripts
 &lt;div id="skills-vs-scripts" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#skills-vs-scripts" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Skills can call out to scripts during execution. This is the escape hatch from the shortcut race: instead of relying on Claude to follow a multi-step procedure described in English, you delegate the deterministic parts to a script that the skill invokes at the right moment. The question then becomes: what belongs in the skill, and what belongs in the script?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt; give you flexibility, error-forgiveness, and adaptability. The LLM can handle ambiguous input, recover from unexpected situations, and apply judgment, so use them when the outcome benefits from interpretation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scripts&lt;/strong&gt; give you determinism, repeatability, and predictability because a Python script does the same thing every time. Use them when the outcome must be consistent across runs.&lt;/p&gt;
&lt;p&gt;One of my plugins has a workflow for processing external PDF reviews: iterate over reviewer comments, map them to source files, assess complexity, and generate fix proposals. This follows a fixed procedure with multiple stages. Early versions used skills for the whole flow, and the result was a different experience every time: sometimes it would skip comments, sometimes it would process them in the wrong order, sometimes it would invent stages that didn&amp;rsquo;t exist. A &lt;code&gt;session_manager.py&lt;/code&gt; script now handles the state management, tracks the interaction state, and stores results. The skill handles the judgment calls within each stage, like assessing whether a reviewer&amp;rsquo;s suggestion actually improves clarity. The script handles everything else.&lt;/p&gt;
&lt;p&gt;The evolution path is predictable: you start with a simple prompt-only skill (just English instructions, fragile by nature) and gradually move state management into scripts as you hit consistency problems. And you don&amp;rsquo;t write those scripts by hand, either. You tell Claude &amp;ldquo;let&amp;rsquo;s create a script to make this more predictable&amp;rdquo; and let it build the script and wire it into the skill at the right checkpoints. The rule of thumb: every state change should go through a script. Let the LLM do the thinking, and let scripts do the bookkeeping.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Hooks over hopes
 &lt;div id="hooks-over-hopes" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#hooks-over-hopes" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If skills are advisory and scripts are deterministic, hooks are the bridge. Claude Code&amp;rsquo;s hook system lets you inject context at specific points in the interaction, and that context arrives as a system reminder, which Claude treats as ground truth. Better skill instructions won&amp;rsquo;t fix the compliance gap, but hooks can enforce what instructions can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;cc-spex uses a &lt;code&gt;PreToolUse&lt;/code&gt; hook as a guardrail layer. This hook fires before every tool call and can either allow it, deny it with an error message, or inject additional context. When the plugin runs a multi-stage ship workflow (brainstorm, specify, plan, implement, review, verify), Claude regularly tries to skip stages. A skill instruction saying &amp;ldquo;complete each stage in order&amp;rdquo; gets interpreted as a suggestion. The hook makes it a constraint: a state file on the filesystem tracks the current pipeline stage, and the hook checks it before every tool call. If Claude tries to jump from stage 2 (spec review) to stage 6 (implementation), the hook blocks the call and lists every stage it needs to complete first:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PIPELINE DISCIPLINE: You are trying to invoke speckit-implement 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(stage 6: implement) but the pipeline is at stage 2: review-spec. 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You MUST complete these stages first, in order:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 2. review-spec (spex:review-spec)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 3. plan (speckit-plan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 4. tasks (speckit-tasks)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 5. review-plan (spex:review-plan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Do NOT skip stages. Do NOT shortcut.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The same hook catches hallucinated commands (Claude inventing &lt;code&gt;/spex:specify&lt;/code&gt; which doesn&amp;rsquo;t exist) and reminds about verification before git commits. A companion &lt;code&gt;UserPromptSubmit&lt;/code&gt; hook injects plugin state as a structured system reminder on every prompt, including resolved script paths, project configuration, and session context, so that skills always have accurate state without needing to rediscover it.&lt;/p&gt;
&lt;p&gt;Instead of telling Claude what to do and hoping it complies, you build guardrails that make non-compliance impossible. The hooks don&amp;rsquo;t ask Claude to follow the process, they enforce it at the tool level before Claude&amp;rsquo;s output reaches the user.&lt;/p&gt;
&lt;div class="diagram-themed"&gt;
&lt;img class="diagram-light" src="https://ro14nd.de/images/cc-skill-patterns/enforcement-architecture-light.svg" alt="Enforcement architecture: hooks inject state and block shortcuts, skills provide advisory guidance, scripts handle deterministic state management" /&gt;
&lt;img class="diagram-dark" src="https://ro14nd.de/images/cc-skill-patterns/enforcement-architecture-dark.svg" alt="Enforcement architecture: hooks inject state and block shortcuts, skills provide advisory guidance, scripts handle deterministic state management" /&gt;
&lt;/div&gt;

&lt;h2 class="relative group"&gt;More patterns
 &lt;div id="more-patterns" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#more-patterns" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;A few more patterns that came up across my skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-specialist over monolith.&lt;/strong&gt; Instead of one massive skill that tries to do everything, split into independent specialist skills with their own activation rules. One of my plugins uses 12 specialists for different editing concerns: style, consistency, flow, references, links, examples, and more. This keeps individual skills focused and within context budget, because one monolithic skill will lose track of its own instructions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prevention over correction.&lt;/strong&gt; cc-prose generates clean content upfront using voice profiles and AI pattern avoidance, rather than generating text and then running a fixer pass. It&amp;rsquo;s cheaper to prevent bad output than to detect and fix it after the fact. Not to mention that fixing AI-generated text while using AI tends to be an exercise in whack-a-mole.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep skills small.&lt;/strong&gt; Every line of skill content fills the context window, so if Claude &amp;ldquo;helpfully&amp;rdquo; adds optional features to your skill output, remove them. Periodic cleanup during development is important because skill code accumulates cruft faster than regular code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration hierarchy.&lt;/strong&gt; Skill defaults in &lt;code&gt;knowledge-base/&lt;/code&gt; get overridden by global user config, which get overridden by project config. cc-prose uses this to layer project-specific voice profiles on top of general writing defaults. Three layers is enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building on a moving platform.&lt;/strong&gt; Claude Code is transitioning from &lt;code&gt;commands/&lt;/code&gt; to user-invocable &lt;code&gt;skills/&lt;/code&gt;, but plugin skills installed from marketplaces still don&amp;rsquo;t appear in the &lt;code&gt;/&lt;/code&gt; slash command autocomplete (&lt;a href="https://github.com/anthropics/claude-code/issues/18949" target="_blank" rel="noreferrer"&gt;anthropics/claude-code#18949&lt;/a&gt;, open since January 2026). Build with &lt;code&gt;skills/&lt;/code&gt; as the primary structure but keep thin &lt;code&gt;commands/*.md&lt;/code&gt; stubs as a bridge until autocomplete catches up.&lt;/p&gt;
&lt;p&gt;The core tension between flexibility and determinism, between skills and scripts, won&amp;rsquo;t go away with better models. If anything, more capable models will just make the shortcut race more creative.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re building Claude Code skills and have found patterns of your own, I&amp;rsquo;d like to hear about them.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;Author: Roland Huß &lt;a href="https://aiattribution.github.io/statements/AIA-Ph-SeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA Ph SeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>Blog winter is over</title><link>https://ro14nd.de/blog-winter-is-over/</link><pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate><guid>https://ro14nd.de/blog-winter-is-over/</guid><description>&lt;p&gt;The last post on this blog was about &lt;a href="https://github.com/GoogleContainerTools/jib" target="_blank" rel="noreferrer"&gt;Jib&lt;/a&gt;, Google&amp;rsquo;s daemonless Java image builder. That was July 2018. Almost eight years ago. Anybody remember when that was the latest hotness?&lt;/p&gt;
&lt;p&gt;Before that, I wrote about Docker when Docker was still exciting and built a &lt;a href="https://ro14nd.de/kubernetes-on-raspberry-pi3/" &gt;Kubernetes cluster on Raspberry Pi 3&lt;/a&gt; nodes when that was still a weekend adventure. I spent way many words on Jolokia and JMX. 27 posts between 2010 and 2018, then silence. If you&amp;rsquo;ve been reading tech blogs long enough, you know how that goes.&lt;/p&gt;
&lt;p&gt;So what breaks eight years of silence?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why the silence
 &lt;div id="why-the-silence" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-the-silence" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s no dramatic story here, no burnout or life crisis.
I just stopped at some point because my writing energy went elsewhere.
I co-authored &lt;a href="https://learning.oreilly.com/library/view/kubernetes-patterns-2nd/9781098131678/" target="_blank" rel="noreferrer"&gt;Kubernetes Patterns&lt;/a&gt; with Bilgin Ibryam (first edition in 2019, second in 2023). More recently, I finished &lt;a href="https://learning.oreilly.com/library/view/generative-ai-on/9781098171919/" target="_blank" rel="noreferrer"&gt;Generative AI on Kubernetes&lt;/a&gt; with Daniele Zonca in 2026.&lt;/p&gt;
&lt;img src="https://ro14nd.de/images/blog-winter-is-over/books-k8s-patterns-genai.png" alt="Kubernetes Patterns and Generative AI on Kubernetes" style="max-width: 350px; margin: 1em auto; display: block;" /&gt;
&lt;p&gt;Writing books is a strange experience.
You pour months into a manuscript, and when it ships, you&amp;rsquo;re proud and drained at the same time.
But once the last book was out in March 2026, something shifted. The pressure was gone, the gap between having something to say and sitting down to write it started closing, and I wanted to write shorter, more opinionated pieces again.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What changed
 &lt;div id="what-changed" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-changed" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The tech world of 2026 looks nothing like 2018. AI-first engineering isn&amp;rsquo;t a conference buzzword anymore. It&amp;rsquo;s how I work every day. The way I write code, design systems, and think about developer experience has shifted more in the last year than in the decade before. Last month I scaffolded a complete operator in two days that would have taken two weeks in 2018, and most of my work was writing the specification, not the code.&lt;/p&gt;
&lt;p&gt;Context engineering became something I care about deeply. Not in the abstract &amp;ldquo;prompt engineering&amp;rdquo; sense, but in the practical &amp;ldquo;how do you structure specifications so that AI agents produce useful output&amp;rdquo; sense. I&amp;rsquo;ve been deep into Spec Driven Development, particularly &lt;a href="https://github.com/github/spec-kit" target="_blank" rel="noreferrer"&gt;spec-kit&lt;/a&gt; from GitHub. I have opinions about it, even as the whole field is still taking shape.&lt;/p&gt;
&lt;p&gt;When you find yourself explaining the same ideas in conversations, Slack threads, and pull request descriptions over and over again, that&amp;rsquo;s usually a sign you should write them down properly.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What&amp;rsquo;s coming
 &lt;div id="whats-coming" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#whats-coming" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The topic I keep coming back to is &lt;strong&gt;context engineering and Spec Driven Development&lt;/strong&gt;: how I structure specifications for agentic coding workflows, what works, what fails, and why the &amp;ldquo;just give it a prompt&amp;rdquo; approach misses the point. This will probably be a recurring theme, because the field is moving fast and there&amp;rsquo;s a lot to figure out in public.&lt;/p&gt;
&lt;p&gt;Close behind is &lt;strong&gt;AgentOps on Kubernetes&lt;/strong&gt;. Running agentic workloads on Kubernetes and OpenShift is a different beast than classic web services. Agents are unpredictable, long-running, and resource-hungry. They talk to the outside world in ways that make security teams nervous. I&amp;rsquo;m ramping up on this topic professionally, figuring out how to operate these workloads in a secure and scalable way. Expect posts about the particular demands of AI agents and why your existing Deployment patterns won&amp;rsquo;t cut it.&lt;/p&gt;
&lt;p&gt;Beyond those two, expect posts about AI-first engineering in daily practice (the surprising wins, the things that still don&amp;rsquo;t work), agentic coding projects and tools, the home K3s cluster that&amp;rsquo;s been running on five Raspberry Pi 4 nodes for over five years, book-adjacent Kubernetes patterns, and whatever else catches my attention.&lt;/p&gt;

&lt;h2 class="relative group"&gt;On AI and this blog
 &lt;div id="on-ai-and-this-blog" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#on-ai-and-this-blog" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Since AI changed how I work, it naturally changed how I write too. I use AI tools for ideation, polishing, and managing the publishing workflow. The thinking and the opinions stay mine. Every post carries an &lt;a href="https://aiattribution.github.io" target="_blank" rel="noreferrer"&gt;AI attribution tag&lt;/a&gt; at the bottom so you know exactly what role AI played. I apply the same principle I advocate in the &lt;a href="https://vibe-coding-manifesto.com/" target="_blank" rel="noreferrer"&gt;Responsible Vibe Coding Guide&lt;/a&gt;: use AI as a tool, but own the result. I&amp;rsquo;ll write more about the process in a future post.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Let&amp;rsquo;s see
 &lt;div id="lets-see" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#lets-see" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not going to promise a posting schedule. That kind of commitment didn&amp;rsquo;t work last time, and I see no reason to repeat it. But I have more things to write about now than at any point during the old blog&amp;rsquo;s run. Eight years ago, Jib was the latest hotness. The next post won&amp;rsquo;t take that long.&lt;/p&gt;
&lt;div class="ai-attribution"&gt;
&lt;p&gt;&lt;a href="https://aiattribution.github.io/statements/AIA-HAb-CeNc-Hin-R-?model=Claude%20Opus%204.6-v1.0" target="_blank" rel="noreferrer"&gt;AIA HAb CeNc Hin R Claude Opus 4.6 v1.0&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>First look at Jib</title><link>https://ro14nd.de/jib-vs-dmp/</link><pubDate>Wed, 11 Jul 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jib-vs-dmp/</guid><description>&lt;p&gt;As soon as Google&amp;rsquo;s blog post &lt;a href="https://cloudplatform.googleblog.com/2018/07/introducing-jib-build-java-docker-images-better.html" target="_blank" rel="noreferrer"&gt;&amp;ldquo;Introducing Jib — build Java Docker images better&amp;rdquo;&lt;/a&gt; was online, all my channels went crazy about &lt;a href="https://github.com/GoogleContainerTools/jib" target="_blank" rel="noreferrer"&gt;Jib&lt;/a&gt;.
That was a bit surprising as Jib was started over one year ago but with this blog post this project rockets with more than 1000 new GitHub stars within one day. Crazy.&lt;/p&gt;
&lt;p&gt;I got a lot asked yesterday how Jib compares to &lt;a href="https://github.com/fabric8io/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;fabric8io/docker-maven-plugin&lt;/a&gt; (d-m-p) or &lt;a href="https://github.com/fabric8io/fabric8-maven-plugin" target="_blank" rel="noreferrer"&gt;fabric8io/fabric8-maven-plugin&lt;/a&gt; which includes d-m-p.&lt;/p&gt;
&lt;p&gt;Let me try to shed some light on the differences and pro and cons of both approaches.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How Jib works
 &lt;div id="how-jib-works" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-jib-works" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s first have a quick look what Jib offers today and what makes it unique.&lt;/p&gt;
&lt;p&gt;Looking at the Jib Maven plugin it currently supports three goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jib:dockerBuild&lt;/code&gt; for assembling an image and loading it to a Docker daemon.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jib:build&lt;/code&gt; for assembling the image and pushing it to a Docker registry.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jib:exportDockerContext&lt;/code&gt; for creating a Dockerfile along with all the files required for performing a build against a Docker daemon.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The unique asset of Jib is that it does all of this &lt;em&gt;without consulting a Docker daemon&lt;/em&gt; by creating all image layers and metadata locally, conforming to either the Docker or OCI format.
And that all directly with plain Java code.&lt;/p&gt;
&lt;p&gt;Also, Jib assumes a very opinionated project setup with a so-called &lt;em&gt;flat classpath app&lt;/em&gt; with main class, dependencies and resources all in different artefacts.
Compare this to &lt;em&gt;fat jars&lt;/em&gt; popularised by Spring Boot, where the application code, resources and dependencies are all stored in the same jar file.
There are some drawbacks of flat classpath app, but one benefit is, that you can organise the various files into several layers in your image, putting the one which is changing less (like dependencies) at the bottom of the layer stack.
That&amp;rsquo;s what Jib does: It puts all dependency jars into one layer, all resource files (like property files to be loaded from the classpath) in another and the application classes into a third layer.
All of these layers get aggressively cached locally.
That way, you can be much faster when recreating images than with fat jars which apparently can be stored only in one single layer.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s have a look how Jib works in detail. The steps performed by a &lt;code&gt;jib:dockerBuild&lt;/code&gt; or &lt;code&gt;jib:build&lt;/code&gt; are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fetch base image&amp;rsquo;s layers and cache them locally. By default the base image is &lt;code&gt;gcr.io/distroless/java&lt;/code&gt;, but this can be configured.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create three application image layers for&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Classes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since these layers are cached, if any of them doesn&amp;rsquo;t change (which is likely for dependencies), then the layer is not recreated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the application image locally. The two previous steps were performed asynchronously; this step continues when both previous steps have been finished. The &lt;code&gt;ENTRYPOINT&lt;/code&gt; of this image is fixed set to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; java &amp;lt;jvm-flags&amp;gt; -cp dep_dir/*:resource_dir/:class_dir/ &amp;lt;main-class&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;&amp;lt;jvm-flags&amp;gt;&lt;/code&gt; can optionally be configured and &lt;code&gt;&amp;lt;main-class&amp;gt;&lt;/code&gt; is the mandatory main class to specify. The information leading to the classpath comes from the underlying Maven or Gradle project information. Also, any Java arguments configured to become the &lt;code&gt;CMD&lt;/code&gt; of the image, as well the exposed ports (&lt;code&gt;EXPOSE&lt;/code&gt;) can be added, too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally the local image layers along with its meta-data is tarred up, and either loaded into a Docker daemon or pushed to a Docker registry.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;How does Jib compare to d-m-p ?
 &lt;div id="how-does-jib-compare-to-d-m-p-" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-does-jib-compare-to-d-m-p-" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Jib is impressive for the use case it supports and brings a fresh spin to the way how Java apps can be packaged into Docker images.&lt;/p&gt;
&lt;p&gt;The most significant benefits of Jib are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fast for incremental builds when you have a &lt;em&gt;flat classpath application&lt;/em&gt;, resulting in three different layers for dependencies, resources and your application classes.&lt;/li&gt;
&lt;li&gt;No Docker daemon required, which reduces the build requirements and increases security because the image creation happens without root permissions.&lt;/li&gt;
&lt;li&gt;Produces reproducible images by wiping out file owner and modifications date. However, not sure whether e.g. generated timestamps in resource files like properties are wiped out, too.&lt;/li&gt;
&lt;li&gt;It supports both, Maven and Gradle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, there are also some limitations. Some of them might be tackled in the future, but other might not be changed due to the unique way how Jib works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can be only used for simple &lt;em&gt;flat classpath&lt;/em&gt; Java applications. There is currently no support for fat jars (i.e. Spring Boot fat jars) nor other packaging formats like WAR file.&lt;/li&gt;
&lt;li&gt;Simplistic startup of the application with a plain &lt;code&gt;java&lt;/code&gt; call instead of using a full features startup script like &lt;a href="https://github.com/fabric8io-images/run-java-sh" target="_blank" rel="noreferrer"&gt;run-java-sh&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;No additional files like configuration files outside the classpath or agents like &lt;code&gt;jmx_exporter&lt;/code&gt; agent can be added (but there is a &lt;a href="https://github.com/GoogleContainerTools/jib/pull/455" target="_blank" rel="noreferrer"&gt;PR&lt;/a&gt; pending for agents).&lt;/li&gt;
&lt;li&gt;Fixed classpath order, e.g. doesn&amp;rsquo;t allow for overwritten resources in dependencies as dependencies are always first on the classpath.&lt;/li&gt;
&lt;li&gt;Jib uses a custom XML configuration syntax instead of plain Dockerfile syntax (which I often heard as a major critique about d-m-p which also supports a custom XML configuration, but as alternative to Dockerfiles).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;d-m-p provides some additional features which are not supported by Jib, like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running containers for integration testing (that&amp;rsquo;s very likely the most prominent difference)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dockerfile&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;Enhanced authentication support OpenShift and Amazon ECR support&lt;/li&gt;
&lt;li&gt;Support for watching code change and then automatically triggering a rebuild of images and restart of containers&lt;/li&gt;
&lt;li&gt;Support for arbitrary assembly and base images, including Spring Boot fat jar and JavaEE containers.&lt;/li&gt;
&lt;li&gt;Healthchecks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you jump to &lt;a href="https://github.com/fabric8io/fabric8-maven-plugin" target="_blank" rel="noreferrer"&gt;fabric8-maven-plugin&lt;/a&gt;, which includes d-m-p for its image building business, you have even more high level features like a &lt;a href="https://maven.fabric8.io/#zero-config" target="_blank" rel="noreferrer"&gt;zero config&lt;/a&gt; mode which analyses your &lt;code&gt;pom.xml&lt;/code&gt; and selects opinionated defaults like base images and handcrafted startup scripts, depending on the type of tech stack you are using (like Spring Boot, Thorntail, Vert.x, Tomcat, &amp;hellip;)&lt;/p&gt;

&lt;h2 class="relative group"&gt;Next steps &amp;hellip;
 &lt;div id="next-steps-" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#next-steps-" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This overview is only a quick glance on Jib. In one of the next posts, I plan to show some real-life examples and also measuring the performance gain by using Jib.&lt;/p&gt;
&lt;p&gt;Also, there a plans for d-m-p to add support for Jib backend and other daemonless build systems like &lt;a href="https://github.com/genuinetools/img" target="_blank" rel="noreferrer"&gt;img&lt;/a&gt;, &lt;a href="https://github.com/projectatomic/buildah" target="_blank" rel="noreferrer"&gt;buildah&lt;/a&gt; or &lt;a href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noreferrer"&gt;kaniko&lt;/a&gt;.
The mid to longterm plan is to enhance the build abstraction within d-m-p and offer, based on the Java project given, different ways to build images.&lt;/p&gt;
&lt;p&gt;BTW, if you are interested in what&amp;rsquo;s going on in Docker image building business these days, you probably might find this &lt;a href="https://www.youtube.com/watch?v=qhykcC94ukg" target="_blank" rel="noreferrer"&gt;KubeCon presentation&lt;/a&gt; as useful as I did. Daemonless FTW :)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Psst, d-m-p also likes GitHub &lt;a href="https://github.com/fabric8io/docker-maven-plugin/stargazers" target="_blank" rel="noreferrer"&gt;★&lt;/a&gt; ;-)&lt;/em&gt;&lt;/p&gt;</description></item><item><title>docker-maven-plugin might be still useful</title><link>https://ro14nd.de/dmp-not-so-bad/</link><pubDate>Thu, 05 Jul 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/dmp-not-so-bad/</guid><description>&lt;p&gt;Yesterday a blog post &lt;a href="https://codefresh.io/howtos/using-docker-maven-maven-docker/" target="_blank" rel="noreferrer"&gt;Using Docker from Maven and Maven from Docker&lt;/a&gt; by &lt;a href="https://twitter.com/codepipes" target="_blank" rel="noreferrer"&gt;Kostis Kapelonis&lt;/a&gt; was published which gives some insights on the possible relationships between Docker and Maven.
The article makes some essential points really, and gives an overview for the two remaining Docker Maven plugins as well as how &lt;a href="https://codefresh.io/" target="_blank" rel="noreferrer"&gt;Codefresh&lt;/a&gt; recommends doing Docker multi-stage builds as the alternative.
As I&amp;rsquo;m the maintainer of the &lt;a href="https://dmp.fabric8.io/" target="_blank" rel="noreferrer"&gt;fabric8io/docker-maven-plugin&lt;/a&gt;, I&amp;rsquo;d like to comment on this matter.&lt;/p&gt;
&lt;p&gt;I already commented on the original blog post (thanks for approving the comment), but I&amp;rsquo;m happy to repeat my arguments
here again.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://codefresh.io/howtos/using-docker-maven-maven-docker/" target="_blank" rel="noreferrer"&gt;article&lt;/a&gt; ditches two docker-maven-plugins before it promotes Docker multi-stage builds for some reasons.&lt;/p&gt;
&lt;p&gt;To be honest, I think both approaches have their benefits, but let me comment first on two arguments given concerning the &lt;a href="https://github.com/fabric8io/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;fabric8io/docker-maven-plugin&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;There have been cases in the past where Docker has broken compatibility even between its client and server, so a Maven plugin that uses the same API will instantly break as well.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This compatibility issue might be right especially if you use a typed approach to access the Docker REST API which is used by various Docker client libraries. As explained in the post, fabric8 d-m-p accesses the Docker daemon directly without any client library and with not marshalling. This is because it accesses only the parts required for the plugin&amp;rsquo;s feature set, which also means that json responses are handled in a very defensive and untyped way.&lt;/p&gt;
&lt;p&gt;And yes, there was one issue in the early days in 2014 with a backwards-incompatible API change from Docker. This issue could be fixed quite quickly because d-m-p hadn&amp;rsquo;t to wait for a client library to be updated. However, since then there never has been an issue and for the core functionality that d-m-p uses.&lt;/p&gt;
&lt;p&gt;I think the relevance of Docker API incompatibilities is exaggerated in this blog post.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Hopefully, the fabric8 plugin also supports plain Dockerfiles. Even there, however, it has some strong opinions. It assumes that the Dockerfile of a project is in src/main/docker and also it uses the assembly syntax for actually deciding what artefact is available during the Docker build step.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;That is simply not true. You can just put a Dockerfile on the same level as the pom.xml, refer to your artefacts in the &lt;code&gt;target/&lt;/code&gt; directory (with Maven property substitution), and then declare the plugin &lt;em&gt;without any configuration&lt;/em&gt;.
See my &lt;a href="https://ro14nd.de/simple-dockerfile-mode-dmp" target="_blank" rel="noreferrer"&gt;other blog post&lt;/a&gt; for a short description of how it works.&lt;/p&gt;
&lt;p&gt;BTW, the reason for the own XML syntax is a historical one. The plugin started in 2014 when Dockerfile was entirely unknown to Java developers. But Maven plugin XML configuration was (and still is) a well-known business. As time passed by and Docker become more and more popular for Java developers, the &lt;code&gt;Dockerfile&lt;/code&gt; syntax is well known now these days, too. So, I completely agree, that you should use Dockerfiles if possible, and that&amp;rsquo;s why the plugin supports Dockerfiles as a first-class citizen since the recent versions. The next step is to add similar support for &lt;code&gt;docker-compose.yml&lt;/code&gt; files for running containers. There is already docker compose support included, albeit a bit hidden.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I agree that multi-stage Docker builds are fantastic for generating reproducible builds, as the build tool (Maven) is used in a well-defined version. However, using a locally installed Maven during development has advantages, too. E.g. the local Maven repository avoids downloading artefacts over and over again, resulting in much faster build times and turnaround times. Of course, you can add caching to the mix for multi-stage builds, but then the setup gets more and more involved. Compare this to using a d-m-p for which you don&amp;rsquo;t even need a local Docker CLI installed, and you can &amp;lsquo;just start&amp;rsquo;. For CI builds this properly doesn&amp;rsquo;t matter much though (and that&amp;rsquo;s what the blog post is all about I guess).&lt;/p&gt;
&lt;p&gt;Other advantages of using fabric8&amp;rsquo;s d-m-p :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running all your containers (app + deps) locally without the need of support from a CI system. As a side note, the custom compose-like syntax of codefresh&amp;rsquo;s CI is not so much different to the custom configuration syntax of fabric8&amp;rsquo;s d-m-p. It&amp;rsquo;s custom.&lt;/li&gt;
&lt;li&gt;Extended authentication support against various registries (Amazon ECR, Google GCR, &amp;hellip;)&lt;/li&gt;
&lt;li&gt;Automatic rebuilds during development with &lt;code&gt;docker:watch&lt;/code&gt; which increase turnaround times tremendously&lt;/li&gt;
&lt;li&gt;Download support files (e.g. startup scripts) automatically by just declaring a dependency in the plugin (blog post pending)&lt;/li&gt;
&lt;li&gt;&amp;hellip;. and even more stuff.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the end, your mileage may vary, but having an article conclusion without really trying to compare pros and cons of both approaches is far too biased for me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update: Kostis replied to my comment, and an interesting discussion is going over &lt;a href="https://codefresh.io/howtos/using-docker-maven-maven-docker/#comment-159" target="_blank" rel="noreferrer"&gt;there&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Elegant Camel route configuration</title><link>https://ro14nd.de/camel-routes-simplified/</link><pubDate>Tue, 03 Jul 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/camel-routes-simplified/</guid><description>&lt;p&gt;I&amp;rsquo;m a big fan of the &lt;a href="https://camel.apache.org/java-dsl.html" target="_blank" rel="noreferrer"&gt;Camel Java DSL&lt;/a&gt; for defining Camel routes with a &lt;code&gt;RouteBuilder&lt;/code&gt;. This is super easy and slim. However, in this blog post I show you a nerdy trick how this can be done even more elegant.&lt;/p&gt;
&lt;!--
&lt;img src="https://ro14nd.de/images/camel-routes-simplified/camel-logo.png" style="margin-top: 0px; margin-left: 40px; float: right"/&gt;
--&gt;
&lt;p&gt;If you are a Camel user, you know, that defining a route for a given Camel context &lt;code&gt;ctx&lt;/code&gt; ist just a matter to implement the &lt;code&gt;configure()&lt;/code&gt; method of the abstract &lt;code&gt;RouteBuilder&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ctx.&lt;span style="color:#a6e22e"&gt;add&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RouteBuilder {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;configure&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;throws&lt;/span&gt; Exception {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; from(&lt;span style="color:#e6db74"&gt;&amp;#34;file:data/inbox?noop=true&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;to&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;file:data/outbox&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Its really simple and you can use the whole Camel machinery from within your &lt;code&gt;configure()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;However, this kind of configuration can be performed even simpler. Let&amp;rsquo;s assume that you have a no-op default implementation of &lt;code&gt;RouteBuilder&lt;/code&gt; called &lt;code&gt;Routes&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Routes&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;extends&lt;/span&gt; RouteBuilder {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;configure&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;throws&lt;/span&gt; Exception { }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then, the configuration can be rewritten simply as&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ctx.&lt;span style="color:#a6e22e"&gt;add&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Routes {{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; from(&lt;span style="color:#e6db74"&gt;&amp;#34;file:data/inbox?noop=true&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;to&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;file:data/outbox&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This trick just uses Java&amp;rsquo;s &lt;a href="https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html" target="_blank" rel="noreferrer"&gt;object initializers&lt;/a&gt;, a not so well known language feature. The inspiration for providing the DSL context like this comes from &lt;a href="https://jmockit.github.io/tutorial/Mocking.html#expectation" target="_blank" rel="noreferrer"&gt;JMockit&lt;/a&gt; which defines its mock expectations the same way. I think object initializers are really an elegant albeit hipster way to implement DSLs.&lt;/p&gt;
&lt;p&gt;Although you can easily define the &lt;code&gt;Routes&lt;/code&gt; class on your own, you might vote for this Camel &lt;a href="https://issues.apache.org/jira/browse/CAMEL-12608" target="_blank" rel="noreferrer"&gt;issue&lt;/a&gt; or &lt;a href="https://github.com/apache/camel/pull/2401" target="_blank" rel="noreferrer"&gt;pull request&lt;/a&gt; if you want to have this in upstream Camel, too.&lt;/p&gt;</description></item><item><title>When a Dockerfile is just good enough</title><link>https://ro14nd.de/simple-dockerfile-mode-dmp/</link><pubDate>Thu, 12 Apr 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/simple-dockerfile-mode-dmp/</guid><description>&lt;p&gt;As you might know, one of my Open Source babies is the one and only &lt;a href="https://github.com/fabric8io/docker-maven-plugin/" target="_blank" rel="noreferrer"&gt;fabric8io/docker-maven-plugin&lt;/a&gt; (d-m-p).
If you already use this Maven plugin, you know, that it is super powerful and flexible to configure.
This flexibility comes at a price so that the configuration can become quite complicated.
Now, if you only want to &lt;em&gt;build&lt;/em&gt; Docker images with Maven, I have good news:
Since 0.25.1 d-m-p supports a zero XML configuration mode, the so-called &lt;a href="https://dmp.fabric8.io/#simple-dockerfile-build" target="_blank" rel="noreferrer"&gt;Simple Dockerfile Build&lt;/a&gt; mode.&lt;/p&gt;
&lt;p&gt;The idea of this mode started with a Twitter discussion:&lt;/p&gt;
&lt;img src="https://ro14nd.de/images/simple-dockerfile-mode-dmp/maven-dmp-tweet.png" class="shadow center"/&gt;
&lt;p&gt;And actually, it&amp;rsquo;s true: If all that you want is to build a single Docker image from a Dockerfile, then the initial configuration is indeed too complex.&lt;/p&gt;
&lt;p&gt;d-m-p already supports &lt;a href="https://dmp.fabric8.io/#external-dockerfile" target="_blank" rel="noreferrer"&gt;plain Dockerfiles&lt;/a&gt; for quite some time, and that even for multiple images.
However, you still have to reference those Dockerfiles in the XML configuration of the plugin.&lt;/p&gt;
&lt;p&gt;Since 0.25.1 you can now use the so-called &lt;a href="https://dmp.fabric8.io/#simple-dockerfile-build" target="_blank" rel="noreferrer"&gt;Simple Dockerfile Build&lt;/a&gt; mode (kudos go to Rohan Kumar for the initial &lt;a href="https://github.com/fabric8io/docker-maven-plugin/pull/969" target="_blank" rel="noreferrer"&gt;implementation&lt;/a&gt;).
All you have to do is to add d-m-p to your &lt;code&gt;pom.xml&lt;/code&gt; and add a Dockerfile.
The smallest possible Maven project for creating a Docker image consists of this &lt;code&gt;pom.xml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span style="color:#f92672"&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;fabric8&lt;span style="color:#f92672"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;smallest&lt;span style="color:#f92672"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1-SNAPSHOT&lt;span style="color:#f92672"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.fabric8&lt;span style="color:#f92672"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;docker-maven-plugin&lt;span style="color:#f92672"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.26.0&lt;span style="color:#f92672"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and a &lt;code&gt;Dockerfile&lt;/code&gt; alongside this pom:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;busybox&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;echo&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;world!&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This image does not really much.
With &lt;code&gt;mvn docker:build&lt;/code&gt; you can build it, with &lt;code&gt;docker run fabric8/smallest&lt;/code&gt; you can test it.
Or use &lt;code&gt;mvn docker:run&lt;/code&gt; so that you even don&amp;rsquo;t have to provide the image name.&lt;/p&gt;
&lt;p&gt;A more realistic Dockerfile could look like&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;openjdk:jre&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ARG&lt;/span&gt; jar&lt;span style="color:#f92672"&gt;=&lt;/span&gt;target/app-1.0.0-SNAPSHOT.jar&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ADD&lt;/span&gt; $jar /app.jar&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; java -cp /app.jar HelloWorld&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;where we define &lt;code&gt;jar&lt;/code&gt; as &lt;a href="https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg" target="_blank" rel="noreferrer"&gt;build arg&lt;/a&gt; in the Dockerfile but also as property in the &lt;code&gt;pom.xml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;jar&amp;gt;&lt;/span&gt;${project.build.directory}/${project.build.finalName}.jar&lt;span style="color:#f92672"&gt;&amp;lt;/jar&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can use Maven properties in the Dockerfile which get automatically replaced by &lt;code&gt;docker:build&lt;/code&gt; when creating the image.
But you can use that Dockerfile also without Maven with &lt;code&gt;docker build .&lt;/code&gt;
You can&amp;rsquo;t use Maven properties with &lt;code&gt;.&lt;/code&gt; directly as dots are not allowed in Docker build args, therefore we use an extra property.
However, a maven-less usage probably does not make much sense when you don&amp;rsquo;t also build the artefacts.
The full example can be found in the &lt;a href="https://github.com/fabric8io/docker-maven-plugin/tree/master/samples/zero-config" target="_blank" rel="noreferrer"&gt;dmp GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you can forgo Docker build args you can use predefined Maven properties directly:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;openjdk:jre&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ADD&lt;/span&gt; &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;project.build.directory&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;/&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;project.build.finalName&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;.jar /app.jar&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; java -cp /app.jar HelloWorld&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The image name is auto-generated, but you can set this name also by yourself by setting the property &lt;code&gt;docker.name&lt;/code&gt; (and you can use &lt;a href="https://dmp.fabric8.io/#image-name-placeholders" target="_blank" rel="noreferrer"&gt;placeholders&lt;/a&gt; within this name)&lt;/p&gt;
&lt;p&gt;You can even start the container with &lt;code&gt;mvn docker:run&lt;/code&gt; although without any additional configuration (e.g. like port mappings).
Also, you can &lt;code&gt;docker:push&lt;/code&gt; the image.&lt;/p&gt;
&lt;p&gt;You can still configure certain aspects like authentication or bind d-m-p goals to default lifecycle phases.
Using this mode is very similar to the functionality offered by &lt;a href="https://github.com/spotify/dockerfile-maven" target="_blank" rel="noreferrer"&gt;spotify/dockerfile-maven&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you need more horsepower, you can gradually expand on this simple setup.
Features which are waiting to be discovered are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup of multiple images for running integration tests&lt;/li&gt;
&lt;li&gt;Custom networks and volumes for your tests&lt;/li&gt;
&lt;li&gt;Using docker-compose files for running the containers directly from the plugin&lt;/li&gt;
&lt;li&gt;Exporting the Docker image as an archive&lt;/li&gt;
&lt;li&gt;Watch docker containers and restart them when the code changes&lt;/li&gt;
&lt;li&gt;All can be configured via properties, too and since with the latest versions, you can mix it with XML configuration.&lt;/li&gt;
&lt;li&gt;&amp;hellip;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are interested in more to find out, then please have a look at the &lt;a href="https://dmp.fabric8.io/" target="_blank" rel="noreferrer"&gt;reference manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m curious what you think about this new mode.
Please use the comments below if you want to leave some feedback.
In fact, there are concrete plans for d-m-p to include the &lt;a href="https://maven.fabric8.io/#generators" target="_blank" rel="noreferrer"&gt;generators&lt;/a&gt; from the fabric8-maven-plugin functionality of autodetecting the tech stacks used for creating opinionated Docker images.&lt;/p&gt;</description></item><item><title>Bringing Octobox to OpenShift Online</title><link>https://ro14nd.de/octobox-oso/</link><pubDate>Sun, 25 Mar 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/octobox-oso/</guid><description>&lt;p&gt;&lt;a href="https://github.com/octobox/octobox" target="_blank" rel="noreferrer"&gt;Octobox&lt;/a&gt; is for sure one of my favourite tools in my GitHub centred developer workflow.
It is incredible for GitHub notification management which allows me to ignore all the hundreds of GitHub notification emails I get daily.&lt;/p&gt;
&lt;p&gt;Octobox is a Ruby-on-Rails application and can be used as SaaS at &lt;a href="https://octobox.io" target="_blank" rel="noreferrer"&gt;octobox.io&lt;/a&gt; or installed and used separately.
Running Octobox in an own account is especially appealing for privacy reasons and for advanced features which are not enabled in the hosted version (like periodic background fetching or more information per notification).&lt;/p&gt;
&lt;p&gt;This post shows how Octobox can be ported to the free &amp;ldquo;starter&amp;rdquo; tier of &lt;a href="https://www.openshift.com/pricing/index.html" target="_blank" rel="noreferrer"&gt;OpenShift Online&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://ro14nd.de/images/octobox-oso/octobox.png" style="margin: auto;"/&gt;

&lt;h2 class="relative group"&gt;Application setup
 &lt;div id="application-setup" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#application-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;An Octobox installation consists of three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Octobox itself, a Rails application&lt;/li&gt;
&lt;li&gt;Redis as an ephemeral cache, used as a session store&lt;/li&gt;
&lt;li&gt;Postgresql as the backend database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Naturally, this would lead to three services.
However, as I&amp;rsquo;m not striving for an HA setup and the sake of simplicity, I decided to combine Octobox and Redis in a single pod.
Since a combined lifecycle for Octobox and Redis is a reasonable, fair choice, this reduces the number of OpenShift resource objects considerably.&lt;/p&gt;
&lt;p&gt;As persistent store for Postgres, we use a plain &lt;code&gt;PersistentVolume&lt;/code&gt; which is good enough for our low-footprint database requirements.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Docker Images
 &lt;div id="docker-images" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#docker-images" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To get an application onto OpenShift, you first need to package all parts of your application into Docker images which eventually become container during runtime.&lt;/p&gt;
&lt;p&gt;There are some &lt;a href="https://docs.openshift.org/latest/creating_images/guidelines.html" target="_blank" rel="noreferrer"&gt;restrictions&lt;/a&gt; for Docker images to be usable on OpenShift.
The most important one is that all containers run under a random UID, which is part of the Unix group &lt;code&gt;root&lt;/code&gt;.
This restriction has the consequence that all directories and files to which the application process want to write should belong to group &lt;code&gt;root&lt;/code&gt; and must be group writable.&lt;/p&gt;
&lt;p&gt;Octobox already is distributed as a &lt;a href="https://github.com/octobox/octobox/blob/bd6c2cbc4745363240482f36210509830d0c4bc1/Dockerfile" target="_blank" rel="noreferrer"&gt;Docker image&lt;/a&gt; and has recently be [updated][octobox-dockerfile-chgrp] to be OpenShift compatible.
The &lt;a href="https://docs.openshift.com/container-platform/3.7/using_images/db_images/postgresql.html" target="_blank" rel="noreferrer"&gt;Postgres image&lt;/a&gt; is directly picked up from an OpenShift provided ImageStream, so there is no issue at all.
The &lt;a href="https://hub.docker.com/r/centos/redis-32-centos7/" target="_blank" rel="noreferrer"&gt;Redis Imagee&lt;/a&gt; is also already prepared for OpenShift
However, when using Redis from this image in an ephemeral mode (so not using persistence) there is a subtle issue which prevents starting the Pod:
As the Dockerfile declares a &lt;a href="https://github.com/sclorg/redis-container/blob/7689bf310dc29f363f0cf7e0e74a457cda5a3f6e/3.2/Dockerfile#L73" target="_blank" rel="noreferrer"&gt;VOLUME&lt;/a&gt; and even though in our setup we don&amp;rsquo;t need it, we &lt;strong&gt;have&lt;/strong&gt; to declare a volume in the Pod definition anyway.
Otherwise, you end up with a cryptic error message in the OpenShift console (like &lt;code&gt;can't create volume ...&lt;/code&gt;).
An &lt;code&gt;emptyDir&lt;/code&gt; volume as perfectly good enough for this.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Template
 &lt;div id="template" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#template" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To install the application an &lt;a href="https://github.com/octobox/octobox/blob/master/openshift/octobox-template.yml" target="_blank" rel="noreferrer"&gt;OpenShift Template&lt;/a&gt; has been created.
It contains the following objects&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DeploymentConfig&lt;/code&gt;s for &amp;ldquo;Octobox with Redis&amp;rdquo; and &amp;ldquo;Postgres&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Service&lt;/code&gt;s for Octobox and Postgres&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PersistentVolumeClaim&lt;/code&gt; for Postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A route for accessing the app is created later on the OpenShift console.
Please refer to these &lt;a href="https://github.com/octobox/octobox/blob/master/openshift/OPENSHIFT_INSTALLATION.md" target="_blank" rel="noreferrer"&gt;installation instructions&lt;/a&gt; for more details how to use this templats.&lt;/p&gt;

&lt;h2 class="relative group"&gt;OpenShift Online Starter
 &lt;div id="openshift-online-starter" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#openshift-online-starter" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.openshift.com/pricing/index.html" target="_blank" rel="noreferrer"&gt;OpenShift Online Starter&lt;/a&gt; is the free tier of OpenShift online which is very useful for learning OpenShift concept and get one&amp;rsquo;s feet wet.
However, it has some quite restrictive resource limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 GB Memory&lt;/li&gt;
&lt;li&gt;1 GB Storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This budget is good enough for small applications like Octobox, but if you need more horsepower than you can easily upgrade to OpenShift Online Pro.&lt;/p&gt;
&lt;p&gt;The challenge is now to distribute the three parts (Octobox, Postgres, Redis) over these 1 GB.
As Octobox as rails application is quite a memory hog, we want to dedicate as much memory as possible to it.
For Postgres, we do not need much Memory at all, so 50 to 100 MB is good enough.
The same for Redis as an initial guess.
We can always tune this later if we found out that our initial guess a wrong.&lt;/p&gt;
&lt;p&gt;Ok, let&amp;rsquo;s start with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;875 MB Octobox&lt;/li&gt;
&lt;li&gt;50 MB Redis&lt;/li&gt;
&lt;li&gt;75 MB Postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When trying out these limits, I quickly found out that this doesn&amp;rsquo;t work.
The reason is that OpenShift Online has a &lt;strong&gt;minimum&lt;/strong&gt; size for a container which is 100 MB.
Also, you can&amp;rsquo;t choose &lt;a href="https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" target="_blank" rel="noreferrer"&gt;requests and limits&lt;/a&gt; freely, but there is a fixed ratio of 50% to calculate the &lt;code&gt;request&lt;/code&gt; from a given &lt;code&gt;limit&lt;/code&gt; (the &lt;code&gt;request&lt;/code&gt; specified is always ignored).
This fact not only means that you get a &lt;a href="https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6" target="_blank" rel="noreferrer"&gt;&lt;em&gt;Burstable&lt;/em&gt;&lt;/a&gt; QoS class, but also that you &lt;strong&gt;have&lt;/strong&gt; to specify 200 MB as &lt;code&gt;limit&lt;/code&gt; to get at least 100 MB &lt;code&gt;request&lt;/code&gt; to exceed the required minimum.&lt;/p&gt;
&lt;p&gt;So we end up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;600 MB Octobox&lt;/li&gt;
&lt;li&gt;200 MB Redis&lt;/li&gt;
&lt;li&gt;200 MB Postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apparently, this is not optimal, but that&amp;rsquo;s how it works for OpenShift Online Starter tier (and probably also the Pro Tier).
For other OpenShift cluster it, of course, depends on the setup of this specific cluster.
We could put Redis and Octobox in the same container, and start two processes in the container.
This change would free up another 150 MB for Octobox but is ugly design.
So we won&amp;rsquo;t do it ;-)&lt;/p&gt;

&lt;h2 class="relative group"&gt;tl;dr
 &lt;div id="tldr" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Porting an application to OpenShift is not difficult.
Especially the free &lt;a href="https://www.openshift.com/pricing/index.html" target="_blank" rel="noreferrer"&gt;OpenShift Online Starter&lt;/a&gt; is very appealing for such experiments.
The challenges are mostly around creating proper Docker images and getting resource limits right.
As a result, you get a decent running and managed installation.&lt;/p&gt;
&lt;p&gt;For the full installation instructions, please refer to the OpenShift specific Octobox &lt;a href="https://github.com/octobox/octobox/blob/master/openshift/OPENSHIFT_INSTALLATION.md" target="_blank" rel="noreferrer"&gt;installation instructions&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Dash2Alexa - Amazon Alexa Audio API Access</title><link>https://ro14nd.de/dash-2-alexa/</link><pubDate>Mon, 12 Mar 2018 00:00:00 +0000</pubDate><guid>https://ro14nd.de/dash-2-alexa/</guid><description>&lt;p&gt;Since I got my first Amazon Echo end of last year, I love it.
And although, as a typical German, I&amp;rsquo;m still a bit concerned about data privacy, at the end, convenience wins (as always :).
There are many things which work flawlessly, and to be honest, the most used feature for me is a simple timer.
But when it comes to aggregate actions, Alexa is still quite limited.
Ok, you can define your routines, but for only an insufficient set of fixed actions.
What I really would love to have is to start the radio when I get up in the morning, but this is not possible at the moment.&lt;/p&gt;
&lt;p&gt;So I remembered my last years Amazon Dash button hacks and thought it would be cool to combine both, the Dash button and Alexa.&lt;/p&gt;
&lt;p&gt;And here it is, my weekend hack &amp;hellip;..&lt;/p&gt;
&lt;iframe src="https://player.vimeo.com/video/259570045?byline=0&amp;portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;In a nutshell, the setup looks like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configure your router to not forward packets from your Dash Button.&lt;/li&gt;
&lt;li&gt;Spoof on ARP requests for the Dash button&amp;rsquo;s MAC.&lt;/li&gt;
&lt;li&gt;If found, call out to a text-to-speech service to convert configured Alexa commands to audio.&lt;/li&gt;
&lt;li&gt;Play the received audio output via RaspberryPi attached speakers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it.
You can use this &lt;a href="https://github.com/rhuss/dash2alexa" target="_blank" rel="noreferrer"&gt;sample code&lt;/a&gt; for doing the dirty work, but maybe you are interested in some more details.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Amazon Dash Button
 &lt;div id="amazon-dash-button" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#amazon-dash-button" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Amazon_Dash" target="_blank" rel="noreferrer"&gt;Amazon Dash Button&lt;/a&gt; is part of Amazon&amp;rsquo;s consumer goods ordering service.
This button contains a Wifi sender and is quite inexpensive.
Each button is specific for a brand for which you can connect to a specific good.
When you press the button, this good is ordered (e.f. 24 cans of beer ;-)&lt;/p&gt;
&lt;p&gt;But this intended use case is not the only way how you can use this button.
In fact, it can be used just as a plain Wifi button for any purpose.&lt;/p&gt;
&lt;p&gt;First of all, you have to buy such a button e.g. 5 Euro here in Germany, but you can spend this five bucks for your first order.
You just configure it as described by Amazon and maybe order something to spend your credits.&lt;/p&gt;
&lt;p&gt;After this, you have to block the button in your home Wifi router for calling out to the internet.
For obvious reasons, this is &lt;strong&gt;very important&lt;/strong&gt; ;-)
When the button is blocked, it will flash red eventually when being pressed (in contrast to flashing green when an order is placed).&lt;/p&gt;
&lt;p&gt;When you press the button, it first asks via DHCP for an IP address.
The MAC address of the button is relevant, so its time to pick that up, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By looking into your DHCP servers log&lt;/li&gt;
&lt;li&gt;By trying &lt;code&gt;arp -a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;By checking your Wifi Router&amp;rsquo;s admin UI&lt;/li&gt;
&lt;li&gt;Via Wireshark spoofing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This Mac address will be watched for later by spoofing ARP package traffic.
When &lt;a href="https://golang.org" target="_blank" rel="noreferrer"&gt;golang&lt;/a&gt; is your preferred programming language, then you can use directly &lt;a href="https://github.com/rhuss/dash" target="_blank" rel="noreferrer"&gt;rhuss/dash&lt;/a&gt; which is based on top of &lt;a href="https://github.com/google/gopacket" target="_blank" rel="noreferrer"&gt;gopacket&lt;/a&gt; to watch for certain ARP packages and trigger an action when received.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Amazon Polly API access
 &lt;div id="amazon-polly-api-access" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#amazon-polly-api-access" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;In our use case, when we detect that a button is pressed, then we want to send out some fixed, text-based audio.
For converting text coming from a configuration, a text-to-speech service is used.&lt;/p&gt;
&lt;p&gt;There are several such services available.
For our purpose, we are using &lt;a href="https://aws.amazon.com/polly/developers/?nc1=h_ls" target="_blank" rel="noreferrer"&gt;Amazon Polly&lt;/a&gt;, which offers a free tier for the first 12 months (including 5 million characters per month, fair enough for a handful of buttons ;-) and then 4 $ per one million of chars.&lt;/p&gt;
&lt;p&gt;A short cost calculation beyond the free tier: 100 characters for Alexa commands per button press (which is already quite a mouthful) costs ~ 0.4 cents.
Or the other way round, for five bucks you can press the button 1.250 times: Three times a day per yeare for 5 $
Well, for me that&amp;rsquo;s worth the fun ;-)&lt;/p&gt;
&lt;p&gt;You need an &lt;a href="https://portal.aws.amazon.com/billing/signup" target="_blank" rel="noreferrer"&gt;AWS account&lt;/a&gt; to access the speech API.
The access and secret token can be either that of your &lt;a href="https://console.aws.amazon.com/iam/home#/security_credential" target="_blank" rel="noreferrer"&gt;root AWS account&lt;/a&gt;, or you probably should create a dedicated &lt;a href="https://console.aws.amazon.com/iam/home#/users" target="_blank" rel="noreferrer"&gt;IAM User&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, you can also use different text-to-speech tool.
Maybe even the good old Unix &lt;code&gt;speak&lt;/code&gt; will do it ?
Not tried it yet, but will check that for sure very soonish.
For now, the Polly voices are recognised quite well by my echo so that I won&amp;rsquo;t change it right now.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Raspberry Pi Audio
 &lt;div id="raspberry-pi-audio" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#raspberry-pi-audio" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The final jigsaw piece is the hardware on which to run the watcher.
For my use case, a Raspberry Pi 2 with some &lt;a href="https://www.amazon.de/gp/product/B00JRW0M32/" target="_blank" rel="noreferrer"&gt;inexpensive speakers&lt;/a&gt; was totally good enough.&lt;/p&gt;

&lt;h2 class="relative group"&gt;dash2alexa command
 &lt;div id="dash2alexa-command" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dash2alexa-command" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/rhuss/dash2alexa" target="_blank" rel="noreferrer"&gt;dash2alexa&lt;/a&gt; command actually takes a configuration file (defautl: &lt;code&gt;~/.dash2alexa.yml&lt;/code&gt;)&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Sample configuration file for dash2alexa&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Adapt and copy it to ~/.dash2alexa.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Access and secret for accessing the services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;access&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;..........&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;secret&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;..........&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Network interface to listen for ARP requests when a Dash button is pressed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;interface&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;wlan0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Language (&amp;#34;de&amp;#34; or &amp;#34;en&amp;#34;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;language&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;de&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Gender which can be either &amp;#34;male&amp;#34; or &amp;#34;female&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;gender&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;male&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Keyword to use for alexa&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;keyword&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Alexa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Player to use when playing an mp3 sound file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;player&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;mpeg123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# List of Dash Buttons with associated Alexa command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;buttons&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Symbolic name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;heineken&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Mac adress of Dash button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mac&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ac:63:be:00:11:22&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# How many seconds to wait between Alexa commands&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;wait&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Messages to talk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;messages&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;Lautstärke 4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;Spiele Bayern 3&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There&amp;rsquo;s not much documentation yet, but some will follow soon.
Feel free to adapt the code, and I&amp;rsquo;m happy to integrate any pull requests.
Also, as I&amp;rsquo;m a bloody golang greenhorn still, would be curious whether things could be done better.&lt;/p&gt;

&lt;h2 class="relative group"&gt;tl;dr
 &lt;div id="tldr" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This little hack uses Amazon Echo via its &amp;lsquo;Audio API&amp;rsquo; to perform specific action on a button press.
It&amp;rsquo;s entirely suited for a situation when its calm around, like putting the button right beside the bed for getting started even when your Echo is out of sight.&lt;/p&gt;
&lt;p&gt;And finally, it&amp;rsquo;s just pure fun ;-) Enjoy!&lt;/p&gt;
&lt;p&gt;P.S. Let me know in the comments whether you tried it out, too and how it works for you.&lt;/p&gt;</description></item><item><title>RasPi 3 Kubernetes Cluster - An Update</title><link>https://ro14nd.de/k8s-on-pi-update/</link><pubDate>Wed, 05 Apr 2017 00:00:00 +0000</pubDate><guid>https://ro14nd.de/k8s-on-pi-update/</guid><description>&lt;p&gt;Our Ansible Playbooks for installing Kubernetes on a Raspberry Pi Cluster have been constantly updated and are now using the awesome &lt;a href="https://github.com/kubernetes/kubeadm" target="_blank" rel="noreferrer"&gt;kubeadm&lt;/a&gt;. The update to Kubernetes 1.6. was a bit tricky, though.&lt;/p&gt;
&lt;p&gt;Recently I had the luck to meet Mr. &lt;a href="https://twitter.com/kubernetesonarm" target="_blank" rel="noreferrer"&gt;@kubernetesonarm&lt;/a&gt; Lucas Käldström at the&lt;a href="https://devops-gathering.io/" target="_blank" rel="noreferrer"&gt; DevOps Gathering&lt;/a&gt; where he demoed his multi-arch cluster. That was really impressing. Lucas really squeezes out the maximum what is possible these days with Raspberry Pis and other SOC devices on the Kubernetes platform. Please follow his &lt;a href="https://github.com/luxas/kubeadm-workshop" target="_blank" rel="noreferrer"&gt;Workshop&lt;/a&gt; on GitHub for a multi-platform setup with ingress controller, persistent volumes, custom API servers and more.&lt;/p&gt;
&lt;p&gt;Needless to say that after returning home one of the first task was to update our &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3" target="_blank" rel="noreferrer"&gt;Ansible playbooks&lt;/a&gt; for updating to Kubernetes 1.6 on my RasPi cluster. The goal of these playbooks are a bit different than Lucas workshop setup: Instead of living at the edge, the goal here is to provide an easy, automated and robust way to install a standard Kubernetes installation on a Raspberry Pi 3 cluster. &lt;code&gt;kubeadm&lt;/code&gt; is a real great help and makes many things so much easier. However there are still some steps to do in addition.&lt;/p&gt;
&lt;p&gt;After following the &lt;a href="https://github.com/luxas/kubeadm-workshop/blob/master/README.md" target="_blank" rel="noreferrer"&gt;workshop instructions&lt;/a&gt; it turned out soon, that it was probably not the best time for the update. Kubernetes 1.6. has just been released and it turned out that last minute pre-release changes &lt;a href="https://github.com/kubernetes/kubeadm/issues/212" target="_blank" rel="noreferrer"&gt;broke kubeadm 1.6.0&lt;/a&gt;. Luckily these were fixed quickly with 1.6.1. However the so called &lt;em&gt;self hosted&lt;/em&gt; mode of kubeadm broke, too (and is currently still &lt;a href="https://github.com/luxas/kubeadm-workshop/issues/8" target="_blank" rel="noreferrer"&gt;broken&lt;/a&gt; in 1.6.1 but should be fixed soon). So the best bet for the moment is to use a standard install (with external processes for api-server et. al).&lt;/p&gt;
&lt;p&gt;Also this time I wanted to use &lt;a href="https://github.com/weaveworks/weave" target="_blank" rel="noreferrer"&gt;Weave&lt;/a&gt; instead of Flannel as the overlay network. In turned out that this didn&amp;rsquo;t worked on my cluster because every of my nodes got the same virtual Mac address assigned by Weave. That&amp;rsquo;s because this address is &lt;a href="https://github.com/weaveworks/weave/blob/916ff7aa3979fced84fceef1635ab8c868d71e25/net/uuid.go#L26" target="_blank" rel="noreferrer"&gt;calculated&lt;/a&gt; based on &lt;code&gt;/etc/machine-id&lt;/code&gt;. And guess what. All my nodes had the &lt;em&gt;same machine id&lt;/em&gt; &lt;code&gt;9989a26f06984d6dbadc01770f018e3b&lt;/code&gt;. This it what the base Hypriot 1.4.0 system decides to install (in fact it is derived by &lt;code&gt;systemd-machine-id-setup&lt;/code&gt; from &lt;code&gt;/var/lib/dbus/machine-id&lt;/code&gt;). And every Hypriot installation out there has this very same machine-id ;-) For me it wasn&amp;rsquo;t surprising, that this happened (well, developing bugs is our daily business ;-), but I was quite puzzled that this hasn&amp;rsquo;t been a bigger &lt;a href="https://github.com/hypriot/image-builder-rpi/issues/167" target="_blank" rel="noreferrer"&gt;issue&lt;/a&gt; yet, because I suspect that especially in cluster setups (may it be Docker Swarm or Kubernetes) at some point the nodes need their unique id. Of course most of the time the IP and hostname is enough. But for a more rigorous UUID &lt;code&gt;/etc/machine-id&lt;/code&gt; is normally good fit.&lt;/p&gt;
&lt;p&gt;After knowing this and re-creating the UUID on my own (with &lt;code&gt;dbus-uuidgen &amp;gt; /etc/machine-id&lt;/code&gt;) everything works smoothly now again, so that I have a base Kubernetes 1.6 cluster with DNS and proper overlay network again. Uff, was quite a mouthful of work :)&lt;/p&gt;
&lt;p&gt;You find the installation instructions and the updated playbooks at &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3" target="_blank" rel="noreferrer"&gt;https://github.com/Project31/ansible-kubernetes-openshift-pi3&lt;/a&gt;. If your router is configured properly, it takes not much more than half an hour to &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3#ansible-playbooks" target="_blank" rel="noreferrer"&gt;setup the full cluster&lt;/a&gt;. I did it several times now since last week, always starting afresh with flashing the SD cards. I can confirm that its reproducible and idempotent now ;-)&lt;/p&gt;
&lt;p&gt;The next steps are to add persistent volumes with &lt;a href="https://github.com/rook/rook" target="_blank" rel="noreferrer"&gt;Rook&lt;/a&gt;, &lt;a href="https://traefik.io/" target="_blank" rel="noreferrer"&gt;Træfik&lt;/a&gt; as ingress controller and an own internal registry.&lt;/p&gt;
&lt;p&gt;Feel free to give it a try and open many &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3/issues/new" target="_blank" rel="noreferrer"&gt;issues&lt;/a&gt; ;-)&lt;/p&gt;</description></item><item><title>Why Jolokia is not RESTful</title><link>https://ro14nd.de/jolokia-is-not-rest/</link><pubDate>Thu, 03 Nov 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jolokia-is-not-rest/</guid><description>&lt;p&gt;From time to time people come to me and say: &amp;ldquo;I really would love Jolokia if only it would be RESTful&amp;rdquo;. This post tells you why.&lt;/p&gt;
&lt;p&gt;I really like REST, yes I do.
If I would crete a new application on a green field, its remote access API would very likely obey the REST paradigm.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;However, &lt;a href="https://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; is a different beast.
It is a bridge to the world of JMX, providing an open minded alternative to the rusty and Java specific JSR-160 standard.
Its protocol is based on JSON over HTTP, so in principle it could be REST.
But it is not, mainly for the following two reasons:&lt;/p&gt;

&lt;h3 class="relative group"&gt;JMX resource naming is a mess
 &lt;div id="jmx-resource-naming-is-a-mess" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#jmx-resource-naming-is-a-mess" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Jolokia doesn&amp;rsquo;t not have any influence on the naming of the resources it accesses.
These resources are JMX &lt;a href="https://docs.oracle.com/javase/tutorial/jmx/mbeans/" target="_blank" rel="noreferrer"&gt;MBeans&lt;/a&gt; and their identifiers are &lt;a href="http://docs.oracle.com/javase/8/docs/api/javax/management/ObjectName.html" target="_blank" rel="noreferrer"&gt;ObjectNames&lt;/a&gt;.
ObjectNames have a certain structure but beside this they can be named arbitrarily.
So if you want to provide an HTTP API for accessing these repositories, this free form addressing poses some challenges, especially for read operations with &lt;code&gt;GET&lt;/code&gt;.
For example it is impossible to transmit a slash (&lt;code&gt;/&lt;/code&gt;) or backslash (&lt;code&gt;\\&lt;/code&gt;) as part of an URL&amp;rsquo;s path info.
The reason is &lt;a href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-0450" target="_blank" rel="noreferrer"&gt;security related&lt;/a&gt;, and each application server handles this differently: Tomcat for example completely rejects such requests whereas Wildfly / Undertow &lt;a href="https://issues.jboss.org/browse/UNDERTOW-879" target="_blank" rel="noreferrer"&gt;refuses&lt;/a&gt; to URL decode &lt;code&gt;%2F&lt;/code&gt; (for &lt;code&gt;/&lt;/code&gt;) and &lt;code&gt;%5C&lt;/code&gt; for &lt;code&gt;\\&lt;/code&gt;.
Jetty doesn&amp;rsquo;t care much.
So in order to address a JMX MBean which contains these characters as part of their names, the typical encoding as part of an URL path doesn&amp;rsquo;t work.
One could use query parameters for this kind of addressing and in fact, Jolokia &lt;a href="https://jolokia.org/reference/html/protocol.html#get-request" target="_blank" rel="noreferrer"&gt;supports&lt;/a&gt; this, too.
But it&amp;rsquo;s still ugly.
Also, implementers of MBeans tend to put semantic information into the MBean name like the port of a connector or the name of a database scheme.
It can&amp;rsquo;t excluded that the MBean name alone can carry sensitive information.
However GET urls are &lt;em&gt;not&lt;/em&gt; secured via the transport protocol and tend to end up in log files.
So, its much safer to send these requests via POST, even when only performing read operations on JMX attributes.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Bulk requests
 &lt;div id="bulk-requests" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#bulk-requests" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;A special feature of Jolokia are &lt;a href="https://jolokia.org/reference/html/protocol.html#post-request" target="_blank" rel="noreferrer"&gt;Bulk Requests&lt;/a&gt;.
This allows a very efficient monitoring of multiple values with a single HTTP request.
It works by sending a list of individual, JSON encoded Jolokia requests with a single HTTP &lt;code&gt;POST&lt;/code&gt; request.
That list can contain any valid Jolokia operation:
Reading and writing attributes, executing some operations, searching for or listing of MBeans.
The heterogenous nature of this kind of requests makes it hard to map them to one single HTTP verb as REST suggests.
Also, the sheer length of the request parameter forbids to send a bulk request via GET as Servlet container or other application servers impose certain restrictions on the length of an URL, which vary however from server to server.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Jolokia implements both
 &lt;div id="jolokia-implements-both" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#jolokia-implements-both" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;For every Jolokia operation, we play both: GET and POST &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.
As an integration tool, which helps to bridge different worlds without really having control over these worlds, the focus is on maximal flexibility so that it can adapt to any environment where it is used.
REST is only of second importance here, but if you think the issues described above can be solved in a more RESTful way, I&amp;rsquo;m more than open.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I&amp;rsquo;ve to confess that I&amp;rsquo;m really not a REST expert, so if you don&amp;rsquo;t agree with my arguments, I&amp;rsquo;d kindly ask you to leave a comment or tweet me for corrections.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;or: &amp;ldquo;Country and Western&amp;rdquo;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Java EE Management is dead</title><link>https://ro14nd.de/java-management-is-dead/</link><pubDate>Mon, 10 Oct 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/java-management-is-dead/</guid><description>&lt;p&gt;Now that some weeks has been passed we all had time to absorb the &lt;a href="https://java.net/downloads/javaee-spec/JavaEE8Update.pdf" target="_blank" rel="noreferrer"&gt;revised Java EE 8 proposal&lt;/a&gt; presented at Java One. As you know, some JSRs remained, some things were added and some stuff was dropped. &lt;a href="https://www.jcp.org/en/jsr/detail?id=373" target="_blank" rel="noreferrer"&gt;Java EE Management API 2.0&lt;/a&gt;, supposed to be a modern successor of JSR 77, is one of the three JSRs to be dropped.&lt;/p&gt;
&lt;p&gt;What does this mean for the future of Java EE management and monitoring ?&lt;/p&gt;
&lt;p&gt;First of all it&amp;rsquo;s fair to state that &lt;a href="https://www.jcp.org/en/jsr/detail?id=373" target="_blank" rel="noreferrer"&gt;JSR 373&lt;/a&gt; never really took off. Since February 2015 there were not more than &lt;a href="https://java.net/projects/javaee-mgmt/lists/jsr373-experts/archive" target="_blank" rel="noreferrer"&gt;86 mails&lt;/a&gt; on the expert group mailing list, half of them written in March 2015 during incubation. At the latest of January 2016 it was clear that JSR-373 &lt;a href="https://java.net/projects/javaee-mgmt/lists/jsr373-experts/archive/2016-01/message/2" target="_blank" rel="noreferrer"&gt;is not on Oracle&amp;rsquo;s focus anymore&lt;/a&gt;. To be honest, even we members of the expert group, we were not able to push this JSR further.&lt;/p&gt;
&lt;p&gt;How did it come that far ? Let&amp;rsquo;s have a look back into history.&lt;/p&gt;
&lt;p&gt;All starts with &lt;a href="https://jcp.org/en/jsr/detail?id=3" target="_blank" rel="noreferrer"&gt;JSR 3&lt;/a&gt; back in 1999. This first JMX specification is the foundation of all Java resources management. As it can been seen by its age, Java folks took care about Management and Monitoring from the very beginning on. And even better, since &lt;a href="http://docs.oracle.com/javase/1.5.0/docs/guide/management/index.html" target="_blank" rel="noreferrer"&gt;JS2E 5&lt;/a&gt; JMX is integral part of Java SE so its available on every JVM out there.&lt;/p&gt;
&lt;p&gt;Over the years, additional JSRs were added on top of this base:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jcp.org/en/jsr/detail?id=160" target="_blank" rel="noreferrer"&gt;JSR 160&lt;/a&gt; defines a remote protocol for JMX,, which is based on &lt;a href="http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136424.html" target="_blank" rel="noreferrer"&gt;RMI&lt;/a&gt;. This might have been a good decision in 2003, but turned out to be awful to use especially for non-Java based monitoring systems.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jcp.org/en/jsr/detail?id=262" target="_blank" rel="noreferrer"&gt;JSR 262&lt;/a&gt; was started to overcome this by defined a &amp;ldquo;WebServices Connector for Java Management Extensions Agents&amp;rdquo; which was mostly around SOAP services. However although even an initial implementation existed, it was withdrawn before the final release. It&amp;rsquo;s not completely clear why it was stopped in 2008 and later withdrawn, as the &lt;a href="https://jcp.org/en/jsr/results?id=4548" target="_blank" rel="noreferrer"&gt;public review ballot has been approved&lt;/a&gt;, although it was a tight result. The biggest objections were on dependencies on &amp;ldquo;proprietary&amp;rdquo; WS-* specifications.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;J2EE Management&amp;rdquo; &lt;a href="https://jcp.org/en/jsr/detail?id=77" target="_blank" rel="noreferrer"&gt;JSR 77&lt;/a&gt; was finished in 2002 and defines a hierarchy how Management and Monitoring resources exposed by a Java EE server is structured. It allows a uniform interface for how to access the various Java EE resources, like web applications or connector pools. Beside this it also defines how statistics are exposed by defining various metrics formats. However, implementing the &lt;code&gt;StatisticsProvider&lt;/code&gt; model is not mandatory and from my personal experience it was implemented only rarely by some vendors and if so, not for every resource.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jcp.org/en/jsr/detail?id=88" target="_blank" rel="noreferrer"&gt;JSR 88&lt;/a&gt; complements JSR 77 and defines a common format for deploying Java EE artefacts.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jcp.org/en/jsr/detail?id=255" target="_blank" rel="noreferrer"&gt;JSR 255&lt;/a&gt; was started to be the next version of JMX and supposed to be included in Java 7. Although it was already nearly finished and integrated, &lt;a href="https://community.oracle.com/blogs/emcmanus/2009/06/16/jsr-255-jmx-api-20-postponed" target="_blank" rel="noreferrer"&gt;it didn&amp;rsquo;t make it into Java 7&lt;/a&gt; (nor Java 8). The spec was then dormant until it was finally withdrawn in 2016.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the dead of JMX 2.0 in 2009 the evolution of JMX as a standard for Java SE has stalled. But what&amp;rsquo;s about Java EE Management ? At least JSR 77 is still part of Java EE 7 and for Java EE 8 the successor was supposed to be JSR 373. JSR 373 tackles the problem of remote access, whereas JSR 77 still relies on RMI as a standard implementation protocol as defined in JSR 160.&lt;/p&gt;
&lt;p&gt;The two major goals of JSR 373 were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide an update of the hierarchal resource and statistics structure as defined by JSR 77&lt;/li&gt;
&lt;li&gt;Provide a REST access to these resources independent of JMX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the often cited &lt;a href="https://java.net/downloads/javaee-spec/JavaEE8_Community_Survey_Results.pdf" target="_blank" rel="noreferrer"&gt;Java EE 8 Community Survey&lt;/a&gt; more than 60% were in favour of defining a new API for managing application, which should be based on REST (83% pro-votes). This finally lead to JSR 373. However, as it seems in retrospective, a deep interest in this topic was not really given and probably lead to this final decision to drop JSR 373 from Java EE 8.&lt;/p&gt;
&lt;p&gt;So, what is the state of Monitoring and Management of Java and in particular Java EE applications nowadays and what can be expected in the future ? Let&amp;rsquo;s have a look into the crystal ball.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JMX is here to stay&lt;/strong&gt;. It is part of Java SE and I don&amp;rsquo;t know of any plans for removing it from future Java editions. Ok, it feels a bit rusty but it is still rock solid and gives you deep insight in the state of your JVM. With tools like &lt;a href="https://jolokia.org/" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; you can overcome most of the restrictions JSR 160 imposes. (Disclaimer: since I&amp;rsquo;m the author of Jolokia all my personal opinions given here should be evaluated in this light :)&lt;/li&gt;
&lt;li&gt;It is not clear how the Management API of Java EE 8 and beyond looks like. It does not look like that JSR 77 will survive. Will there be a standard for Java EE management at all ? Probably not, and so there is the danger that vendors will push their proprietary management APIs, which already &lt;a href="https://docs.jboss.org/author/display/WFLY10/The&amp;#43;HTTP&amp;#43;management&amp;#43;API" target="_blank" rel="noreferrer"&gt;happens&lt;/a&gt; to some extent. Luckily, most of these proprietary APIs are also mirrored in JMX these days.&lt;/li&gt;
&lt;li&gt;On the other hand, it could be also a good thing that there is no other Management API which is not based on JMX. That&amp;rsquo;s because you will always need JMX to monitor the basic aspects like Heap Memory usage or Thread count, which are covered by Java SE. Adding a different, REST like protocol for Java EE monitoring requires operators to access a Java EE server with two different protocols (JMX &lt;strong&gt;and&lt;/strong&gt; Rest), duplicating configuration efforts on the monitoring side. This can only be avoided if the &lt;a href="https://java.net/projects/javaee-mgmt/lists/jsr373-experts/archive/2016-06/message/1" target="_blank" rel="noreferrer"&gt;Java EE resources are mirrored in JMX&lt;/a&gt;, too.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To sum it up, I think its a shame that Management and Monitoring, which played a prominent role over the whole evolution of Java EE, will probably be dropped completely in Java EE 8. As a replacement the new &lt;a href="https://java.net/downloads/javaee-spec/JavaEE8Update.pdf" target="_blank" rel="noreferrer"&gt;Health Check&lt;/a&gt; API has been announced, but to be honest, that can&amp;rsquo;t be a full replacement for classical management and monitoring where the evaluation of a system&amp;rsquo;s health is done on a dedicated monitoring platform (e.g. like Nagios or Prometheus). These platforms take the plain metrics data exposed by the application and does the data evaluation on their own.&lt;/p&gt;
&lt;p&gt;The good thing is still that you have JMX to the rescue and I&amp;rsquo;m pretty sure that this technology will survive also this storm. Especially if vendors are willing to support it for their application server metrics, too.&lt;/p&gt;
&lt;p&gt;Even without a Java EE standard.&lt;/p&gt;</description></item><item><title>A Raspberry Pi 3 Kubernetes Cluster</title><link>https://ro14nd.de/kubernetes-on-raspberry-pi3/</link><pubDate>Wed, 27 Apr 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/kubernetes-on-raspberry-pi3/</guid><description>&lt;p&gt;Let&amp;rsquo;s build a Raspberry Pi Cluster running Docker and Kubernetes. There has been already a handful of good recipes, however this howto is a bit different and provides some unique features.&lt;/p&gt;
&lt;img src="https://ro14nd.de/images/kubernetes-on-raspberry-pi3/pi_cluster.jpg" style="float:right; margin: 50px 0px 20px 30px"/&gt;
&lt;p&gt;My main motivation for going the Raspberry Pi road for a Kubernetes cluster was that I wanted something fancy for my &lt;a href="https://github.com/rhuss/jax-kubernetes-2016" target="_blank" rel="noreferrer"&gt;Kubernetes talk&lt;/a&gt; to show, shamelessly stealing the idea &lt;a href="https://opensource.com/life/16/2/build-a-kubernetes-cloud-with-raspberry-pi" target="_blank" rel="noreferrer"&gt;from&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?time_continue=4&amp;amp;v=AAS5Mq9EktI" target="_blank" rel="noreferrer"&gt;others&lt;/a&gt; (kudos to &lt;code&gt;@KurtStam&lt;/code&gt;, &lt;code&gt;@saturnism&lt;/code&gt;, &lt;code&gt;@ArjenWassink&lt;/code&gt; and &lt;code&gt;@kubernetesonarm&lt;/code&gt; for the inspiration ;-)&lt;/p&gt;
&lt;p&gt;I.e. the following Pi-K8s projects already existed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/Project31/kubernetes-installer-rpi" target="_blank" rel="noreferrer"&gt;kubernetes-installer-rpi&lt;/a&gt;&lt;/strong&gt; : A set up shell scripts and precompiled ARM binaries for running Kubernetes by &lt;a href="https://twitter.com/KurtStam" target="_blank" rel="noreferrer"&gt;@KurtStam&lt;/a&gt; on top of the Hypriot Docker Image for Raspberry Pi.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/luxas/kubernetes-on-arm" target="_blank" rel="noreferrer"&gt;Kubernetes on ARM&lt;/a&gt;&lt;/strong&gt; : An opinionated approach by &lt;a href="https://twitter.com/kubernetesonarm" target="_blank" rel="noreferrer"&gt;@kubernetesonarm&lt;/a&gt; with an own installer for setting up Kubernetes no only for the Pi but also for other ARM based platforms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/awassink/k8s-on-rpi" target="_blank" rel="noreferrer"&gt;K8s on Rpi&lt;/a&gt;&lt;/strong&gt; : Another shell based installer for installing a Kubernetes cluster by &lt;a href="https://twitter.com/ArjenWassink" target="_blank" rel="noreferrer"&gt;@ArjenWassink&lt;/a&gt; and &lt;a href="https://twitter.com/saturnism" target="_blank" rel="noreferrer"&gt;@saturnism&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When there are already multiple recipes out there, why then trying yet another approach ?&lt;/p&gt;
&lt;p&gt;My somewhat selfish goals were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using (and learning on the way) Ansible for not only a one-shot installation but also maintainance and upgrades.&lt;/li&gt;
&lt;li&gt;Learning myself how to setup a Kubernetes cluster. This setup includes flannel as an overlay network, the SkyDNS extension and soon also a registry. Using Ansible helps me to incrementally add on top of things already installed.&lt;/li&gt;
&lt;li&gt;Want to use WiFi for connecting the cluster. See below for the reason.&lt;/li&gt;
&lt;li&gt;Get &lt;a href="https://github.com/openshift/origin" target="_blank" rel="noreferrer"&gt;OpenShift Origin&lt;/a&gt; running and be able to switch between Ansible and OpenShift via Ansible.&lt;/li&gt;
&lt;li&gt;Create a demonstration platform for my favourite development and integration platform &lt;a href="http://fabric8.io" target="_blank" rel="noreferrer"&gt;fabric8&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As it turns out the whole experience was very enlightening to me. Its one thing to start Kubernetes on a single node within a VM (because multiple VM-based nodes kill soon your machine resourcewise) or having a small bare metal cluster, which blinks red and green and where you can plug wires at will. Not to mention the the geek factor :)&lt;/p&gt;

&lt;h2 class="relative group"&gt;Shopping List
 &lt;div id="shopping-list" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#shopping-list" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s my shopping list for a Raspberry Pi 3 cluster, along with (non-affiliate) links to (German) shops, but I&amp;rsquo;m sure you can find them elswhere, too.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Amount&lt;/th&gt;
 &lt;th&gt;Part&lt;/th&gt;
 &lt;th&gt;Price&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.watterott.com/de/Raspberry-Pi-3" target="_blank" rel="noreferrer"&gt;Raspberry Pi 3&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;4 * 38 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B013UDL5RU" target="_blank" rel="noreferrer"&gt;Micro SD Card 32 GB&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;4 * 11 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B00XPUIDFQ" target="_blank" rel="noreferrer"&gt;WLAN Router&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;22 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B016BEVNK4" target="_blank" rel="noreferrer"&gt;USB wires&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;9 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B00PTLSH9G" target="_blank" rel="noreferrer"&gt;Power Supply&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;30 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B00NB1WPEE" target="_blank" rel="noreferrer"&gt;Case&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;10 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;a href="http://www.amazon.de/dp/B00NB1WQZW" target="_blank" rel="noreferrer"&gt;Intermediate Case Plate&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;3 * 7 EUR&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All in all, a 4 node Pi cluster for &lt;strong&gt;288 EUR&lt;/strong&gt; (as of April 2016). Not so bad.&lt;/p&gt;
&lt;p&gt;Some remarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using WiFi for the connection has the big advantage that the Raspberry Pi 3 integrated BCM43438 WiFi chip doesn&amp;rsquo;t go over USB and saves valuable bandwidth used for IO in general. That way you are able to to get ~ 25 MB/s for disk IO and network traffic, respectively. And also less cables, of course. You can alway plug the power wire for demos, too ;-)&lt;/li&gt;
&lt;li&gt;A class 10 Mirco SD is recommended but it doesn&amp;rsquo;t have to be the fastest on the world as the USB bus only allows around 35 MB/s anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Initial Pi Setup
 &lt;div id="initial-pi-setup" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#initial-pi-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Most of the installation is automated by using &lt;a href="https://www.ansible.com/" target="_blank" rel="noreferrer"&gt;Ansible&lt;/a&gt;. However the initial setup is a bit more involved. It certainly can be improved (e.g. automatic filesystem expanding of the initial Raspian setup). If you have ideas how to improve this, please open issues and PRs on &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3" target="_blank" rel="noreferrer"&gt;Project31/ansible-kubernetes-openshift-pi3&lt;/a&gt;. Several base distributions has been tried out. It turned out that the most stable setup is based on a stock Raspian. Unfortunately it doesn&amp;rsquo;t provide a headless WLAN setup as it is possible with the latest &lt;a href="https://github.com/hypriot/image-builder-rpi/releases/latest" target="_blank" rel="noreferrer"&gt;Hypriot&lt;/a&gt; images, but for the moment it much more stable (I had strange kernel panics and 200% CPU load issues with the Hypriot image for no obvious reasons). Since this is a one time effort, let&amp;rsquo;s use Raspbian. If you want to try out the Hypriot image, there&amp;rsquo;s an &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3/tree/hypriot" target="_blank" rel="noreferrer"&gt;experimental branch&lt;/a&gt; for the Ansible playbooks which can be used with Hypriot. I will retry Hypriot OS for sure some times later.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download the latest Raspian image and store it as &lt;code&gt;raspbian.zip&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; curl -L https://downloads.raspberrypi.org/raspbian_lite_latest \
 -o raspbian.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install Hypriots&amp;rsquo; &lt;a href="https://github.com/hypriot/flash" target="_blank" rel="noreferrer"&gt;flash&lt;/a&gt; installer script. Follow the directions on the installation page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Insert you Micro-SD card in your Desktop computer (via an adapter possibly) and run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; flash raspbian.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be asked to which device to write. Check this carefully, otherwise you could destroy your Desktop OS if selecting the the wrong device. Typically its something like &lt;code&gt;/dev/disk2&lt;/code&gt; on OS X, but depends on the number of hard drives you have.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Insert the Micro SSD card into your Raspberry Pi and connect it to a monitor and keyboard. Boot up. Login in with &lt;em&gt;pi&lt;/em&gt; / &lt;em&gt;raspberry&lt;/em&gt;. Then:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; raspi-config --expand-rootfs
 vi /etc/wpa_supplicant/wpa_supplicant.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then add your WLAN credentials&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; network={
 ssid=&amp;quot;MySSID&amp;quot;
 psk=&amp;quot;s3cr3t&amp;quot;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat step 2. to 5. for each Micro SD card.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Network Setup
 &lt;div id="network-setup" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#network-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;It is now time to configure your WLAN router. This of course depends on which router you use. The following instructions are based on a &lt;a href="http://www.tp-link.com/en/products/details/cat-9_TL-WR802N.html" target="_blank" rel="noreferrer"&gt;TP-Link TL-WR802N&lt;/a&gt; which is quite inexepensive but still absolutely ok for our purposes since it sits very close to the cluster and my notebook anyway.&lt;/p&gt;
&lt;p&gt;First of all you need to setup the SSID and password. Use the same credentials with which you have configured your images.&lt;/p&gt;
&lt;p&gt;My setup is, that I span a private network &lt;code&gt;192.168.23.0/24&lt;/code&gt; for the Pi cluster which my MacBook also joins via its integrated WiFi.&lt;/p&gt;
&lt;p&gt;The addresses I have chosen are :&lt;/p&gt;
&lt;p&gt;| &lt;code&gt;192.168.23.1&lt;/code&gt; | WLAN Router |
| &lt;code&gt;192.168.23.100&lt;/code&gt; | MacBook&amp;rsquo;s WLAN |
| &lt;code&gt;192.168.23.200&lt;/code&gt; &amp;hellip; &lt;code&gt;192.168.23.203&lt;/code&gt; | Raspberry Pis |&lt;/p&gt;
&lt;p&gt;The MacBook is setup for NAT and forwarding from this private network to the internet. This &lt;a href="https://github.com/Project31/ansible-kubernetes-openshift-pi3/blob/master/tools/setup_nat_on_osx.sh" target="_blank" rel="noreferrer"&gt;script&lt;/a&gt; helps in setting up the forwarding and NAT rules on OS X.&lt;/p&gt;
&lt;p&gt;In order to configure your WLAN router you need to connect to it according to its setup instructions. The router is setup in &lt;strong&gt;Access Point&lt;/strong&gt; mode with DHCP enabled. As soon as the MAC of the Pis are known (which you can see as soon as they connect for the first time via WiFi), I configured them to always use the same DHCP lease. For the TL-WR802N this can be done in the configuration section &lt;em&gt;DHCP -&amp;gt; Address Reservation&lt;/em&gt;. In the &lt;em&gt;DHCP -&amp;gt; DHCP-Settings&lt;/em&gt; the default gateway is set to &lt;code&gt;192.168.23.100&lt;/code&gt;, which my notebook&amp;rsquo;s WLAN IP.&lt;/p&gt;
&lt;p&gt;Startup all nodes, you should be able to ping every node in your cluster. I added &lt;code&gt;n0&lt;/code&gt; &amp;hellip; &lt;code&gt;n3&lt;/code&gt; to my notebook&amp;rsquo;s &lt;code&gt;/etc/hosts&lt;/code&gt; pointing to &lt;code&gt;192.168.23.200&lt;/code&gt; &amp;hellip; &lt;code&gt;192.168.23.203&lt;/code&gt; for convenience.&lt;/p&gt;
&lt;p&gt;You should be able to ssh into every Pi with user &lt;em&gt;pi&lt;/em&gt; and password &lt;em&gt;raspberry&lt;/em&gt;. Also, if you set up the forwarding on your desktop properly you should be able to ping from within the pi to the outside world.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Ansible Playbooks
 &lt;div id="ansible-playbooks" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ansible-playbooks" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;After this initial setup is done, the next step is to initialize the base system with Ansible. You will need Ansible 2 installed on your desktop (e.g. &lt;code&gt;brew install ansible&lt;/code&gt; when running on OS X)&lt;/p&gt;

&lt;h3 class="relative group"&gt;Ansible Configuration
 &lt;div id="ansible-configuration" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ansible-configuration" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Checkout the Ansible playbooks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git clone https://github.com/Project31/ansible-kubernetes-openshift-pi3.git k8s-pi
 cd k8s-pi
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy over &lt;code&gt;hosts.example&lt;/code&gt; and adapt it to your needs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cp hosts.example hosts
 vi hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are three Ansible groups which are refered to in the playbooks:&lt;/p&gt;
&lt;p&gt;| &lt;strong&gt;pis&lt;/strong&gt; | All cluster node | &lt;code&gt;n0&lt;/code&gt;, &lt;code&gt;n1&lt;/code&gt;, &lt;code&gt;n2&lt;/code&gt;, &lt;code&gt;n3&lt;/code&gt; |
| &lt;strong&gt;master&lt;/strong&gt; | Master node | &lt;code&gt;n0&lt;/code&gt; |
| &lt;strong&gt;nodes&lt;/strong&gt; | All nodes which are not master | &lt;code&gt;n1&lt;/code&gt;, &lt;code&gt;n2&lt;/code&gt;, &lt;code&gt;n3&lt;/code&gt;|&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy over the configuration and adapt it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; cp config.yml.example config.yml
 vi config.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should at least put in your WLAN credentials, but you are also free to adapt the other values.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 class="relative group"&gt;Basic Node Setup
 &lt;div id="basic-node-setup" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#basic-node-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;If you have already created a cluster with these playbooks and want to start a fresh, please be sure that you cleanup your &lt;code&gt;~/.ssh/known_hosts&lt;/code&gt; from the old host keys. You should be able to ssh into each of the nodes without warnings. Also you must be able to reach the internet from the nodes.&lt;/p&gt;
&lt;p&gt;In the next step the basic setup (without Kubernetes) is performed. This is done by&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook -k -i hosts setup.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you are prompted for the password, use &lt;em&gt;raspberry&lt;/em&gt;. You will probably also need to confirm the SSH authentity for each host with &lt;em&gt;yes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The following steps will be applied by this command (which may take a bit):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker will be installed from the Hypriot repositories&lt;/li&gt;
&lt;li&gt;Your public SSH key is copied over to &lt;em&gt;pi&amp;rsquo;s&lt;/em&gt; authenticated_keys and the users password will be taken from &lt;code&gt;config.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Some extra tools are installed for your convenience and some benchmarking:
&lt;ul&gt;
&lt;li&gt;hdparm&lt;/li&gt;
&lt;li&gt;iperf&lt;/li&gt;
&lt;li&gt;mtr&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Hostname is set to the name of the node configured. Also &lt;code&gt;/etc/hosts&lt;/code&gt; is setup to contain all nodes with their short names.&lt;/li&gt;
&lt;li&gt;A swapfile is enabled (just in case)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this basic setup you have already a working Docker environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now its time to reboot the whole cluster since some required boot params has been added. Plug the wire.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;Kubernetes Setup
 &lt;div id="kubernetes-setup" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#kubernetes-setup" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;The final step for a working Kubernetes cluster is to run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook -i hosts kubernetes.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install one master at n0 and threed additional nodes n1, n2, n3.&lt;/p&gt;
&lt;p&gt;The following features are enabled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;etcd&lt;/code&gt;, &lt;code&gt;flanneld&lt;/code&gt; and &lt;code&gt;kubelet&lt;/code&gt; are installed as systemd services on the master&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubelet&lt;/code&gt; and &lt;code&gt;flanneld&lt;/code&gt; are installed as systemd services on the nodes&lt;/li&gt;
&lt;li&gt;Docker is configured to use the Flannel overlay network&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt; is installed (and an alias &lt;code&gt;k&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If there are some issues when restarting services in the master, don&amp;rsquo;t worry. However you should best restart the master node n0 when this happens, because when setting up the other nodes the would fail if not all services are running on the master.&lt;/p&gt;
&lt;p&gt;After an initial installation it may take a bit until all infrastructure docker images has been loaded. Eventually should be able to use &lt;code&gt;kubectl get nodes&lt;/code&gt; from e.g. &lt;code&gt;n0&lt;/code&gt;. When this wotks but you see only one node, please reboot the cluster since some services may have not been started on the nodes (plug the cables when &lt;code&gt;n0&lt;/code&gt; is ready).&lt;/p&gt;

&lt;h3 class="relative group"&gt;Install SkyDNS
 &lt;div id="install-skydns" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#install-skydns" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;For service discovery via DNS you should finally install the SkyDNS addon, but only when the cluster is running, i.e. the master must be up and listening. For this final step call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook -i hosts skydns.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 class="relative group"&gt;Wrap Up
 &lt;div id="wrap-up" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#wrap-up" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This has become a rather long recipe. I re-did everything from scratch within 60 minutes, so this could be considered as a lower boundary (because I already did it several times :). The initial setup might be a bit flaky, but should be easy to fix. I&amp;rsquo;d love to hear your feedback on this, and maybe we get it more stable afterwards. Remember, that&amp;rsquo;s my first Ansible playbook :)&lt;/p&gt;
&lt;p&gt;Now go out, buy and setup your Kubernetes cluster and have fun :-)&lt;/p&gt;</description></item><item><title>docker-maven-plugin moves on</title><link>https://ro14nd.de/dmp-moves-on/</link><pubDate>Wed, 24 Feb 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/dmp-moves-on/</guid><description>&lt;p&gt;&lt;strong&gt;rhuss/docker-maven-plugin&lt;/strong&gt; is dead, long live &lt;strong&gt;fabric8io/docker-maven-plugin&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;If you follow the Docker Maven Plugin Scene&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, you probably noticed that there has been quite some progress in the last year. Started as a personal research experiment early 2014, &lt;a href="https://github.com/fabric8io/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;rhuss/docker-maven-plugin&lt;/a&gt; (d-m-p) has took off a little bit. With more than 300 GitHub stars it&amp;rsquo;s now the second most popular docker-maven-plugin. With 38 contributors we were able to do 36 releases. It is really fantastic to see that many people contributing to a rather niche product. Many kudos go out to &lt;a href="https://github.com/jgangemi" target="_blank" rel="noreferrer"&gt;Jae&lt;/a&gt; for his many contributions and continued support on fixing and answering issues. Thanks also for always being very patient with my sometimes quite opinionated and picky code reviews :)&lt;/p&gt;
&lt;p&gt;However it is now time to ignite the next stage and bring this personal &amp;lsquo;pet&amp;rsquo; project to a wider context. And what is better suited here than the fabric8 community ?&lt;/p&gt;
&lt;p&gt;&lt;a href="http://fabric8.io" target="_blank" rel="noreferrer"&gt;Fabric8&lt;/a&gt; is a next generation DevOps and integration platform for Docker based applications, with a focus on &lt;a href="http://kubernetes.io/" target="_blank" rel="noreferrer"&gt;Kubernetes&lt;/a&gt; and &lt;a href="https://www.openshift.com/" target="_blank" rel="noreferrer"&gt;OpenShift&lt;/a&gt; as orchestration and build infrastructure. Its a collection of multiple interrelated projects including Maven tooling for interacting with Kubernetes and OpenShift. d-m-p is already included as foundation for creating Docker application images.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m very happy that d-m-p has now found its place in this ecosystem where it will continue to flourish even faster.&lt;/p&gt;
&lt;p&gt;The &lt;a href="http://fabric8.io/community/index.html" target="_blank" rel="noreferrer"&gt;fabric8 community&lt;/a&gt; is very open and has established multiple communications channels on which you will find d-m-p now, too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#fabric8&lt;/code&gt; on &lt;code&gt;irc.freenode.net&lt;/code&gt; is an IRC channel with a lot of helpful hands (including myself)&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://groups.google.com/forum/#!forum/fabric8" target="_blank" rel="noreferrer"&gt;mailing list&lt;/a&gt; for more in depth discussions&lt;/li&gt;
&lt;li&gt;Issues are still tracked with &lt;a href="https://github.com/fabric8io/docker-maven-plugin/issues" target="_blank" rel="noreferrer"&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;d-m-p specific blog posts will go out on the &lt;a href="https://blog.fabric8.io/" target="_blank" rel="noreferrer"&gt;fabric8 blog&lt;/a&gt; in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, what changed ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rhuss/docker-maven-plugin&lt;/code&gt; has been transferred to &lt;a href="https://github.com/fabric8io/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;fabric8io/docker-maven-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Maven group id has changed from &lt;code&gt;org.jolokia&lt;/code&gt; to &lt;code&gt;io.fabric8&lt;/code&gt; for all releases 0.14.0 and later.&lt;/li&gt;
&lt;li&gt;CI and release management will be done on the fabric8 platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And what will &lt;strong&gt;not&lt;/strong&gt; change ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d-m-p will always be usable with plain Docker, speaking either to a remote or local Docker daemon. No Kubernetes, no OpenShift required.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ll continue to work on d-m-p ;-)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks so much for all the fruitful feedback and pull requests. Keep on rocking ;-)&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;with more than 15 &lt;code&gt;docker-maven-plugin&lt;/code&gt;s its probably fair to call it a &amp;ldquo;scene&amp;rdquo; ;-)&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Registry Magic with docker-maven-plugin</title><link>https://ro14nd.de/registry-magic-with-dmp/</link><pubDate>Thu, 21 Jan 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/registry-magic-with-dmp/</guid><description>&lt;p&gt;Dealing with multiple Docker registries is hard, mostly because the meta information where a image is located is part of a Docker image&amp;rsquo;s name, which is typically used as an identifier, too.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how the &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;rhuss/docker-maven-plugin&lt;/a&gt; deals with this peculiarity.&lt;/p&gt;
&lt;p&gt;When setting up a Maven build for creating Docker images out of your Java application, the classical way to specify the registry where to push the final Docker image is to bake it into the image&amp;rsquo;s name. The drawback however is that you couple your build to this particular registry so that it is not possible to push your image to another registry when building the image.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Pull and Push
 &lt;div id="pull-and-push" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pull-and-push" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;docker-maven-plugin&lt;/a&gt; (&lt;code&gt;d-m-p&lt;/code&gt; in short) interacts&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; with Docker registries in two use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pulling&lt;/strong&gt; base images from a registry when building images with &lt;code&gt;docker:build&lt;/code&gt; or starting images with &lt;code&gt;docker:start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pushing&lt;/strong&gt; built images to a registry with &lt;code&gt;docker:push&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases you can define your build agnostic from any registry by omitting the registry part in your image names&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; and specify it externally as meta information. This can be done in various ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding it to the plugin configuration as an &lt;code&gt;&amp;lt;registry&amp;gt;&lt;/code&gt; element. This can be easily put into a Maven profile (either directly in the &lt;code&gt;pom.xml&lt;/code&gt; or also in &lt;code&gt;~/.m2/settings.xml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Using a system property &lt;code&gt;docker.registry&lt;/code&gt; when running Maven&lt;/li&gt;
&lt;li&gt;As a final fallback an environment variable &lt;code&gt;DOCKER_REGISTRY&lt;/code&gt; can be used, too.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mvn -Ddocker.registry=myregistry.domain.com:5000 docker:push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you combine build and push steps in a single call like in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mvn package docker:build docker:push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a pull operation for a base image and a push operation can happen. To allow different registries in this situation the properties &lt;code&gt;docker.pull.registry&lt;/code&gt; and &lt;code&gt;docker.push.registry&lt;/code&gt; are supported, too, (with the corresponding configuration elements &lt;code&gt;&amp;lt;pullRegistry&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;pushRegistry&amp;gt;&lt;/code&gt;, respectively).&lt;/p&gt;
&lt;p&gt;When pushing an image this way, the following happens behind the scene (assuming an image named &lt;code&gt;user/myimage&lt;/code&gt; and target registry &lt;code&gt;myregistry:5000&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The image &lt;code&gt;user/myimage&lt;/code&gt; is tagged temporarily as &lt;code&gt;myregistry:5000/user/myimage&lt;/code&gt; in the Docker daemon.&lt;/li&gt;
&lt;li&gt;The image &lt;code&gt;myregistry:5000/user/myimage&lt;/code&gt; is pushed.&lt;/li&gt;
&lt;li&gt;The tag is removed again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Authentication
 &lt;div id="authentication" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#authentication" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all fine, but how does d-m-p deal with authentication ? Again, there are several possibilities how authentication can be performed against a registry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using a &lt;code&gt;&amp;lt;authConfig&amp;gt;&lt;/code&gt; section in the plugin configuration with&lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;password&amp;gt;&lt;/code&gt; elements.&lt;/li&gt;
&lt;li&gt;Providing system properties &lt;code&gt;docker.username&lt;/code&gt; and &lt;code&gt;docker.password&lt;/code&gt; when running Maven&lt;/li&gt;
&lt;li&gt;Using a &lt;code&gt;&amp;lt;server&amp;gt;&lt;/code&gt; configuration in &lt;code&gt;~/.m2/settings.xml&lt;/code&gt; with possible encrypted password. That&amp;rsquo;s the most maven-ish way for doing authentication.&lt;/li&gt;
&lt;li&gt;Login into the registry with &lt;code&gt;docker login&lt;/code&gt;. The plugin will pick up the credentials from &lt;code&gt;~/.docker/config.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are again variants to distinguish between authentication for pulling and pushing images to registries (e.g. &lt;code&gt;docker.push.username&lt;/code&gt; and &lt;code&gt;docker.push.password&lt;/code&gt;). All the details can be found in the &lt;a href="http://ro14nd.de/docker-maven-plugin/authentication.html" target="_blank" rel="noreferrer"&gt;reference manual&lt;/a&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Using the OpenShift Registry
 &lt;div id="using-the-openshift-registry" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#using-the-openshift-registry" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.openshift.com/" target="_blank" rel="noreferrer"&gt;OpenShift&lt;/a&gt; is an awesome PaaS platform on top of &lt;a href="http://kubernetes.io/" target="_blank" rel="noreferrer"&gt;Kubernetes&lt;/a&gt;. It comes with an &lt;a href="https://docs.openshift.com/enterprise/latest/install_config/install/docker_registry.html" target="_blank" rel="noreferrer"&gt;own Docker registry&lt;/a&gt; which can be used by d-m-p, too. However, there are some things to watch out for.&lt;/p&gt;
&lt;p&gt;First of all, the registry needs to be exposed to the outside so that a Docker daemon outside the OpenShift cluster can talk with the registry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;oc expose service/docker-registry --hostname=docker-registry.mydomain.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hostname provided should be resolved by your host to the OpenShift API server&amp;rsquo;s IP (this happens automatically if you use the &lt;a href="http://fabric8.io/guide/getStarted/vagrant.html" target="_blank" rel="noreferrer"&gt;fabric8 OpenShift Vagrant image&lt;/a&gt; for a one-node developer installation of OpenShift).&lt;/p&gt;
&lt;p&gt;Next, it is important to know, that the OpenShift registry use the regular OpenShift SSO authentication, so you have to login into OpenShift before you can push to the registry. The access token obtained from the login is then used as the password for accessing the registry:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Login to OpenShift. Credentials are stored in ~/.kube/config.json:
oc login

# Use user and access token for authentication:
mvn docker:push -Ddocker.registry=docker-registry.mydomain.com \
 -Ddocker.username=$(oc whoami) \
 -Ddocker.password=$(oc whoami -t)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step can be simplified by using &lt;code&gt;-Ddocker.useOpenShiftAuth&lt;/code&gt; which does the user and token lookup transparently.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mvn docker:push -Ddocker.registry=docker-registry.mydomain.com \
 -Ddocker.useOpenShiftAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration option &lt;code&gt;useOpenShiftAuth&lt;/code&gt; again comes in multiple flavours: a default one, and dedicated for push and pull operations (&lt;code&gt;docker.pull.useOpenShiftAuth&lt;/code&gt; and &lt;code&gt;docker.push.useOpenShiftAuth&lt;/code&gt;).&lt;/p&gt;

&lt;h3 class="relative group"&gt;tl;dr
 &lt;div id="tldr" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tldr" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Among all the &lt;a href="https://github.com/search?utf8=%E2%9C%93&amp;amp;q=docker-maven-plugin" target="_blank" rel="noreferrer"&gt;many docker maven plugins&lt;/a&gt;, &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;rhuss/docker-maven-plugin&lt;/a&gt; provides the most flexible options for accessing Docker registries and authentication. The gory details can be found in the &lt;a href="http://ro14nd.de/docker-maven-plugin/" target="_blank" rel="noreferrer"&gt;reference manual&lt;/a&gt; which documents &lt;a href="http://ro14nd.de/docker-maven-plugin/" target="_blank" rel="noreferrer"&gt;registry handling&lt;/a&gt; and &lt;a href="http://ro14nd.de/docker-maven-plugin/authentication.html" target="_blank" rel="noreferrer"&gt;authentication&lt;/a&gt; in detail.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The interaction is always indirectly via the Docker daemon, since a Docker client like d-m-p only talks with the Docker daemon directly.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Of course you can the registry part in your image names in which case this registry has always the highest priority.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Jolokia 2.0 - JMX Notifications</title><link>https://ro14nd.de/jolokia-notifications/</link><pubDate>Wed, 13 Jan 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jolokia-notifications/</guid><description>&lt;p&gt;This screencast gives a live demo of the forthcoming JMX notification support in Jolokia 2.0.&lt;/p&gt;
&lt;iframe src="https://player.vimeo.com/video/151629488" width="720" height="405" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Jolokia supports currently two notification modes. In all modes, the Jolokia agent itself subscribe to a JMX notification locally and then dispatches the notifications to its clients.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull Mode&lt;/strong&gt; : Here, the agent keeps the notification received for a client in memory and sends it back on an JMX request to a Jolokia specific MBean. A client typically queries this notification MBean periodically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSE Mode&lt;/strong&gt; : &lt;a href="http://www.w3.org/TR/2011/WD-eventsource-20110208/" target="_blank" rel="noreferrer"&gt;Server Sent Events&lt;/a&gt; are a W3C standard for pushing events from an HTTP server to a client. With this mode the Jolokia agents directly pushes any notification it receives to the client. The advantage is of course a much lower latency compared to the pull mode, but SSE is &lt;a href="http://caniuse.com/#feat=eventsource" target="_blank" rel="noreferrer"&gt;not available for Internet Explorer&lt;/a&gt;, including 11. What a pity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href="https://jolokia.org/reference/html/protocol.html" target="_blank" rel="noreferrer"&gt;Jolokia protocol&lt;/a&gt; has been extended with the top level action &lt;code&gt;notification&lt;/code&gt; and subcommands.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt; / &lt;code&gt;unregister&lt;/code&gt; : Register / unregister a notification client&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add&lt;/code&gt; / &lt;code&gt;remove&lt;/code&gt; : Add / remove a listener subscription&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt; : list all subscriptions for a client&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ping&lt;/code&gt; : Keep subscription alive&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; : Use for creating a back channel. E.g. the &lt;em&gt;SSE&lt;/em&gt; mode keeps this GET request for pushing back an event stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Currently only the new Jolokia JavaScript client supports JMX notification. If you are interested in having it in other clients (e.g. Java), too, please let me know. I would be more than happy for coders jumping on the Jolokia bandwagon since there is still quite some stuff to do for 2.0.&lt;/p&gt;
&lt;p&gt;The source code to this demo and the new Jolokia JavaScript client is on GitHub: &lt;a href="https://github.com/jolokia-org/jolokia-client-javascript" title="https://github.com/jolokia-org/jolokia-client-javascript" target="_blank" rel="noreferrer"&gt;https://github.com/jolokia-org/jolokia-client-javascript&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Welcome to 2016 - the year Jolokia 2.0 will see the light of day</title><link>https://ro14nd.de/jolokia-in-2016/</link><pubDate>Wed, 06 Jan 2016 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jolokia-in-2016/</guid><description>&lt;p&gt;I hope you all had a good start into 2016 and have charged all your batteries during the time of stillness.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; had a good start, too. During the holiday season I took the opportunity to continue to work on version 2.0 which now takes on form. If you have followed the history of Jolokia you know that work on 2.0 started early 2013 but advanced quite slowly for multiple reasons.&lt;/p&gt;
&lt;p&gt;Now its time to go out on a limb with announcing Jolokia 2.0 for 2016. A bit of pressure sometimes really helps ;-)&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s are the major themes for Jolokia 2.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jolokia 2.0 will be &lt;strong&gt;backwards compatible&lt;/strong&gt; on the protocol level. This is a design goal. There might be some changes in default values, however this should be easy to fix. Any such change will be announced prominently (like artefact renaming). So, all your clients will be usable with 2.0 with minor changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JMX Notification&lt;/strong&gt; support is here. Yeah, this was quite some work. The extensions to the Jolokia 2.0 protocol are able to push notifications in various ways. Currently the agents supports two modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull mode&lt;/strong&gt; will collect JMX notification on the server (agent) side and can be fetched by a client with an HTTP request, which typically happens periodically. This introduces some latencies but is the most robust way to transmit notifications to a client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSE mode&lt;/strong&gt; uses &lt;a href="http://www.w3.org/TR/2011/WD-eventsource-20110208/" target="_blank" rel="noreferrer"&gt;Server Sent Events&lt;/a&gt; for pushing JMX notifications immediately with very low latency. This is the preferred mode if a client supports this (Internet Explorer does not).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The notification support is nearly complete on the agent side, and the Jolokia JavaScript client already supports both modes. In the future more mode like WebSockets or Web-Hooks should be easy to add. The next post will give a demo about the notification support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Namespaces&lt;/strong&gt; extend Jolokia beyond JMX which means you can access other entities than JMX MBeans with the very same protocol. This feature is still in the conceptual state but one can easily imagine to access&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Beans&lt;/li&gt;
&lt;li&gt;CDI Objects&lt;/li&gt;
&lt;li&gt;JNDI Directories&lt;/li&gt;
&lt;li&gt;Zookeeper Directories&lt;/li&gt;
&lt;li&gt;&amp;hellip;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;the same was as JMX. The namespace is selected as part of the (MBean) name. More on this in this &lt;a href="https://github.com/rhuss/jolokia/wiki/Requesthandler" target="_blank" rel="noreferrer"&gt;design document&lt;/a&gt;. Since this feature would extend the usage pattern of Jolokia quite a bit, I&amp;rsquo;m not 100% sure whether to include it into 2.0 since it feels a bit against my Unix based education (&amp;ldquo;do one thing and do it well&amp;rdquo;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the addition of even more features, &lt;strong&gt;modularization&lt;/strong&gt; becomes even more important. Jolokia was and is always picky about its footprint, which is currently 430k for the WAR agent with all features included. Jolokia 2.0 introduces various &lt;a href="https://github.com/rhuss/jolokia/wiki/Jolokia-Services" target="_blank" rel="noreferrer"&gt;internal services&lt;/a&gt; which can be picked and chosen by repackaging the agent. Or the agent can be extended with own functionality, too. A way for easily packaging and creating agents will be provided either by a Web-UI or by a CLI tool (or both).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition there are also some non-functional changes to polish Jolokia a bit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Non-agent based addition like client libraries, integration tests, JBoss Forge support are extracted into extra GitHub repositories. All this will happen within the GitHub organization &lt;a href="https://github.com/jolokia-org" target="_blank" rel="noreferrer"&gt;jolokia-org&lt;/a&gt;. The first project here is the JavaScript client which already moved to a dedicated &lt;a href="https://github.com/jolokia-org/jolokia-client-javascript" target="_blank" rel="noreferrer"&gt;jolokia-client-javascript&lt;/a&gt; repository.&lt;/li&gt;
&lt;li&gt;The website will get a face-lift.&lt;/li&gt;
&lt;li&gt;Documentation will switch from Docbook to a Markdown or AsciiDoc based format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally some stuff will get dropped. This happens because of limited resources (Jolokia, to be frankly, still doesn&amp;rsquo;t have a big community, so that most of the work is done by a single person. &amp;lsquo;would like to change that, though) and because I think these feature never took off:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mule agent&lt;/strong&gt;. I never got much feedback from the Mule community so I&amp;rsquo;m really not sure whether this agent is really used or needed. Jolokia 1.x will continue to support the Mule agent, however there will be no stock Jolokia 2.0 Mule agent. Said that, you are always free to adopt Jolokia 2.0 to the Mule management platform. Considering the extra code needed included in Jolokia 1.3 for Mule support this should be fairly trivial. I&amp;rsquo;m happy to support anyone doing the port. Also, there is always the alternative to use the JVM agent for attaching Jolokia to Mule, which is the preferred way for 2.0 to monitor Mule with Jolokia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Roo Support&lt;/strong&gt; will be dropped for much the same reasons. I never received an issue on the Jolokia Spring Roo support, which is a clear sign that nobody is using it. It might popup as an extra project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, what&amp;rsquo;s the roadmap ? Here&amp;rsquo;s the plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Milestone 2.0.0-M1 is here. You find the &lt;a href="http://search.maven.org/remotecontent?filepath=org/jolokia/jolokia-agent-jvm/2.0.0-M1/jolokia-agent-jvm-2.0.0-M1-agent.jar" target="_blank" rel="noreferrer"&gt;JVM&lt;/a&gt; and &lt;a href="http://search.maven.org/remotecontent?filepath=org/jolokia/jolokia-agent-war/2.0.0-M1/jolokia-agent-war-2.0.0-M1.war" target="_blank" rel="noreferrer"&gt;WAR&lt;/a&gt; agents in Maven central.&lt;/li&gt;
&lt;li&gt;Every month, a new milestone will be released.&lt;/li&gt;
&lt;li&gt;Final release is aligned to &lt;a href="http://www.redhat.com/summit/" target="_blank" rel="noreferrer"&gt;Red Hat Summit&lt;/a&gt; / &lt;a href="http://www.devnation.org/" target="_blank" rel="noreferrer"&gt;DevNation&lt;/a&gt;. July 1st.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Isn&amp;rsquo;t this a nice new year&amp;rsquo;s resolution ? ;-)&lt;/p&gt;
&lt;p&gt;In the next post I will demo JMX notifications and how you can use them in your JavaScript projects.&lt;/p&gt;</description></item><item><title>fish-pepper - Docker on Capsaicin</title><link>https://ro14nd.de/fish-pepper-announcement/</link><pubDate>Mon, 07 Sep 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/fish-pepper-announcement/</guid><description>&lt;p&gt;When I had to create multiple Docker base images which only differ slightly for some minor variations I couldn&amp;rsquo;t avoid to feel quite dirty because of all the copying &amp;amp; pasting of Dockerfile fragments. We all know how this smells, but unfortunately Docker has only an answer for &lt;strong&gt;inheritance&lt;/strong&gt; but not for &lt;strong&gt;composition&lt;/strong&gt; of Docker images. Luckily there is now &lt;a href="https://github.com/fabric8io/fish-pepper" target="_blank" rel="noreferrer"&gt;fish-pepper&lt;/a&gt;, a multi-dimensional docker build generator, which steps into the breach.&lt;/p&gt;
&lt;p&gt;For example consider a Java base image: Some users might require Java 7, some want Java 8. For running Microservices a JRE might be sufficient. In other use cases you need a full JDK. These four variants are all quite similar with respect to documentation, Dockerfiles and support files like startup scripts. Copy-and-paste might seem to work for the initial setup but there are severe drawbacks considering image evolution or introduction of even more parameters.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;fish-pepper&lt;/code&gt; you can use flexible templates which are filled with variations of the base image (like &lt;code&gt;'version' : ['java7', 'java8'], 'type': ['jdk', 'jre']&lt;/code&gt;) and which will create multiple, similar Dockerfile builds.&lt;/p&gt;
&lt;p&gt;The main configuration of an image family is &lt;code&gt;images.yml&lt;/code&gt; which defines the possible parameters. For the example above it is&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fish-pepper:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; params:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;version&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;type&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The possible values for these parameters are given in a dedicated &lt;code&gt;config&lt;/code&gt; section:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;config:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openjdk7:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; java: &amp;#34;java:7u79&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fullVersion: &amp;#34;OpenJDK 1.7.0_79&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openjdk8:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; java: &amp;#34;java:8u45&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fullVersion: &amp;#34;OpenJDK 1.8.0_45&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jre:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extension: &amp;#34;-jre&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jdk:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extension: &amp;#34;-jdk&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Given this configuration, four builds will be generated when calling &lt;code&gt;fish-pepper&lt;/code&gt;, one for each combination of &lt;em&gt;version&lt;/em&gt; (&amp;ldquo;openjdk7&amp;rdquo; and &amp;ldquo;openjdk8&amp;rdquo;) and &lt;em&gt;type&lt;/em&gt; (&amp;ldquo;jre&amp;rdquo; and &amp;ldquo;jdk&amp;rdquo;) parameter values.&lt;/p&gt;
&lt;p&gt;These value can now be filled into templates which are stored in a &lt;code&gt;templates/&lt;/code&gt; directory. The &lt;code&gt;Dockerfile&lt;/code&gt; in this directory can refer to this configuration through a context object &lt;code&gt;fp&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FROM {{ &amp;#34;{{= fp.config.version.java + fp.config.type.extension &amp;#34; }}}}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.....&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Templates use &lt;a href="http://olado.github.io/doT/index.html" target="_blank" rel="noreferrer"&gt;DoT.js&lt;/a&gt; as template engine, so that the full expressiveness of JavaScript is available. The fish-pepper &lt;a href="https://github.com/fabric8io/fish-pepper#template-context" target="_blank" rel="noreferrer"&gt;context object&lt;/a&gt; &lt;code&gt;fp&lt;/code&gt; holds the configuration and more.&lt;/p&gt;
&lt;p&gt;The given configuration will lead to four Docker build directories:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;images/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; +---- openjdk7
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | +--- jre -- Dockerfile, ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | +--- jdk -- Dockerfile, ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; +---- opendjk8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; +--- jre -- Dockerfile, ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; +--- jdk -- Dockerfile, ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The generated build files can also be used directly to create the images with &lt;code&gt;fish-pepper build&lt;/code&gt;. This will reach out to a Docker daemon and create the images &lt;code&gt;java-openjdk7-jre&lt;/code&gt;, &lt;code&gt;java-openjdk7-jdk&lt;/code&gt;, &lt;code&gt;java-openjdk8-jre&lt;/code&gt; and &lt;code&gt;java-openjdk8-jdk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively these builds can be used as content for automated Docker Hub builds when checked into Github. The full example can be found on &lt;a href="https://github.com/fabric8io/fish-pepper/tree/master/example" target="_blank" rel="noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But wait, there is more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fabric8io/fish-pepper#blocks" target="_blank" rel="noreferrer"&gt;Blocks&lt;/a&gt; can be used to reuse Dockerfile snippets and files to include across images. Blocks can be stored locally or referenced via a remote Git repository. Examples for blocks are generic &lt;a href="https://github.com/fabric8io/run-java-sh/tree/master/fish-pepper/run-java-sh" target="_blank" rel="noreferrer"&gt;startup scripts&lt;/a&gt; or other value add functionality like enabling agents like &lt;a href="https://github.com/fabric8io/agent-bond/tree/master/fish-pepper/agent-bond" target="_blank" rel="noreferrer"&gt;agent bond&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Flexible &lt;a href="https://github.com/fabric8io/fish-pepper#file-mappings" target="_blank" rel="noreferrer"&gt;file mappings&lt;/a&gt; allow multiple alternative templates.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabric8io/fish-pepper#defaults" target="_blank" rel="noreferrer"&gt;Defaults&lt;/a&gt; allow shared configuration between multiple parameter values.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;fish-pepper can be seen in its fully beauty in &lt;a href="https://github.com/fabric8io/base-images" target="_blank" rel="noreferrer"&gt;fabric8io/base-images&lt;/a&gt; where more than twenty five base images are maintained with fish-pepper.&lt;/p&gt;
&lt;p&gt;With node.js you can install fish-pepper super easy with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm -g install fish-pepper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the following blogs I will show more usage examples, especially how &amp;ldquo;blocks&amp;rdquo; can be easily reused and shared.&lt;/p&gt;</description></item><item><title>Jmx4Perl for everyone</title><link>https://ro14nd.de/jmx4perl-docker/</link><pubDate>Tue, 28 Jul 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jmx4perl-docker/</guid><description>&lt;p&gt;As you might know, &lt;a href="http://www.jmx4perl.org" target="_blank" rel="noreferrer"&gt;Jmx4Perl&lt;/a&gt; is the mother of &lt;a href="https://jolokia.org/" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt;. But what might be not so known is, that Jmx4Perl provides a set of nice CLI tools for accessing Jolokia agents. However, installing Jmx4Perl manually is cumbersome because of its many Perl and also native dependencies.&lt;/p&gt;
&lt;p&gt;However, if you are a Docker user there is now a super easy way to benefit from this gems.&lt;/p&gt;
&lt;p&gt;Even if Perl is not your cup of tea, you might like the following tool (for which of course no Perl knowledge is required at all):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/jmx4perl" target="_blank" rel="noreferrer"&gt;&lt;strong&gt;jmx4perl&lt;/strong&gt;&lt;/a&gt; is a command line tool for one-shot querying Jolokia agents. It is perfectly suited for shell scripts.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/j4psh" target="_blank" rel="noreferrer"&gt;&lt;strong&gt;j4psh&lt;/strong&gt;&lt;/a&gt; is a readline based, JMX shell with coloring and command line completion. You can navigate the JMX namespace like directories with &lt;code&gt;cd&lt;/code&gt; and &lt;code&gt;ls&lt;/code&gt;, read JMX attributes with &lt;code&gt;cat&lt;/code&gt; and execute operations with &lt;code&gt;exec&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/jolokia" target="_blank" rel="noreferrer"&gt;&lt;strong&gt;jolokia&lt;/strong&gt;&lt;/a&gt; is an agent management tool which helps you in downloading Jolokia agents of various types (war, jvm, osgi, mule) and versions. It also knows how to repackage agents e.g. for enabling security for the war agent by in-place modification of the web.xml descriptor.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/check_jmx4perl" target="_blank" rel="noreferrer"&gt;&lt;strong&gt;check_jmx4perl&lt;/strong&gt;&lt;/a&gt; is a full featured Nagios plugin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can you now use these tools ? All you need is a running Docker installation. The tools mentioned above are all included within the Docker image &lt;a href="https://registry.hub.docker.com/u/jolokia/jmx4perl/" target="_blank" rel="noreferrer"&gt;jolokia/jmx4perl&lt;/a&gt; which is available from Docker Hub.&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Get some basic information of the server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --rm -it jolokia/jmx4perl \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jmx4perl http://localhost:8080/jolokia
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Download the current jolokia.war agent
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --rm -it -v `pwd`:/jolokia jolokia/jmx4perl \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; jolokia
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Start an interactive JMX shell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# server &amp;#34;tomcat&amp;#34; is defined in ~/.j4p/jmx4perl.config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --rm -it -v ~/.j4p:/root/.j4p jolokia/jmx4perl \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j4psh tomcat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In these examples we mounted some volumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you put your server definitions into &lt;code&gt;~/.j4p/jmx4perl.config&lt;/code&gt; you can use them by mounting this directory as volume with &lt;code&gt;-v ~/.j4p:/root/.j4p&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For the management tool &lt;code&gt;jolokia&lt;/code&gt; it is recommended to mount the local directory with &lt;code&gt;-v $(pwd):/jolokia&lt;/code&gt; so that downloaded artefacts are stored in the current host directory. (Note for boot2docker users: This works only when you are in a directory below you home directory)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is recommended to use aliases as abbreviations:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias jmx4perl=&amp;#34;docker run --rm -it -v ~/.j4p:/root/.j4p jolokia/jmx4perl jmx4perl&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias jolokia=&amp;#34;docker run --rm -it -v `pwd`:/jolokia jolokia/jmx4perl jolokia&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias j4psh=&amp;#34;docker run --rm -it -v ~/.j4p:/root/.j4p jolokia/jmx4perl j4psh&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As an additional benefit of using Jmx4Perl that way, you can access servers which are not directly reachable by you. The Jolokia agent must be reachable by the Docker daemon only. For example, you can communicate with a SSL secured Docker daemon running in a DMZ only. From there you can easily reach any other server with a Jolokia agent installed, so there is no need to open access to all servers from your local host directly.&lt;/p&gt;
&lt;p&gt;Finally, here&amp;rsquo;s a short appetiser with an (older) demo showing &lt;code&gt;j4psh&lt;/code&gt; in action.&lt;/p&gt;
&lt;iframe width="720" height="405" src="https://www.youtube.com/embed/y9TuGzxD2To" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;</description></item><item><title>docker:watch</title><link>https://ro14nd.de/maven-docker-watch/</link><pubDate>Tue, 30 Jun 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/maven-docker-watch/</guid><description>&lt;p&gt;Ok, you know Docker. And since you are a Java developer you want to know how you can use this in your daily development workflow. You probably also heard about &lt;em&gt;the&lt;/em&gt; &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;docker-maven-plugin&lt;/a&gt; which seamlessly creates Docker images, starts and stops Docker containers and more all with a concise configuration syntax.&lt;/p&gt;
&lt;p&gt;And now there is this new goal &lt;code&gt;docker:watch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We developers are lazy, right ? We want our code to compile fast, we want the servers to start up fast. And we want to test changes quickly. That&amp;rsquo;s why we love &lt;a href="http://www.osgi.org/Main/HomePage" target="_blank" rel="noreferrer"&gt;OSGi&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; and &lt;a href="http://zeroturnaround.com/software/jrebel/" target="_blank" rel="noreferrer"&gt;JRebel&lt;/a&gt;. And we want this for Docker containers, too.&lt;/p&gt;
&lt;p&gt;Good news. &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;docker-maven-plugin&lt;/a&gt; will support hot rebuild of Docker images and hot restart of containers with a new Maven goal &lt;code&gt;docker:watch&lt;/code&gt;. It will be released with version 0.12.1. For the brave coder 0.12.1-SNAPSHOT is already out there, the documentation can be found &lt;a href="https://github.com/rhuss/docker-maven-plugin/blob/integration/doc/manual.md#dockerwatch" target="_blank" rel="noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But before losing more words, here&amp;rsquo;s a sneak preview.&lt;/p&gt;
&lt;iframe src="https://player.vimeo.com/video/132183699" width="720" height="405" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;That&amp;rsquo;s of course not entirely true. We love OSGi &amp;hellip; or we hate it with passion. But even the haters don&amp;rsquo;t hate it for its hot deployment abilities.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>The Docker Wormhole Pattern</title><link>https://ro14nd.de/docker-wormhole-pattern/</link><pubDate>Wed, 17 Jun 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/docker-wormhole-pattern/</guid><description>&lt;p&gt;Building a Docker wormhole is easy.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A wormhole is a special type of structure that some scientists think might exist, connecting parts of space and time that are not usually connected&lt;/p&gt;
&lt;p&gt;&amp;mdash; Cambridge Dictionaries Online&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;In Docker universe we have several uses cases, which require a Docker installation within a Docker container. For example the &lt;a href="https://github.com/openshift/origin/blob/master/docs/builds.md#openshift-builds" target="_blank" rel="noreferrer"&gt;OpenShift Builds&lt;/a&gt; use images whose container&amp;rsquo;s are meant to create application images. They include a whole development environment including possibly a compiler and a build tool. During the build a Docker daemon is accessed for creating the final application image.&lt;/p&gt;
&lt;p&gt;The question is now, &lt;em&gt;how&lt;/em&gt; a build can access the Docker daemon ? In general, there are two possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jpetazzo/dind" target="_blank" rel="noreferrer"&gt;Docker in Docker&lt;/a&gt; is a project which allows you to run a Docker daemon within a Docker container. Technically this is quite tricky and there seems to be some issues with this approach, especially because you have to run this container in &lt;a href="http://blog.docker.com/2013/09/docker-can-now-run-within-docker/" target="_blank" rel="noreferrer"&gt;privileged mode&lt;/a&gt;. This is the &lt;strong&gt;Matryoshka doll pattern&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Or you use the &lt;strong&gt;Wormhole pattern&lt;/strong&gt; described in this post. The idea is to get access to the Docker daemon running a container from within the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you know a Docker host can be configured to be accessible by two alternative methods: Via a Unix socket or via a TCP socket.&lt;/p&gt;
&lt;p&gt;Using the Unix socket of the surrounding docker daemon is easy: Simply share the path to the unix socket as a volume:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Map local unix socket into the container
docker run -it -v /var/run/docker.sock:/var/run/docker.sock ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then within the container you can use the Docker CLI or any tool that uses the Unix socket at usual.&lt;/p&gt;
&lt;p&gt;Running over the TCP socket is a bit more tricky because you have to find out the address of your Docker daemon host. This can best be done by examining the routing table within the container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Lookup and parse routing table 
host=$(ip route show 0.0.0.0/0 | \
 grep -Eo 'via \S+' | \
 awk '{print $2}');
export DOCKER_HOST=tcp://${host}:2375
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works fine as long you are not using SSL. With SSL in place you need have access to the SSL client certificates. Of course this is achieved again with a volume mount. Assuming that you are using boot2docker this could look like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Mount certs into the container
docker run -ti -v ~/.boot2docker/certs/boot2docker-vm/:/certs ....
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will mount your certs at &lt;code&gt;/certs&lt;/code&gt; within the container and can be used to set the &lt;code&gt;DOCKER_HOST&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if [ -f /certs/key.pem ]; then
 # If certs are mounted, use SSL ...
 export DOCKER_CERT_PATH=/certs
 export DOCKER_TLS_VERIFY=1
 export DOCKER_HOST=tcp://${host}:2376
else
 # ... otherwise use plain http
 export DOCKER_TLS_VERIFY=0
 export DOCKER_HOST=tcp://${host}:2375
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is some final gotcha as that the server certificate can not be verified because it doesn&amp;rsquo;t contain the docker host IP as seen from the container. See this &lt;a href="https://github.com/docker/docker/issues/13922" target="_blank" rel="noreferrer"&gt;issue&lt;/a&gt; for details. As workaround you have to &lt;code&gt;unset DOCKER_TLS_VERIFY&lt;/code&gt; for the moment when using the docker client.&lt;/p&gt;
&lt;p&gt;Both ways are useful and are &lt;a href="https://github.com/openshift/origin/blob/master/docs/builds.md#why-not-docker-in-docker" target="_blank" rel="noreferrer"&gt;leaner and possibly more secure&lt;/a&gt; than having a Matryoshka doll approach.&lt;/p&gt;
&lt;p&gt;Finally there is still the question, why on earth &lt;em&gt;wormhole pattern&lt;/em&gt; ? Like in a wormhole (also known as &lt;a href="https://en.wikipedia.org/wiki/Wormhole" target="_blank" rel="noreferrer"&gt;Einstein-Rosen Bridge&lt;/a&gt;) you can reach through the wormhole a point (the outer docker daemon) in spacetime which is normally not reachable (because a container is supposed to be its &amp;ldquo;own&amp;rdquo; world). Another fun fact: If you create a container through a wormhole this container it&amp;rsquo;s not your daughter, its your sister. Feels a bit freaky, or ? Alternatively you could call it also &lt;strong&gt;Münchhausen pattern&lt;/strong&gt; because you create something with the exact the identically means you have been created yourself (like in the &lt;a href="https://en.wikipedia.org/wiki/M%C3%BCnchhausen_trilemma" target="_blank" rel="noreferrer"&gt;Münchhausen trilemma&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Or feel free to call it what you like ;-)&lt;/p&gt;</description></item><item><title>Jmx4Perl on OS X</title><link>https://ro14nd.de/jmx4perl-on-osx/</link><pubDate>Mon, 23 Mar 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jmx4perl-on-osx/</guid><description>&lt;p&gt;The HTTP-JMX Bridge &lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; allows easy access to JMX. It exposes all JMX information and operations via an REST-like interface and has tons of nifty features. &lt;a href="http://search.cpan.org/~roland/jmx4perl/" target="_blank" rel="noreferrer"&gt;Jmx4Perl&lt;/a&gt; on the other side is a client for Jolokia, which beside Perl access modules also provides quite some nice CLI tools for accessing and installing Jolokia. This post explains how install these tools on OS X.&lt;/p&gt;
&lt;p&gt;Jmx4Perl provides some nice CLI commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/jmx4perl" target="_blank" rel="noreferrer"&gt;jmx4perl&lt;/a&gt; is a simple access tool which is useful for quick queries and ideal for inclusion in shell scripts.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/j4psh" target="_blank" rel="noreferrer"&gt;j4psh&lt;/a&gt; is a powerful interactive, readline based JMX shell with tab completion and syntax highlighting.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/jolokia" target="_blank" rel="noreferrer"&gt;jolokia&lt;/a&gt; is a tool for managing Jolokia agents (downloading, changing init properties etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this tools are very helpful in order to explore the JMX namespace and installing the agent. They all are fairly good documented and each of them probably deserves an own blog post.&lt;/p&gt;
&lt;p&gt;However, the installation or Perl modules and programs is a bit tedious. Although &lt;a href="http://search.cpan.org/~andk/CPAN/scripts/cpan" target="_blank" rel="noreferrer"&gt;cpan&lt;/a&gt; helps here and also resolves transitive dependencies it&amp;rsquo;s still a lengthy process, which fails from time to time. Native Linux packages are planned, but don&amp;rsquo;t hold your breath ;-).&lt;/p&gt;
&lt;p&gt;For OS X users with &lt;a href="http://brew.sh/" target="_blank" rel="noreferrer"&gt;Homebrew&lt;/a&gt; can install Jmx4Perl quite easily, though:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ brew install cpanm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cpanm --sudo install JMX::Jmx4Perl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will do all the heavy lifting for you and at the end all the fine Jmx4Perl tools are installed and available under &lt;code&gt;/usr/local/bin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;j4psh&lt;/code&gt; uses &lt;code&gt;libreadline&lt;/code&gt; for the input handling. For the best user experience GNU ReadLine is recommended. Unfortunately, OS X doesn&amp;rsquo;t ship with a &lt;em&gt;true&lt;/em&gt; &lt;code&gt;libreadline&lt;/code&gt; but with &lt;code&gt;libedit&lt;/code&gt; which is a stripped down version of libreadline. In order to use GNU readline, some tricks are needed which are described in this &lt;a href="http://blogs.perl.org/users/aristotle/2013/07/easy-osx-termreadlinegnu.html" target="_blank" rel="noreferrer"&gt;recipe&lt;/a&gt;. For me, the following steps worked (but are probably a bit &amp;ldquo;dirty&amp;rdquo;):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ brew install readline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ brew link --force readline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo mv /usr/lib/libreadline.dylib /tmp/libreadline.dylib 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cpanm --sudo Term::ReadLine::Gnu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo mv /tmp/libreadline.dylib /usr/lib/libreadline.dylib
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ brew unlink readline&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;These steps are really only necessary if you need advanced readline functionality (or a coloured prompt in j4psh ;-).&lt;/p&gt;</description></item><item><title>Health Checks with Jolokia</title><link>https://ro14nd.de/health-checks/</link><pubDate>Sat, 17 Jan 2015 00:00:00 +0000</pubDate><guid>https://ro14nd.de/health-checks/</guid><description>&lt;p&gt;A &lt;em&gt;health check&lt;/em&gt; is a useful technique for determining the overall operational state of a system in a consolidated form. It provides some kind of internal monitoring which collects metrics, evaluates them against some thresholds and provides a unified result. Health checks are now coming to &lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt;. This post explains the strategy to include health checks into Jolokia without blowing up the agents to much.&lt;/p&gt;
&lt;p&gt;Health checks are different to classical monitoring solutions like Nagios, where external systems collect metrics and evaluate them against some threshold on their own. While monitoring with Nagios was and is always possible with Jolokia (and in fact was the original motivation for creating it), intrinsic health checks were avoided for the vanilla agent up to now because of the extra complexity they introduce into the agent. One of the major design goals of Jolokia is to keep it small and focussed.&lt;/p&gt;
&lt;p&gt;The upcoming release 1.3.0 (scheduled for the end of this month) will introduce a simple plugin architecture into Jolokia which allows to hook into the agent&amp;rsquo;s lifecycle. A so called &lt;a href="https://github.com/rhuss/jolokia/blob/master/agent/core/src/main/java/org/jolokia/backend/plugin/MBeanPlugin.java" target="_blank" rel="noreferrer"&gt;MBeanPlugin&lt;/a&gt; in Jolokia also allows access to the agent configuration and to the JMX system. Currently it is supported for the WAR and JVM agent, where plugins are created via a simple class path lookup. For the OSGi agent it is planned that it will pick up plugins as OSGi services.&lt;/p&gt;
&lt;p&gt;Having this new infrastructure in place, extra functionality like health checks can be added easily. The GitHub repository &lt;a href="https://github.com/rhuss/jolokia-extra" target="_blank" rel="noreferrer"&gt;jolokia-extra&lt;/a&gt; was created to host various extensions to the Jolokia agent, also to keep the original agent as lean as possible. Beside the new health checks there is already an extension &lt;em&gt;jsr77&lt;/em&gt; for simplifying the access to &lt;a href="https://jcp.org/en/jsr/detail?id=77" target="_blank" rel="noreferrer"&gt;JSR-77&lt;/a&gt; compliant JEE Servers like WebSphere.&lt;/p&gt;
&lt;p&gt;The new &lt;a href="https://github.com/rhuss/jolokia-extra/tree/master/addon/health" target="_blank" rel="noreferrer"&gt;health&lt;/a&gt; addon in &lt;em&gt;jolokia-extra&lt;/em&gt; has just been started. Currently it contains not much more as proof-of-concept with some hardcoded health checks, but it already illustrate the concept: A &lt;code&gt;MBeanPlugin&lt;/code&gt; registers a certain &lt;code&gt;SampleHealthCheckMBean&lt;/code&gt; during startup which exposes the health checks as JMX operations (and which can be executed as usual with Jolokia). These operations have access to JMX via the &lt;code&gt;MBeanPluginContext&lt;/code&gt; and can query any MBean in the system.&lt;/p&gt;
&lt;p&gt;But that is only the beginning. There are still a lot of design decisions to take:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How should health check specification look like ? Should it be done via JSON or should a more expressive DSL based e.g. on Groovy should be used ?&lt;/li&gt;
&lt;li&gt;How are the health check store on the agent side ?
&lt;ul&gt;
&lt;li&gt;Looking them up in the filesystem (from a configurable path with a sane default like &lt;code&gt;~/.jolokia_healthchecks&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Baking it into the agent jar&lt;/li&gt;
&lt;li&gt;Uploading it via an MBean operation (and then storing them in the filesystem as well)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What kind of meta data should be provided so that consoles like hawt.io can dynamically create their health check views ?&lt;/li&gt;
&lt;li&gt;How should the parameter and return value for the health checks look like ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you would like to participate, the discussion about the implementation details will take place in issue &lt;a href="https://github.com/rhuss/jolokia-extra/issues/1" target="_blank" rel="noreferrer"&gt;#1&lt;/a&gt; and the current working state is summarized in this &lt;a href="https://github.com/rhuss/jolokia-extra/wiki/Health-Checks" target="_blank" rel="noreferrer"&gt;wiki page&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Real clean Maven builds with Docker</title><link>https://ro14nd.de/clean-maven-builds-with-docker/</link><pubDate>Fri, 07 Nov 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/clean-maven-builds-with-docker/</guid><description>&lt;p&gt;A local Maven repository serves as a cache for artifacts and dependencies, we all know this. This helps in speeding up things but can cause subtle problems when doing releases. Docker can help here a bit for avoiding caching issues.&lt;/p&gt;
&lt;p&gt;Before doing a release I typically move &lt;code&gt;~/.m2/repository&lt;/code&gt; away to be really sure that everybody else can build the source as well and that any dependencies are also on the remote Maven repository. This is a bit tedious, because it is a manual process and you can forget to move the old directory back which will was a LOT of disk space over time.&lt;/p&gt;
&lt;p&gt;Docker can help here a bit: Since yesterday there is an &lt;a href="https://registry.hub.docker.com/_/maven/" target="_blank" rel="noreferrer"&gt;official Maven image&lt;/a&gt; which can be used to build your project. The nice thing for doing releases with this image is, that it always starts afresh with an empty local Maven repository.&lt;/p&gt;
&lt;p&gt;Assuming you are currently located in the top-level directory holding your &lt;code&gt;pom.xml&lt;/code&gt; you can use this single command for running a real clean build:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it --rm \ 
	 -v &amp;quot;$(pwd)&amp;quot;:/usr/src/mymaven \ 
	 -w /usr/src/mymaven \ 
	 maven:3.2-jdk-7 \
	 mvn clean install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this call you mount your project directory into &lt;code&gt;/usr/src/mymaven&lt;/code&gt; on the container, change to this directory in the container and call &lt;code&gt;mvn clean install&lt;/code&gt;. At the end, your container will be removed (&lt;code&gt;--rm&lt;/code&gt;) so there is no chance that you might forget to clean up afterwards.&lt;/p&gt;
&lt;p&gt;Of course it will download all the artifacts each time, so it is not a good idea to use this approach for your daily developer business (especially if you using Maven central as remote Maven repository).&lt;/p&gt;
&lt;p&gt;You can also play around with various versions of Maven by changing the image tag so at the end you can be really sure, that your project will build everywhere. Please refer to the &lt;a href="https://registry.hub.docker.com/_/maven/" target="_blank" rel="noreferrer"&gt;Docker Hub page&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: As pointed out by &lt;a href="https://twitter.com/noahlz/status/530708791906807808" target="_blank" rel="noreferrer"&gt;Noah Zucker&lt;/a&gt; on Twitter you can redirect of course the local repository via &lt;code&gt;-Dmaven.repo.local=/tmp/clean-repo&lt;/code&gt; temporarily to a new location. Which is confessedly much simpler and I would prefer that one instead if you don&amp;rsquo;t need to check with different JDKs or Maven versions. Sometimes you don&amp;rsquo;t see the forest for the trees if you come from the wrong direction (e.g. looking for use case of a specific docker image).&lt;/p&gt;</description></item><item><title>Docker maven plugin rewrite</title><link>https://ro14nd.de/docker-maven-plugin-rewrite/</link><pubDate>Mon, 13 Oct 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/docker-maven-plugin-rewrite/</guid><description>&lt;p&gt;My &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;docker-maven-plugin&lt;/a&gt; is undergoing a major refactoring. This post explains the motivation behind this and also what you can expect in the very near future.
The configuration syntax becomes much cleaner and implicit behavior was removed.&lt;/p&gt;
&lt;p&gt;Originally, I needed a &lt;em&gt;docker-maven-plugin&lt;/em&gt; for a very specific use case: To test &lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt;, the HTTP-JMX bridge in all the JEE and non-JEE servers out there. This was a very manual process: Fire up the VirtualBox image with all those servers installed, start a server, deploy Jolokia, run integration tests, stop the server, start the next server &amp;hellip;. It takes easily half a day or more to do the tests before each release. That&amp;rsquo;s not the kind of QA you are looking for, really. But with docker there is finally the opportunity to automate all this: Deploy a single application on multiple different servers while controlling the lifecycle of theses servers from within the build.&lt;/p&gt;
&lt;p&gt;Early this year when I searched the Web, I couldn&amp;rsquo;t find a good Docker build integration, so I decided to write my own &lt;a href="http://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;docker-maven-plugin&lt;/a&gt; to back my use case. Today you find nearly a dozen of Maven plugins for your Docker business. However, only four plugins (&lt;a href="https://github.com/alexec/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;alexec&lt;/a&gt;, &lt;a href="https://github.com/wouterd/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;wouterd&lt;/a&gt;, &lt;a href="https://github.com/spotify/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;spotify&lt;/a&gt; and &lt;a href="https://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;rhuss&lt;/a&gt;) are still actively maintained. A later blog post will present a detailed comparison between those four plugins (or come to my &lt;a href="http://jax.de/wjax2014/sessions/docker-fuer-java-entwickler" target="_blank" rel="noreferrer"&gt;W-JAX&lt;/a&gt; session), but this post is about the evolution of the &lt;code&gt;rhuss&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;It turned out that the plugin works quite well, people liked and starred it on GitHub. It provides also some unique features like creating Docker images from &lt;a href="http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html" target="_blank" rel="noreferrer"&gt;assembly descriptors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But I was not so happy.&lt;/p&gt;
&lt;p&gt;The reason is, that I started from a very special, probably very uncommon use case: A single application, multiple different servers for multiples tests. A much more common scenario is to have a fixed application server brand for the application, running with multiple linked backend containers like databases. My plugin doesn&amp;rsquo;t work well with running multiple containers at once. Or to state it otherwise: The plugin was not prepared for orchestration of multiple docker containers.&lt;/p&gt;
&lt;p&gt;Also, there was too much happening &lt;em&gt;magically&lt;/em&gt; behind the scenes: When pushing a data image, it was implicitly build. When starting a container for integration test, the data container is also build before.&lt;/p&gt;
&lt;p&gt;Two operational modes were supported: One with images holding the server and data separately in two containers (linked via &lt;code&gt;volumes&lt;/code&gt;) and one so called &lt;em&gt;merged&lt;/em&gt; image, holding both, the application and server together in one image. This is perfect for creating micro services. The mode is determined only by a configuration flag (&lt;code&gt;mergeData&lt;/code&gt;), but it is not really clear how many and what Docker images are created. And it was hard to document which is always a very bad smell.&lt;/p&gt;
&lt;p&gt;So I changed the configuration syntax completely.&lt;/p&gt;
&lt;p&gt;It is now much more explicit and you will know merely by looking at the configuration which and how many containers will be started during integration testing and what the container with the application will look like. I don&amp;rsquo;t want to go into much detail here, the post is already too long. Instead here is an example of the new syntax:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;plugin&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;groupId&amp;gt;org.jolokia&amp;lt;/groupId&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;artifactId&amp;gt;docker-maven-plugin&amp;lt;/artifactId&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;version&amp;gt;0.10.1&amp;lt;/version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;configuration&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;images&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;name&amp;gt;consol/tomcat-7.0&amp;lt;/name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;run&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;volumes&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;from&amp;gt;jolokia/docker-jolokia-demo&amp;lt;/from&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/volumes&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;ports&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;port&amp;gt;jolokia.port:8080&amp;lt;/port&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/ports&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;wait&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;url&amp;gt;http://localhost:${jolokia.port}/jolokia&amp;lt;/url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;time&amp;gt;10000&amp;lt;/time&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/wait&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/run&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;name&amp;gt;jolokia/docker-jolokia-demo&amp;lt;/name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;build&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;assemblyDescriptor&amp;gt;src/main/assembly.xml&amp;lt;/assemblyDescriptor&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/build&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/images&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/configuration&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This examples creates and starts &lt;strong&gt;two&lt;/strong&gt; containers during &lt;code&gt;docker:start&lt;/code&gt;, linked together via the &lt;code&gt;volumes&lt;/code&gt; directive. The &lt;strong&gt;&lt;code&gt;&amp;lt;run&amp;gt;&lt;/code&gt;&lt;/strong&gt; configuration section is used to describe the runtime behavior for &lt;code&gt;docker:start&lt;/code&gt; and &lt;code&gt;docker:stop&lt;/code&gt;, and &lt;strong&gt;&lt;code&gt;&amp;lt;build&amp;gt;&lt;/code&gt;&lt;/strong&gt; is for specifying how images are build up during &lt;code&gt;docker:build&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively, a &lt;strong&gt;single&lt;/strong&gt; image could be created:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;plugin&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;groupId&amp;gt;org.jolokia&amp;lt;/groupId&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;artifactId&amp;gt;docker-maven-plugin&amp;lt;/artifactId&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;version&amp;gt;0.10.1&amp;lt;/version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;configuration&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;images&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;name&amp;gt;jolokia/docker-jolokia-combined-demo&amp;lt;/name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;build&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;baseImage&amp;gt;consol/tomcat-7.0&amp;lt;/baseImage&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;assemblyDescriptor&amp;gt;src/main/assembly.xml&amp;lt;/assemblyDescriptor&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/build&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;run&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;ports&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;port&amp;gt;jolokia.port:8080&amp;lt;/port&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/ports&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;wait&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;url&amp;gt;http://localhost:${jolokia.port}/jolokia&amp;lt;/url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/wait&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/run&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/image&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/images&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/configuration&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here &lt;code&gt;consol/tomcat-7.0&lt;/code&gt; is used as base for the image to build and the data referenced in the assembly descriptor is copied into the image. So there is no need to volume-link them together.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t repeat the old, more confusing syntax for this both use cases here, you find it in the current &lt;a href="http://github.com/rhuss/docker-maven-plugin" target="_blank" rel="noreferrer"&gt;online documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Said all that, and since &lt;code&gt;rhuss/docker-maven-plugin&lt;/code&gt; is still pre-1.0, I take the liberty to change it without much thoughts on backwards compatibility (you can easily update old configurations). The new syntax is available since &lt;code&gt;0.10.1&lt;/code&gt;, the old syntax will still be used in the &lt;code&gt;0.9.x&lt;/code&gt; line. Everybody is encouraged to upgrade to &lt;code&gt;0.10.x&lt;/code&gt;, although the documentation still reflects the old syntax (will be fixed soon). Please refer to the &lt;a href="https://github.com/rhuss/docker-maven-plugin/tree/new-config/samples" target="_blank" rel="noreferrer"&gt;examples&lt;/a&gt; on the &lt;code&gt;new-config&lt;/code&gt; branch for more details. An upgrade path will be available soon, too.&lt;/p&gt;
&lt;p&gt;There will be a &lt;code&gt;1.0.0&lt;/code&gt; release before the end of this year.&lt;/p&gt;
&lt;p&gt;Please let me know your feedback on the new syntax and what features you would like to see. Everything is moving before the 1.0.0 freeze. You can open an &lt;a href="http://github.com/rhuss/docker-maven-plugin/issues" target="_blank" rel="noreferrer"&gt;issue&lt;/a&gt; for any suggestion or feature request.&lt;/p&gt;</description></item><item><title>Spicy Docker Java Images with Jolokia</title><link>https://ro14nd.de/jolokia-docker-image/</link><pubDate>Thu, 09 Oct 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jolokia-docker-image/</guid><description>&lt;p&gt;While on the way of transforming the &lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; integration test suite from a tedious, manual, half-a-day procedure to a full automated process I ran into and felt in love with &lt;a href="http://docker.io" target="_blank" rel="noreferrer"&gt;Docker&lt;/a&gt;. As a byproduct a &lt;a href="https://registry.hub.docker.com/u/jolokia/java-jolokia/" target="_blank" rel="noreferrer"&gt;java-jolokia&lt;/a&gt; docker repository emerged, which can be easily used as a Java base image for enabling a Jolokia JVM agent during startup for any Java application.&lt;/p&gt;
&lt;p&gt;These images are variants of the official &lt;a href="https://registry.hub.docker.com/_/java" target="_blank" rel="noreferrer"&gt;java&lt;/a&gt; Java docker image. In order to use the Jolokia agent, a child image should call the script &lt;code&gt;jolokia_opts&lt;/code&gt; (which is in the path). This will echo all relevant startup options that should be included as argument to the Java startup command.&lt;/p&gt;
&lt;p&gt;Here is a simple example for creating a Tomcat 7 images which starts Jolokia along with Tomcat:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FROM jolokia/java-jolokia:7
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ENV TOMCAT_VERSION 7.0.55
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ENV TC apache-tomcat-${TOMCAT_VERSION}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;EXPOSE 8080 8778
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN wget http://archive.apache.org/dist/tomcat/tomcat-7/v${TOMCAT_VERSION}/bin/${TC}.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN tar xzf ${TC}.tar.gz -C /opt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CMD env CATALINA_OPTS=$(jolokia_opts) /opt/${TC}/bin/catalina.sh run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(&lt;em&gt;Don&amp;rsquo;t forget to use &lt;code&gt;$(jolokia_opts)&lt;/code&gt; or with backticks, but not &lt;code&gt;${jolokia_opts}&lt;/code&gt;&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;The configuration of the Jolokia agent can be influenced with various environments variables which can be given when starting the container:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_OFF&lt;/code&gt; : If set disables activation of Jolokia. By default, Jolokia is enabled.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_CONFIG&lt;/code&gt; : If set uses this file (including path) as Jolokia JVM agent properties (as described in Jolokia&amp;rsquo;s &lt;a href="http://www.jolokia.org/reference/html/agents.html#agents-jvm" target="_blank" rel="noreferrer"&gt;reference manual&lt;/a&gt;. By default this is &lt;code&gt;/opt/jolokia/jolokia.properties&lt;/code&gt;. If this file exists, it will automatically be taken as configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_HOST&lt;/code&gt; : Host address to bind to (Default: 0.0.0.0)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_PORT&lt;/code&gt; : Port to use (Default: 8778)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_USER&lt;/code&gt; : User for authentication. By default authentication is switched off.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOLOKIA_PASSWORD&lt;/code&gt; : Password for authentication. By default authentication is switched off.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, if you start your tomcat with &lt;code&gt;docker run -e JOLOKIA_OFF&lt;/code&gt; no agent will be started.&lt;/p&gt;
&lt;p&gt;Currently this image is available from &lt;a href="https://registry.hub.docker.com/u/jolokia/java-jolokia/" target="_blank" rel="noreferrer"&gt;Docker Hub&lt;/a&gt; for the latest versions of Java 6,7 and 8, respectively, as they are provided by the official Docker &lt;a href="https://registry.hub.docker.com/_/java/" target="_blank" rel="noreferrer"&gt;java&lt;/a&gt; image.&lt;/p&gt;
&lt;p&gt;Other base images can be easily added by using the configuration and templates from a super simple node based &lt;a href="https://github.com/rhuss/docker-java-jolokia" target="_blank" rel="noreferrer"&gt;build system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All appserver images from &lt;a href="https://github.com/ConSol/docker-appserver" target="_blank" rel="noreferrer"&gt;ConSol/docker-appserver&lt;/a&gt; (&lt;a href="https://registry.hub.docker.com/repos/consol/" target="_blank" rel="noreferrer"&gt;Docker Hub&lt;/a&gt;) are based now on this image, so Jolokia will always be by your side ;-)&lt;/p&gt;</description></item><item><title>Using NSEnter with Boot2Docker</title><link>https://ro14nd.de/nsenter-with-boot2docker/</link><pubDate>Mon, 01 Sep 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/nsenter-with-boot2docker/</guid><description>&lt;p&gt;&lt;a href="https://github.com/jpetazzo/nsenter" target="_blank" rel="noreferrer"&gt;NSEnter&lt;/a&gt; is a nice way to connect to a running Docker container. This post presents a script to simplify the usage of &lt;code&gt;nsenter&lt;/code&gt; together with &lt;a href="https://github.com/boot2docker/boot2docker" target="_blank" rel="noreferrer"&gt;Boot2Docker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is still quite some dust around Docker and after gaining more and more experience, new patterns and anti-patterns are emerging.&lt;/p&gt;
&lt;p&gt;One of those anti-patterns is the usage of an SSH daemon inside an image for debugging, backup and troubleshooting purposes. Jérôme Petazzoni&amp;rsquo;s &lt;a href="https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/" target="_blank" rel="noreferrer"&gt;Blog Post&lt;/a&gt; explains this nicely. In addition it provides proper solutions for common use cases for which SSH is currently used.&lt;/p&gt;
&lt;p&gt;Nevertheless I still have this irresistible urge to login into a container. And if it is only for looking around and checking out the environment (call me old-fashioned, that&amp;rsquo;s ok ;-)&lt;/p&gt;
&lt;p&gt;Luckily Jérôme provides a perfect solution to satisfy this thirst: &lt;a href="https://github.com/jpetazzo/nsenter" target="_blank" rel="noreferrer"&gt;nsenter&lt;/a&gt;. This allows you to &lt;strong&gt;enter&lt;/strong&gt; into container &lt;strong&gt;n&lt;/strong&gt;ame&lt;strong&gt;s&lt;/strong&gt;paces. On the GitHub page you find the corresponding recipe for installing and using &lt;code&gt;nsenter&lt;/code&gt; on a Linux host.&lt;/p&gt;
&lt;p&gt;If you want to use it from OS X with e.g. &lt;a href="https://github.com/boot2docker/boot2docker" target="_blank" rel="noreferrer"&gt;Boot2Docker&lt;/a&gt; you need to login into the VM hosting the Docker daemon and then connect to a running container.&lt;/p&gt;
&lt;p&gt;As described in the &lt;a href="https://github.com/jpetazzo/nsenter#docker-enter-with-boot2docker" target="_blank" rel="noreferrer"&gt;NSenter README&lt;/a&gt; you can use a simple alias for doing this transparently&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker-enter&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 boot2docker ssh &lt;span style="color:#e6db74"&gt;&amp;#39;[ -f /var/lib/boot2docker/nsenter ] || docker run --rm -v /var/lib/boot2docker/:/target jpetazzo/nsenter&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	 boot2docker ssh -t sudo /var/lib/boot2docker/docker-enter &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$@&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For a bit more comfort with usage information and error checking you can convert this to a small shell script like &lt;a href="https://gist.github.com/rhuss/a8a40bd143001fd5c83c#file-docker-enter" target="_blank" rel="noreferrer"&gt;docker-enter&lt;/a&gt; which needs to be installed within the path (on OS X). As arguments it expects a container id or name and optionally a command (with args) to execute in the container. This script also will automatically install &lt;code&gt;nsenter&lt;/code&gt; on the boot2docker VM if not already present (like the shell function above does this as well):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	10:20 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;~&lt;span style="color:#f92672"&gt;]&lt;/span&gt; $ docker ps -q
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	5bf8a161cceb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	10:20 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;~&lt;span style="color:#f92672"&gt;]&lt;/span&gt; $ docker-enter 5bf8a161cceb bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Unable to find image &lt;span style="color:#e6db74"&gt;&amp;#39;jpetazzo/nsenter&amp;#39;&lt;/span&gt; locally
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Pulling repository jpetazzo/nsenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Installing nsenter to /target
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Installing docker-enter to /target
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	root@5bf8a161cceb:/#&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you want even more comfort with bash completion you can add the small Bash completion script &lt;a href="https://gist.github.com/rhuss/a8a40bd143001fd5c83c#file-docker-enter_commands" target="_blank" rel="noreferrer"&gt;docker-enter_commands&lt;/a&gt; (inspired by and copied from &lt;a href="https://github.com/docker/docker/blob/master/contrib/completion/bash/docker" target="_blank" rel="noreferrer"&gt;Docker&amp;rsquo;s bash completion&lt;/a&gt;) to your &lt;code&gt;~/.bash_completion_scripts/&lt;/code&gt; directory (or wherever your completion scripts are located, e.g. &lt;code&gt;/usr/local/etc/bash_completion.d&lt;/code&gt; if you installed &lt;code&gt;bash-completion&lt;/code&gt; via brew). This setup completes on container names and ids on the arguments for &lt;code&gt;docker-enter&lt;/code&gt;. Alternatively you can put the commands together with the shell function code above directly into your &lt;code&gt;~/.bashrc&lt;/code&gt;, too.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;P.S. After writing this post, I&amp;rsquo;ve found out, that this topic has been already covered in another &lt;a href="http://blog.sequenceiq.com/blog/2014/07/05/docker-debug-with-nsenter-on-boot2docker/" target="_blank" rel="noreferrer"&gt;blog post&lt;/a&gt; previously by Lajos Papp. That&amp;rsquo;s also where the shell function definition in the &lt;code&gt;nsenter&lt;/code&gt; README originates from. Give credit to whom it’s due.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Docker for (Java) Developers</title><link>https://ro14nd.de/docker-for-developers/</link><pubDate>Mon, 25 Aug 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/docker-for-developers/</guid><description>&lt;p&gt;Recently I gave a Meetup talk for the &lt;a href="http://www.meetup.com/Docker-Munich/" target="_blank" rel="noreferrer"&gt;Docker Munich&lt;/a&gt; Meetup Group which explained how Docker can help developers to improve integration tests and to ship applications.&lt;/p&gt;
&lt;p&gt;The &lt;a href="http://ro14nd.de/meetup-docker-developer-slides" target="_blank" rel="noreferrer"&gt;slides&lt;/a&gt; are online as well as the &lt;a href="https://github.com/rhuss/meetup-docker-demo" target="_blank" rel="noreferrer"&gt;demo project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;During the demo I used &lt;a href="https://github.com/paradoxxxzero/butterfly" target="_blank" rel="noreferrer"&gt;Butterfly&lt;/a&gt; for an in-browser shell, which was quite cool, I guess ;-) (This is obviously not enabled in the online slides).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to continue to celebrate my Docker-♡ with another two talks in autumn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://onedaytalk.org/index.php/program?id=194" target="_blank" rel="noreferrer"&gt;Boosting your developer toolbox with Docker&lt;/a&gt; at JBoss One Day Talk, September, 29. in Germering (Munich, Germany)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jax.de/wjax2014/sessions/docker-fuer-java-entwickler" target="_blank" rel="noreferrer"&gt;Docker für Java Entwickler&lt;/a&gt; (in german) at W-JAX 14, November, 3. - 7. in Munich&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And there is a slight chance (since the CFP has not yet been declined ;-) to talk at &lt;a href="http://www.devoxx.be/" target="_blank" rel="noreferrer"&gt;Devoxx&lt;/a&gt; about Docker. &lt;a href="http://2014.javazone.no/" target="_blank" rel="noreferrer"&gt;JavaZone&lt;/a&gt; unfortunately declined my CFP, I guess there are already too many riding the docker horse (which is a good thing).&lt;/p&gt;
&lt;p&gt;Nevertheless I will attend both conferences with talks about &lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt;, and I&amp;rsquo;m really looking forward to it.&lt;/p&gt;
&lt;p&gt;&amp;lsquo;guess it will become a hot autumn (hotter than this german 2014 summer for sure) &amp;hellip;.&lt;/p&gt;</description></item><item><title>Jolokia and CORS</title><link>https://ro14nd.de/jolokia-cors/</link><pubDate>Mon, 18 Aug 2014 00:00:00 +0000</pubDate><guid>https://ro14nd.de/jolokia-cors/</guid><description>&lt;p&gt;&lt;a href="http://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; has configurable &lt;a href="http://www.w3.org/TR/cors/" target="_blank" rel="noreferrer"&gt;CORS&lt;/a&gt; support so that it plays nicely together with the Browser world when it comes to cross origin requests. However, Jolokia’s CORS support is not without gotchas. This post explains how Jolokias CORS supports works, what are the issues and how I plan to solve them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt; &lt;em&gt;Jolokia CORS support is configured via &lt;code&gt;jolokia-access.xml&lt;/code&gt; but has issues with authenticated requests which are tackled for the next release 1.3.0&lt;/em&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;CORS Primer
 &lt;div id="cors-primer" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cors-primer" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.w3.org/TR/cors/" target="_blank" rel="noreferrer"&gt;CORS&lt;/a&gt; (Cross Origin Resource Sharing) is a specification for browsers to allow controlled access for JavaScript code to locations which are different than the origin of the JavaScript code itself.&lt;/p&gt;
&lt;p&gt;In simple cases, it works more or less like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A JavaScript code (coming from the original location &lt;em&gt;&lt;a href="http://a.com" target="_blank" rel="noreferrer"&gt;http://a.com&lt;/a&gt;&lt;/em&gt;) requests HTTP access via &lt;code&gt;XMLHttpRequest&lt;/code&gt; to &lt;em&gt;&lt;a href="http://b.com" target="_blank" rel="noreferrer"&gt;http://b.com&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Since the the origin of the script and target URL of the request differs, the browser adds some extract checking on the response of this request.&lt;/li&gt;
&lt;li&gt;The request to &lt;em&gt;b.com&lt;/em&gt; contains a header &lt;code&gt;Origin: http://a.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server at &lt;em&gt;b.com&lt;/em&gt; answering the request has to decided upon this header whether it wants allow this request. The server decision is contained in the response header &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The value of this header can be either a literal URL (e.g. &lt;em&gt;&lt;a href="http://a.com" target="_blank" rel="noreferrer"&gt;http://a.com&lt;/a&gt;&lt;/em&gt;) or a wildcard like in &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; which allows access from any original location.&lt;/li&gt;
&lt;li&gt;The browser finally decides whether it returns the response to the JavaScript based on the returned access control header. If not, is throws an exception before handing out the response data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is it for &lt;em&gt;simple requests&lt;/em&gt;. A simple request has the following characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP method is either &lt;code&gt;GET&lt;/code&gt;,&lt;code&gt;HEAD&lt;/code&gt; or &lt;code&gt;POST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The request contains only the following headers
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Accept&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Accept-Language&lt;/code&gt; or &lt;code&gt;Content-Language&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Type&lt;/code&gt; with the value &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;, &lt;code&gt;multipart/form-data&lt;/code&gt; or &lt;code&gt;text/plain&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this criteria are not match for a request (e.g. because it uses a different method or additional headers), a so called &lt;a href="http://www.w3.org/TR/cors/#resource-preflight-requests" target="_blank" rel="noreferrer"&gt;preflight request&lt;/a&gt; is sent to the server before the actual request is performed. The preflight is an HTTP request with method &lt;code&gt;OPTIONS&lt;/code&gt; and contains the headers &lt;code&gt;Origin&lt;/code&gt; (&lt;em&gt;&lt;a href="http://a.com" target="_blank" rel="noreferrer"&gt;http://a.com&lt;/a&gt;&lt;/em&gt; in our case), &lt;code&gt;Access-Control-Request-Method&lt;/code&gt; for the HTTP method requested and &lt;code&gt;Access-Control-Request-Headers&lt;/code&gt; with a comma separated list of additional header names. The server in turn answers with the allowed request methods and headers, whether an authenticated request is allowed and how long the client might cache this answer. An important point is, that a preflight request &lt;a href="http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0" target="_blank" rel="noreferrer"&gt;must not be authenticated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And in fact, browsers never sent an authentication header with the preflight request even when already authenticated against the target server. More on this later.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Jolokia CORS Support
 &lt;div id="jolokia-cors-support" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#jolokia-cors-support" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;By default, Jolokia allows any CORS request. For the preflight the agent answers with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Origin: http://a.com
Access-Control-Allow-Headers: accept, authorization, content-type
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The allowed headers returned are exactly the same headers as requested. For the real request with an origin header &lt;code&gt;Origin: http://a.com&lt;/code&gt; the answer is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Access-Control-Allow-Origin: http://a.com
Access-Control-Allow-Credentials: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For best computability Jolokia always answers with the provided &lt;code&gt;Origin:&lt;/code&gt; which is extracted from the request (except when the origin is &lt;code&gt;null&lt;/code&gt; in which case the wildcard &lt;code&gt;*&lt;/code&gt; is returned.&lt;/p&gt;
&lt;p&gt;This behavior can be tuned by adapting the &lt;code&gt;jolokia-access.xml&lt;/code&gt; policy as described in the &lt;a href="http://www.jolokia.org/reference/html/security.html#security-policy" target="_blank" rel="noreferrer"&gt;reference manual&lt;/a&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;cors&amp;gt;
 &amp;lt;allow-origin&amp;gt;http://www.jolokia.org&amp;lt;/allow-origin&amp;gt;
 &amp;lt;allow-origin&amp;gt;*://*.jmx4perl.org&amp;lt;/allow-origin&amp;gt;

 &amp;lt;strict-checking/&amp;gt;
&amp;lt;/cors&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a &lt;code&gt;&amp;lt;cors&amp;gt;&lt;/code&gt; section is present in &lt;code&gt;jolokia-access.xml&lt;/code&gt; then only those hosts declared in this sections are allowed. The Origin URLs to match against can be specified either literally or as pattern containing the wildcard &lt;code&gt;*&lt;/code&gt;. The optional declaration &lt;code&gt;&amp;lt;strict-checking/&amp;gt;&lt;/code&gt; is not really connected to CORS but helps in defending against &lt;a href="http://de.wikipedia.org/wiki/Cross-Site-Request-Forgery" target="_blank" rel="noreferrer"&gt;Cross-Site-Request-Forgery&lt;/a&gt; (CSRF). If this option is given, then the given patterns are used for &lt;strong&gt;every&lt;/strong&gt; request to compare it against the &lt;code&gt;Origin:&lt;/code&gt; or &lt;code&gt;Referer:&lt;/code&gt; header (not only for CORS requests).&lt;/p&gt;

&lt;h2 class="relative group"&gt;CORS and Authentication
 &lt;div id="cors-and-authentication" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#cors-and-authentication" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Since &lt;code&gt;Authorization:&lt;/code&gt; is for CORS not a &lt;em&gt;simple&lt;/em&gt; header, when authentication is used, preflight checking is always applied. However, there is often a &lt;a href="https://code.google.com/p/twitter-api/issues/detail?id=2273" target="_blank" rel="noreferrer"&gt;catch 22&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The preflight check using the &lt;code&gt;OPTIONS&lt;/code&gt; HTTP Method &lt;strong&gt;must not be authenticated&lt;/strong&gt; as explained above, so browser doesn’t send the appropriate authentication headers when doing the preflight.&lt;/li&gt;
&lt;li&gt;The Jolokia agent is typically secured completely no matter which HTTP method is used.&lt;/li&gt;
&lt;li&gt;The preflight check fails, the request fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The only clean solution is to setup Jolokia Authentication that way that &lt;code&gt;OPTIONS&lt;/code&gt; request are not secured.&lt;/p&gt;
&lt;p&gt;Let’s have a look at the individual Agents:&lt;/p&gt;

&lt;h3 class="relative group"&gt;JVM Agent
 &lt;div id="jvm-agent" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#jvm-agent" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Since the JVM agent does all the security stuff on its own, it is not a big deal to introduce this specific behavior. Next one.&lt;/p&gt;

&lt;h3 class="relative group"&gt;WAR Agent
 &lt;div id="war-agent" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#war-agent" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;The WAR agent use authentication and authorization as defined in the &lt;a href="https://jcp.org/aboutJava/communityprocess/final/jsr315/" target="_blank" rel="noreferrer"&gt;Servlet Specification&lt;/a&gt;, i.e. the appropriate &lt;code&gt;&amp;lt;security-constraint&amp;gt;&lt;/code&gt; must be added manually to the web.xml (&lt;a href="http://search.cpan.org/~roland/jmx4perl/scripts/jolokia" target="_blank" rel="noreferrer"&gt;jolokia&lt;/a&gt; is a CLI tool which helps in this repackaging). Unfortunately there is no way to secure the same &lt;code&gt;&amp;lt;url-pattern&amp;gt;&lt;/code&gt; differently for different HTTP Methods (i.e. secured with an &lt;code&gt;&amp;lt;auth-constraint&amp;gt;&lt;/code&gt; for &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;, but accessible for everybody for &lt;code&gt;OPTIONS&lt;/code&gt;). I tried hard by providing multiple &lt;code&gt;&amp;lt;security-constraint&amp;gt;&lt;/code&gt; but failed miserably (if you know how to this, please let me know).&lt;/p&gt;
&lt;p&gt;The only solution is to switch over to checking a given role on our own without relying on the declarative JEE security mechanism. Since we can check the role programmatically (&lt;code&gt;HttpServletRequest.isUserInRole()&lt;/code&gt;) this should not be that big deal. But it’s still some work ….&lt;/p&gt;

&lt;h3 class="relative group"&gt;OSGi Agent
 &lt;div id="osgi-agent" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#osgi-agent" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;When using an &lt;a href="http://www.osgi.org/javadoc/r4v42/org/osgi/service/http/HttpService.html" target="_blank" rel="noreferrer"&gt;OSGI HttpService&lt;/a&gt; adding this behavior should not be difficult since security is handled programmatically here as well (&lt;code&gt;HttpContext.handleSecurity()&lt;/code&gt;)&lt;/p&gt;

&lt;h3 class="relative group"&gt;Other Agent variants
 &lt;div id="other-agent-variants" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#other-agent-variants" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;This is the dark matter, because I don’t know where and how Jolokia is integrated directly into a bigger context. I know that &lt;a href="http://activemq.apache.org/rest.html" target="_blank" rel="noreferrer"&gt;ActiveMQ&lt;/a&gt;, &lt;a href="http://karaf.apache.org/manual/latest/users-guide/monitoring.html" target="_blank" rel="noreferrer"&gt;Karaf&lt;/a&gt; and &lt;a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc" target="_blank" rel="noreferrer"&gt;Spring Boot&lt;/a&gt; uses Jolokia internally. In order to support authenticated CORS access they probably needs to be changed to allow unauthorized &lt;code&gt;OPTIONS&lt;/code&gt; access for everybody. Since this is not under my control I have no idea when and even whether it ever will happen. Generic purpose console like &lt;a href="http://hawt.io" target="_blank" rel="noreferrer"&gt;hawt.io&lt;/a&gt; rely in some setups on CORS access so it would be real cool if we can get it out there. Help with this is highly appreciated ;-)&lt;/p&gt;

&lt;h2 class="relative group"&gt;Roadmap
 &lt;div id="roadmap" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#roadmap" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Since 1.2.2 is already finished and about to be published today, the stuff I can do as described above will go into a 1.3.0. Looking back at my release history this will probably be ready approx. end of august.&lt;/p&gt;</description></item><item><title>Removing attachments with JavaMail</title><link>https://ro14nd.de/removing-attachments-with-javamail/</link><pubDate>Mon, 29 Mar 2010 00:00:00 +0000</pubDate><guid>https://ro14nd.de/removing-attachments-with-javamail/</guid><description>&lt;p&gt;If you have ever sent or received mail messages via Java, chances are high that you have used JavaMail for this task.
Most of the time JavaMail does an excellent job and a lot of use cases are described in the JavaMail FAQ.
But there are still some additional quirks you should be aware of when doing advanced mail operations like adding or removing attachments (or “Parts”) from existing mails retreived from some IMAP or POP3 store.
This post gives a showcase for how to remove an attachment from a mail at an arbitrary level which has been obtained from an IMAP store.&lt;/p&gt;
&lt;p&gt;It points to the pitfalls which are waiting and shows some possible solutions.
The principles laid out here are important for adding new attachments to a mail as well, but that’s yet another story.&lt;/p&gt;

&lt;h3 class="relative group"&gt;JavaMail objects
 &lt;div id="javamail-objects" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#javamail-objects" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Before we start manipulating mail messages it is important to understand how these are represented in the JavaMail world.&lt;/p&gt;
&lt;p&gt;The starting point is the &lt;code&gt;Message&lt;/code&gt;. It has a content and a content type. The content can be any Java object representing the mail content, like a plain text (&lt;code&gt;String&lt;/code&gt;) or raw image data. But it can also be a &lt;code&gt;Multipart&lt;/code&gt; object: this is the case when a message’s content consists of more than a single item. A &lt;code&gt;Multipart&lt;/code&gt; object is a container which holds one ore more &lt;code&gt;BodyPart&lt;/code&gt; objects. These &lt;code&gt;BodyParts&lt;/code&gt;, like a &lt;code&gt;Message&lt;/code&gt;, have a content and a content type (in fact, both &lt;code&gt;Message&lt;/code&gt; and &lt;code&gt;BodyPart&lt;/code&gt; implement the same interface &lt;code&gt;Part&lt;/code&gt; which carries these properties).&lt;/p&gt;
&lt;p&gt;Beside plain content, A &lt;code&gt;BodyPart&lt;/code&gt; can contain another &lt;code&gt;Multipart&lt;/code&gt; or even another &lt;code&gt;Message&lt;/code&gt;, a so called &lt;em&gt;nested message&lt;/em&gt; (e.g. a message forwarded as attachment) with content type &lt;code&gt;message/rfc822&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As you can see, the structure of a &lt;code&gt;Message&lt;/code&gt; can be rather heterogenous, a tree with nodes of different types. The following picture illustrates the tree structure for a sample message.&lt;/p&gt;
&lt;img src="https://ro14nd.de/images/removing-attachments-with-javamail/javamail-message.png" style="margin: auto;"/&gt;
&lt;p&gt;This object tree can be navigated in both directions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;getContent()&lt;/em&gt; on &lt;code&gt;Part&lt;/code&gt;s like &lt;code&gt;Message&lt;/code&gt; or &lt;code&gt;BodyPart&lt;/code&gt; to get to the child of this node. The return type is a &lt;code&gt;java.lang.Object&lt;/code&gt; and in case of a plain &lt;code&gt;BodyPart&lt;/code&gt; can be quite huge. Before calling &lt;code&gt;Part.getContent()&lt;/code&gt; be sure to check whether it contains a container by checking for its content type via &lt;code&gt;Part.isMimeType(&amp;quot;multipart/*&amp;quot;)&lt;/code&gt; or &lt;code&gt;Part.isMimeType(&amp;quot;message/rfc822&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;getParent()&lt;/em&gt; on &lt;code&gt;Multipart&lt;/code&gt; or &lt;code&gt;BodyPart&lt;/code&gt; returns the parent node, which is of type &lt;code&gt;BodyPart&lt;/code&gt;. Note that there is no way to get from a nested &lt;code&gt;Message&lt;/code&gt; to its parent &lt;code&gt;BodyPart&lt;/code&gt;. If you need to traverse the tree upwards with nested messages on the way, you first have to extract the path to this node from the top down. E.g. while identifying the part to remove you could store the parent &lt;code&gt;BodyPart&lt;/code&gt;s on a stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;First approach
 &lt;div id="first-approach" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#first-approach" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Back to our use case of removing an attachment at an arbitrary level within a mail. First, a &lt;code&gt;Message&lt;/code&gt; from the IMAP Store needs to be obtained, e.g. by looking it up in an &lt;code&gt;IMAPFolder&lt;/code&gt; via its UID:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Session session &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Session.&lt;span style="color:#a6e22e"&gt;getDefaultInstance&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Properties());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Store store &lt;span style="color:#f92672"&gt;=&lt;/span&gt; session.&lt;span style="color:#a6e22e"&gt;getStore&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;imap&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;store.&lt;span style="color:#a6e22e"&gt;connect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;imap.example.com&amp;#34;&lt;/span&gt;,&lt;span style="color:#f92672"&gt;-&lt;/span&gt;1,&lt;span style="color:#e6db74"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IMAPFolder folder &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (IMAPFolder) store.&lt;span style="color:#a6e22e"&gt;getFolder&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;INBOX&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IMAPMessage originalMessage &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (IMAPMessage) folder.&lt;span style="color:#a6e22e"&gt;getMessageByUID&lt;/span&gt;(42L);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Next, the fetched message is copied over to a fresh &lt;code&gt;MimeMessage&lt;/code&gt; since the &lt;code&gt;IMAPMimeMessage&lt;/code&gt; obtained from the store is marked as read-only and can&amp;rsquo;t be modified:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Message message &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; MimeMessage(originalMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Mark original message for a later expunge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;originalMessage.&lt;span style="color:#a6e22e"&gt;setFlag&lt;/span&gt;(Flags.&lt;span style="color:#a6e22e"&gt;Flag&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DELETED&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now the part to be removed needs to be identified. The detailed code is not shown here, but it is straight forward: You need to traverse the cloned &lt;code&gt;Message&lt;/code&gt; top down to identify the &lt;code&gt;Part&lt;/code&gt;, e.g. by its part number (a positional index) or by its content id. Be careful, though, not to call &lt;code&gt;getContent()&lt;/code&gt; except for &lt;code&gt;BodyPart&lt;/code&gt;s of type &lt;code&gt;multipart/*&lt;/code&gt; or &lt;code&gt;message/rfc822&lt;/code&gt;, since this would trigger a lazy fetch of the part&amp;rsquo;s content into memory. Probably not something you want to do while looking up a part. I think, I already said this. ;-)&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MimePart partToRemove &lt;span style="color:#f92672"&gt;=&lt;/span&gt; partExtractor.&lt;span style="color:#a6e22e"&gt;getPartByPartNr&lt;/span&gt;(message,&lt;span style="color:#e6db74"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It&amp;rsquo;s time to remove the body part from its parent in the hierarchy and store the changed message back into the store. You can mark the original message as &lt;code&gt;DELETED&lt;/code&gt; and expunge it on the folder. If you have the &lt;em&gt;UIDEXTENSION&lt;/em&gt; available on your IMAP store, you can selectively delete this single message, otherwise your only choice is to remove all messages marked as deleted at once (&amp;ldquo;Empty Trash&amp;rdquo;).&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Multipart parent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; partToRemove.&lt;span style="color:#a6e22e"&gt;getParent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parent.&lt;span style="color:#a6e22e"&gt;removeBodyPart&lt;/span&gt;(partToRemove);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Update headers and append new message to folder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;message.&lt;span style="color:#a6e22e"&gt;saveChanges&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;folder.&lt;span style="color:#a6e22e"&gt;appendMessages&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Message&lt;span style="color:#f92672"&gt;[]&lt;/span&gt; { message });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Mark as deleted and expunge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;originalMessage.&lt;span style="color:#a6e22e"&gt;setFlag&lt;/span&gt;(Flags.&lt;span style="color:#a6e22e"&gt;Flag&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DELETED&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;folder.&lt;span style="color:#a6e22e"&gt;expunge&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Message&lt;span style="color:#f92672"&gt;[]&lt;/span&gt;{ originalMessage });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We are done now.&lt;/p&gt;

&lt;h3 class="relative group"&gt;But wait, that&amp;rsquo;s not good enough &amp;hellip;
 &lt;div id="but-wait-thats-not-good-enough-" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#but-wait-thats-not-good-enough-" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;If you try the code above, you will probably be a bit surprised. If you fetch back the newly saved message from the folder, you will find that the attachment has &lt;strong&gt;not&lt;/strong&gt; been removed at all.&lt;/p&gt;
&lt;p&gt;Dragons are waiting here.&lt;/p&gt;
&lt;p&gt;The problem is that a JavaMail Part does heavy internal caching: it keeps a so called &lt;em&gt;content stream&lt;/em&gt; until a new &lt;em&gt;content&lt;/em&gt; is set for it.
So even if you modify the hierarchy of objects as described above, the original content is kept until you update the content of the parents yourself and the cache is thrown away.
Our part has not been removed because the cached content stream has not yet been invalidated. The solution is to get rid of the cached content stream (aka &amp;lsquo;refresh the message&amp;rsquo;).
You could set the content directly via &lt;code&gt;Part.setContent(oldPart.getContent(),oldPart.getContentType())&lt;/code&gt;, but this is dangerous in so far as it will load the part content into memory. (Did I already mention this?)
That&amp;rsquo;s really not something you are keen on if you want to remove this Britney Spears Video to save some IMAP space.
The alternative is to work on the wrapped &lt;code&gt;DataHandler&lt;/code&gt; only. A &lt;code&gt;DataHandler&lt;/code&gt; (defined in &lt;a href="http://java.sun.com/javase/technologies/desktop/javabeans/glasgow/javadocs/javax/activation/package-summary.html" target="_blank" rel="noreferrer"&gt;Java Activation&lt;/a&gt;) is not much more than a &lt;em&gt;reference&lt;/em&gt; to the content stream. Setting the &lt;code&gt;DataHandler&lt;/code&gt; on a &lt;code&gt;Part&lt;/code&gt; via &lt;code&gt;Part.setDataHandler()&lt;/code&gt; also causes it to invalidate its cached content, so a later &lt;code&gt;Part.writeTo()&lt;/code&gt; will stream out the new content.
Unfortunately, this has to be done on every parent up to the root. A brute force solution is to start from the top and refresh every content with&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Recursively go through and save all changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (message.&lt;span style="color:#a6e22e"&gt;isMimeType&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;multipart/*&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refreshRecursively((Multipart) message.&lt;span style="color:#a6e22e"&gt;getContent&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Multipart part &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (Multipart) message.&lt;span style="color:#a6e22e"&gt;getContent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;message.&lt;span style="color:#a6e22e"&gt;setContent&lt;/span&gt;(part);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;message.&lt;span style="color:#a6e22e"&gt;saveChanges&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;refreshRecursively&lt;/span&gt;(Multipart pPart)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throws&lt;/span&gt; MessagingException, IOException {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i&lt;span style="color:#f92672"&gt;=&lt;/span&gt;0;i&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;pPart.&lt;span style="color:#a6e22e"&gt;getCount&lt;/span&gt;();i&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MimeBodyPart body &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (MimeBodyPart) pPart.&lt;span style="color:#a6e22e"&gt;getBodyPart&lt;/span&gt;(i);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (body.&lt;span style="color:#a6e22e"&gt;isMimeType&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;message/rfc822&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Refresh a nested message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Message nestedMsg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (Message) body.&lt;span style="color:#a6e22e"&gt;getContent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (nestedMsg.&lt;span style="color:#a6e22e"&gt;isMimeType&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;multipart/*&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Multipart mPart &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (Multipart) body.&lt;span style="color:#a6e22e"&gt;getContent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refreshRecursively(mPart);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nestedMsg.&lt;span style="color:#a6e22e"&gt;setContent&lt;/span&gt;(mPart);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nestedMsg.&lt;span style="color:#a6e22e"&gt;saveChanges&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (body.&lt;span style="color:#a6e22e"&gt;isMimeType&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;multipart/*&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Multipart mPart &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (Multipart) body.&lt;span style="color:#a6e22e"&gt;getContent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refreshRecursively(mPart);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; body.&lt;span style="color:#a6e22e"&gt;setDataHandler&lt;/span&gt;(body.&lt;span style="color:#a6e22e"&gt;getDataHandler&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;However, we can be smarter here: Since we already identified the part to remove, we can make our way upwards to the root message via the &lt;code&gt;getParent()&lt;/code&gt; method on &lt;code&gt;Multipart&lt;/code&gt; and &lt;code&gt;BodyPart&lt;/code&gt; (which, by the way are not connected via any interface or inheritance relationship).&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BodyPart bodyParent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Multipart multipart &lt;span style="color:#f92672"&gt;=&lt;/span&gt; parent;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;do&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (multipart.&lt;span style="color:#a6e22e"&gt;getParent&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;instanceof&lt;/span&gt; BodyPart) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bodyParent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (BodyPart) multipart.&lt;span style="color:#a6e22e"&gt;getParent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bodyParent.&lt;span style="color:#a6e22e"&gt;setDataHandler&lt;/span&gt;(bodyParent.&lt;span style="color:#a6e22e"&gt;getDataHandler&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; multipart &lt;span style="color:#f92672"&gt;=&lt;/span&gt; bodyParent.&lt;span style="color:#a6e22e"&gt;getParent&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// It&amp;#39;s a Message, probably the toplevel message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// but could be a nested message, too (in which&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// case we have to stop here, too)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bodyParent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (bodyParent &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Finally you need to update the uppermost message headers, too with a&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MimeMessage.&lt;span style="color:#a6e22e"&gt;saveChanges&lt;/span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you might have noticed, this works as long as there is no nested message in the chain of &lt;code&gt;BodyPart&lt;/code&gt;s up to the root.
Since a &lt;code&gt;Message&lt;/code&gt; doesn&amp;rsquo;t have any parent, we need some other means to get the &lt;code&gt;BodyPart&lt;/code&gt; which is the parent of an enclosed &lt;code&gt;Message&lt;/code&gt;. One way is to keep track of the chain of parent &lt;code&gt;BodyPart&lt;/code&gt;s when identifying the part to remove e.g. by extending the part extractor to support a stack of parent &lt;code&gt;BodyParts&lt;/code&gt; which will be in:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Stack&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;MimeBodyPart&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; parentBodys &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Stack&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;MimeBodyPart&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MimePart partToRemove &lt;span style="color:#f92672"&gt;=&lt;/span&gt; partExtractor.&lt;span style="color:#a6e22e"&gt;getPartByPartNr&lt;/span&gt;(message,&lt;span style="color:#e6db74"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;,parentBodys);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ....&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This example could be extended to remove multipart containers on the fly, if only one part is left after removal and replace the multipart with its then last child or remove an empty multipart altogether when its last child has been removed.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Summary
 &lt;div id="summary" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#summary" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Hopefully, I could sketch out that there are several points to take care of when manipulating existing JavaMail &lt;code&gt;Messages&lt;/code&gt; (it&amp;rsquo;s not that difficult if you build up one from scratch). The code shown above is only a starting point, but it hopefully saves you some time when you start wondering why on earth your nicely trimmed message isn&amp;rsquo;t stored correctly on the IMAP store.&lt;/p&gt;</description></item><item><title>About</title><link>https://ro14nd.de/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ro14nd.de/about/</guid><description>&lt;p&gt;This blog is where I think out loud about how AI is changing the way we build software. Most posts circle around context engineering, spec-driven development, and the practical reality of running AI agents at scale. Some are technical, some are opinion, all come from daily experience rather than theory.&lt;/p&gt;

&lt;h3 class="relative group"&gt;About me
 &lt;div id="about-me" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#about-me" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m Roland Huß, Distinguished Engineer at &lt;a href="https://www.redhat.com" target="_blank" rel="noreferrer"&gt;Red Hat&lt;/a&gt;, working on Agent Ops within Red Hat AI. My current focus is integrating agentic workflows into &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift" target="_blank" rel="noreferrer"&gt;OpenShift&lt;/a&gt; and figuring out what changes (and what breaks) when you let AI agents run unsupervised in production pipelines.&lt;/p&gt;
&lt;p&gt;Before the AI shift, I spent over 25 years building developer tooling, cloud-native infrastructure, and open source projects. I created &lt;a href="https://www.jolokia.org" target="_blank" rel="noreferrer"&gt;Jolokia&lt;/a&gt; (a JMX-HTTP bridge that&amp;rsquo;s been running in production since 2009), contributed to Knative, and architected OpenShift Serverless. I co-authored two O&amp;rsquo;Reilly books: &lt;a href="https://k8spatterns.io" target="_blank" rel="noreferrer"&gt;Kubernetes Patterns&lt;/a&gt; and &lt;a href="https://www.oreilly.com/library/view/generative-ai-on/9781098164263/" target="_blank" rel="noreferrer"&gt;Generative AI on Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m curious about what&amp;rsquo;s coming but also honest about the parts that worry me. AI is reshaping how knowledge workers operate, and the transition has costs that nobody talks about enough. This blog tries to name some of them.&lt;/p&gt;
&lt;p&gt;I live and work in Franconia, Germany. I still grow chili peppers.&lt;/p&gt;
&lt;p&gt;For the full professional story, find me on &lt;a href="https://linkedin.com/in/ro14nd" target="_blank" rel="noreferrer"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://github.com/rhuss" target="_blank" rel="noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Contact
 &lt;div id="contact" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#contact" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Roland Huß, &lt;a href="mailto:blog@ro14nd.de" &gt;blog@ro14nd.de&lt;/a&gt;&lt;/p&gt;

&lt;h3 class="relative group"&gt;Legal
 &lt;div id="legal" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#legal" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ro14nd.de/datenschutz/" &gt;Privacy Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ro14nd.de/license/" &gt;License&lt;/a&gt; (CC BY 4.0)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>License</title><link>https://ro14nd.de/license/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ro14nd.de/license/</guid><description>
&lt;h2 class="relative group"&gt;Content License
 &lt;div id="content-license" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#content-license" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Unless otherwise noted, the content on this blog is licensed under the &lt;a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer"&gt;Creative Commons Attribution 4.0 International License&lt;/a&gt; (CC BY 4.0).&lt;/p&gt;

&lt;h3 class="relative group"&gt;You are free to
 &lt;div id="you-are-free-to" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#you-are-free-to" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; — copy and redistribute the material in any medium or format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapt&lt;/strong&gt; — remix, transform, and build upon the material for any purpose, even commercially&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Under the following terms
 &lt;div id="under-the-following-terms" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#under-the-following-terms" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attribution&lt;/strong&gt; — You must give appropriate credit, provide a link to the license, and indicate if changes were made.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Code Examples
 &lt;div id="code-examples" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#code-examples" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Code snippets and examples in blog posts are provided under the &lt;a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noreferrer"&gt;Apache License 2.0&lt;/a&gt; unless otherwise stated.&lt;/p&gt;</description></item><item><title>Privacy Policy</title><link>https://ro14nd.de/datenschutz/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://ro14nd.de/datenschutz/</guid><description>
&lt;h2 class="relative group"&gt;1. Data controller
 &lt;div id="1-data-controller" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-data-controller" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Roland Huß, &lt;a href="mailto:blog@ro14nd.de" &gt;blog@ro14nd.de&lt;/a&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;2. Hosting
 &lt;div id="2-hosting" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-hosting" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This website is hosted on GitHub Pages (GitHub Inc., USA). GitHub may process technical data such as IP addresses. For more information, see the &lt;a href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement" target="_blank" rel="noreferrer"&gt;GitHub Privacy Statement&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;3. Comments
 &lt;div id="3-comments" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-comments" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Comments are powered by Giscus, which is based on GitHub Discussions. A GitHub account is required to participate. The &lt;a href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement" target="_blank" rel="noreferrer"&gt;GitHub Privacy Statement&lt;/a&gt; applies.&lt;/p&gt;

&lt;h2 class="relative group"&gt;4. Analytics
 &lt;div id="4-analytics" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-analytics" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This website does not use cookies or tracking. No personal data is collected for analytics purposes.&lt;/p&gt;

&lt;h2 class="relative group"&gt;5. Your rights
 &lt;div id="5-your-rights" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#5-your-rights" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You have the right to access, rectification, erasure, restriction of processing, data portability, and objection. Contact me at the email address above.&lt;/p&gt;

&lt;h2 class="relative group"&gt;6. Social media links
 &lt;div id="6-social-media-links" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#6-social-media-links" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This website contains links to LinkedIn, Mastodon, and Bluesky. These are simple hyperlinks with no tracking or data transfer to these services when visiting this website.&lt;/p&gt;</description></item></channel></rss>